(no commit message)
[utils] / crawler / basic / src / org / wamblee / crawler / AbstractPageRequest.java
1 /*
2  * Copyright 2005 the original author or authors.
3  * 
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  * 
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  * 
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package org.wamblee.crawler;
18
19 import java.io.ByteArrayOutputStream;
20 import java.io.File;
21 import java.io.IOException;
22 import java.io.PrintStream;
23
24 import javax.xml.transform.OutputKeys;
25 import javax.xml.transform.Transformer;
26 import javax.xml.transform.TransformerConfigurationException;
27 import javax.xml.transform.TransformerException;
28 import javax.xml.transform.TransformerFactory;
29 import javax.xml.transform.dom.DOMSource;
30 import javax.xml.transform.stream.StreamResult;
31
32 import org.apache.commons.httpclient.Header;
33 import org.apache.commons.httpclient.HttpClient;
34 import org.apache.commons.httpclient.HttpException;
35 import org.apache.commons.httpclient.HttpMethod;
36 import org.apache.commons.httpclient.HttpStatus;
37 import org.apache.commons.httpclient.NameValuePair;
38 import org.apache.commons.httpclient.methods.GetMethod;
39 import org.apache.commons.logging.Log;
40 import org.apache.commons.logging.LogFactory;
41 import org.w3c.dom.Document;
42 import org.w3c.tidy.Tidy;
43 import org.wamblee.io.FileResource;
44 import org.wamblee.xml.XSLT;
45
46 /**
47  * General support claas for all kinds of requests.
48  */
49 public abstract class AbstractPageRequest implements PageRequest {
50
51     private static final Log LOG = LogFactory.getLog(AbstractPageRequest.class);
52     private static final String REDIRECT_HEADER = "Location";
53    
54     private int _maxTries; 
55     private int _maxDelay; 
56
57     private NameValuePair[] _params;
58
59     private String _xslt;
60     
61     private PrintStream _os; 
62
63     protected AbstractPageRequest(int aMaxTries, int aMaxDelay, NameValuePair[] aParams, String aXslt, PrintStream aOs) {
64         if ( aParams == null ) { 
65             throw new IllegalArgumentException("aParams is null");
66         }
67         if ( aXslt == null ) { 
68             throw new IllegalArgumentException("aXslt is null");
69         }
70         _maxTries = aMaxTries;
71         _maxDelay = aMaxDelay;
72         _params = aParams;
73         _xslt = aXslt;
74         _os = aOs; 
75     }
76     
77     /* (non-Javadoc)
78      * @see org.wamblee.crawler.PageRequest#overrideXslt(java.lang.String)
79      */
80     public void overrideXslt(String aXslt) {
81         _xslt = aXslt;   
82     }
83     
84     protected NameValuePair[] getParameters() { 
85         return _params;
86     }
87     
88     protected Document executeMethod(HttpClient client, HttpMethod method) throws TransformerException { 
89         int triesLeft = _maxTries; 
90         while ( triesLeft > 0 ) {
91             triesLeft--;
92             try { 
93                 return executeMethodWithoutRetries(client, method);
94             } catch (TransformerException e) { 
95                 if ( triesLeft == 0 ) { 
96                     throw e;
97                 }
98             }
99         }
100         throw new RuntimeException("Code should never reach this point");
101     }
102         
103
104     protected Document executeMethodWithoutRetries(HttpClient client, HttpMethod method) throws TransformerException {
105         try {
106             // Execute the method.
107             method = executeWithRedirects(client, method);
108
109             // Transform the HTML into wellformed XML.
110             Tidy tidy = new Tidy();
111             tidy.setXHTML(true);
112             tidy.setQuiet(true); 
113             tidy.setShowWarnings(false);
114             if ( _os != null ) { 
115                 _os.println("Content of '" + method.getURI() + "'");
116                 _os.println();
117             }
118             // We let jtidy produce raw output because the DOM it produces is 
119             // is not namespace aware. We let the XSLT processor parse the XML again
120             // to ensure that the XSLT uses a namespace aware DOM tree. An alternative 
121             // is to configure namespace awareness of the XML parser in a system wide way. 
122             ByteArrayOutputStream xhtml = new ByteArrayOutputStream();
123             tidy.parse(method.getResponseBodyAsStream(), xhtml);
124             _os.print(new String(xhtml.toByteArray()));
125             // Obtaining the XML as dom is not used. 
126             //Document w3cDoc = tidy.parseDOM(method.getResponseBodyAsStream(),
127             //        _os);
128             if ( _os != null ) { 
129                 _os.println();
130             }
131             xhtml.flush();
132             byte[] xhtmlData = xhtml.toByteArray();
133             Document transformed = new XSLT().transform(xhtmlData, new FileResource(new File(_xslt)));
134             _os.println("Transformed result is: ");
135             Transformer transformer = TransformerFactory.newInstance().newTransformer();
136             transformer.setParameter(OutputKeys.INDENT, "yes");
137             transformer.setParameter(OutputKeys.METHOD, "xml");
138             transformer.transform(new DOMSource(transformed), new StreamResult(_os));
139             
140             return transformed;
141         } catch (HttpException e) { 
142             throw new RuntimeException(e.getMessage(), e); 
143         } catch (IOException e) { 
144             throw new RuntimeException(e.getMessage(), e);
145         } catch (TransformerConfigurationException e) { 
146             throw new RuntimeException(e.getMessage(), e);
147         } finally {
148             // Release the connection.
149             method.releaseConnection();
150         }
151     }
152     
153     private void delay() { 
154         try {
155             Thread.sleep((long)((float)_maxDelay* Math.random()));
156         } catch (InterruptedException e) { 
157             // 
158         }
159     }
160
161
162     /**
163      * @param aClient
164      * @param aMethod
165      * @throws IOException
166      * @throws HttpException
167      */
168     private HttpMethod executeWithRedirects(HttpClient aClient, HttpMethod aMethod) throws IOException, HttpException {
169         delay();
170         int statusCode = aClient.executeMethod(aMethod);
171
172         switch (statusCode) { 
173         case HttpStatus.SC_OK: {
174             return aMethod;
175         }
176         case HttpStatus.SC_MOVED_PERMANENTLY:
177         case HttpStatus.SC_MOVED_TEMPORARILY:
178         case HttpStatus.SC_SEE_OTHER: {
179             aMethod.releaseConnection();
180             Header header = aMethod.getResponseHeader(REDIRECT_HEADER);
181             aMethod = new GetMethod(header.getValue());
182             return executeWithRedirects(aClient, aMethod); // TODO protect against infinite recursion.
183         }
184         default: {
185             throw new RuntimeException("Method failed: "
186                     + aMethod.getStatusLine());
187         }
188         }
189     }
190 }