View Javadoc

1   /*
2    * Copyright 2002-2004 The Apache Software Foundation
3    * Licensed  under the  Apache License,  Version 2.0  (the "License");
4    * you may not use  this file  except in  compliance with the License.
5    * You may obtain a copy of the License at
6    *
7    *   http://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed  under the  License is distributed on an "AS IS" BASIS,
11   * WITHOUT  WARRANTIES OR CONDITIONS  OF ANY KIND, either  express  or
12   * implied.
13   *
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.struts.flow.core.source.impl;
18  
19  import java.io.IOException;
20  import java.io.InputStream;
21  import java.io.UnsupportedEncodingException;
22  import java.net.HttpURLConnection;
23  import java.net.URL;
24  import java.net.URLConnection;
25  import java.net.URLDecoder;
26  import java.util.Iterator;
27  import java.util.Map;
28  
29  import org.apache.struts.flow.core.source.Source;
30  import org.apache.struts.flow.core.source.SourceException;
31  import org.apache.struts.flow.core.source.SourceParameters;
32  import org.apache.struts.flow.core.source.SourceResolver;
33  import org.apache.struts.flow.core.source.SourceUtil;
34  import org.apache.struts.flow.core.source.SourceValidity;
35  import org.apache.struts.flow.core.source.impl.validity.TimeStampValidity;
36  
37  /***
38   * Description of a source which is described by an URL.
39   *
40   * @author <a href="mailto:dev@avalon.apache.org">Avalon Development Team</a>
41   * @version CVS $Revision: 1.5 $ $Date: 2004/02/28 11:47:24 $
42   */
43  public class URLSource extends AbstractSource implements Source
44  {
45  
46      /*** The URL of the source */
47      protected URL m_url;
48  
49      /*** The connection for a real URL */
50      protected URLConnection m_connection;
51  
52      /*** The <code>SourceParameters</code> used for a post*/
53      protected SourceParameters m_parameters;
54  
55      /*** The encoding of the <code>SourceParameters</code>*/
56      protected String m_encoding;
57  
58      /*** Is this a post? */
59      protected boolean m_isPost = false;
60  
61      /*** Does this source exist ? */
62      protected boolean m_exists = false;
63  
64      /*** the prev returned SourceValidity */
65      protected SourceValidity m_cachedValidity;
66  
67      protected long m_cachedLastModificationDate;
68  
69      /*** The content type (if known) */
70      protected String m_mimeType;
71  
72      /***
73       * Constructor
74       */
75      public URLSource()
76      {
77      }
78  
79      /***
80       * Initialize a new object from a <code>URL</code>.
81       * @param parameters This is optional
82       */
83      public void init(URL url, Map parameters) throws IOException
84      {
85          String systemId = url.toExternalForm();
86          setSystemId(systemId);
87          setScheme(SourceUtil.getScheme(systemId));
88  
89          m_url = url;
90          m_isPost = false;
91          // get the default system encoding in case no encoding is specified
92          try {
93              m_encoding = System.getProperty("file.property", "ISO-8859-1");
94          } catch (SecurityException e) {
95              m_encoding = "ISO-8859-1"; 
96          }
97  
98          if (null != parameters)
99          {
100             m_parameters = (SourceParameters) parameters.get(SourceResolver.URI_PARAMETERS);
101             final String method = (String) parameters.get(SourceResolver.METHOD);
102 
103             if ("POST".equalsIgnoreCase(method))
104                 m_isPost = true;
105 
106             final String encoding = (String) parameters.get(SourceResolver.URI_ENCODING);
107             if (encoding != null && !"".equals(encoding))
108                 m_encoding = encoding;
109         }
110 
111         if (null != m_parameters && m_parameters.hasParameters() && !m_isPost)
112         {
113             StringBuffer urlBuffer = new StringBuffer(systemId);
114             String key;
115             final Iterator i = m_parameters.getParameterNames();
116             Iterator values;
117             String value;
118             boolean first = (systemId.indexOf('?') == -1);
119             if (first == true)
120                 urlBuffer.append('?');
121             while (i.hasNext())
122             {
123                 key = (String) i.next();
124                 values = m_parameters.getParameterValues(key);
125                 while (values.hasNext() == true)
126                 {
127                     value = SourceUtil.encode((String) values.next(), m_encoding);
128                     if (first == false)
129                         urlBuffer.append('&');
130                     first = false;
131                     urlBuffer.append(key);
132                     urlBuffer.append('=');
133                     urlBuffer.append(value);
134                 }
135             }
136 
137             m_url = new URL(urlBuffer.toString());
138             m_parameters = null;
139         }
140     }
141 
142     /***
143      * Get the last modification date and content length of the source.
144      * Any exceptions are ignored.
145      * Override this to get the real information
146      */
147     protected void getInfos()
148     {
149         // exists will be set below depending on the m_url type
150         m_exists = false;
151 
152         if (!m_isPost)
153         {
154             try
155             {
156                 if (null == m_connection)
157                 {
158                     m_connection = m_url.openConnection();
159                     String userInfo = getUserInfo();
160                     if (m_url.getProtocol().startsWith("http") && userInfo != null){
161                         m_connection.setRequestProperty("Authorization", "Basic " + SourceUtil.encodeBASE64(userInfo));
162                     }
163                 }
164                 setLastModified(m_connection.getLastModified());
165                 m_mimeType = m_connection.getContentType();
166                 int contentLength = m_connection.getContentLength();
167                 setContentLength(contentLength);
168                 if ( m_connection instanceof HttpURLConnection )
169                 {
170                     // check the status code for exists
171                     // if the url does not exists we might also get an IOException here!
172                     try 
173                     {
174                         final int statusCode = ((HttpURLConnection)m_connection).getResponseCode();
175                         if ( statusCode == 200 || statusCode == 304 )
176                         {
177                             m_exists = true;
178                         } 
179                         else
180                         {
181                             m_exists = false;
182                         }                        
183                     }
184                     catch (IOException ignore)
185                     {
186                         m_exists = false;
187                     }
188                         
189                 } 
190                 else 
191                 {
192                     m_exists = contentLength > 0;
193                 }
194             }
195             catch (IOException ignore)
196             {
197                 super.getInfos();
198             }
199         }
200         else
201         {
202             // do not open m_connection when using post!
203             super.getInfos();
204         }
205     }
206 
207     /***
208      * Does this source exist ?
209      */
210     public boolean exists()
211     {
212         checkInfos();
213         return m_exists;
214     }
215 
216     /***
217      * Return an <code>InputStream</code> object to read from the source.
218      *
219      * The returned stream must be closed by the calling code.
220      *
221      * @throws SourceException if file not found or
222      *         HTTP location does not exist.
223      * @throws IOException if I/O error occured.
224      */
225     public InputStream getInputStream() throws IOException, SourceException
226     {
227         checkInfos();
228         InputStream input = null;
229         if (m_connection == null)
230         {
231             m_connection = m_url.openConnection();
232 
233             String userInfo = getUserInfo();
234             if (m_url.getProtocol().startsWith("http") && userInfo != null)
235             {
236                 m_connection.setRequestProperty("Authorization", "Basic " + SourceUtil.encodeBASE64(userInfo));
237             }
238 
239             // do a post operation
240             if (m_connection instanceof HttpURLConnection && m_isPost)
241             {
242                 StringBuffer buffer = new StringBuffer(2000);
243                 String key;
244                 Iterator i = m_parameters.getParameterNames();
245                 Iterator values;
246                 String value;
247                 boolean first = true;
248                 while (i.hasNext())
249                 {
250                     key = (String) i.next();
251                     values = m_parameters.getParameterValues(key);
252                     while (values.hasNext() == true)
253                     {
254                         value = SourceUtil.encode((String) values.next(), m_encoding);
255                         if (first == false)
256                             buffer.append('&');
257                         first = false;
258                         buffer.append(key.toString());
259                         buffer.append('=');
260                         buffer.append(value);
261                     }
262                 }
263                 HttpURLConnection httpCon = (HttpURLConnection) m_connection;
264                 httpCon.setDoInput(true);
265 
266                 if (buffer.length() > 1)
267                 { // only post if we have parameters
268                     String postString = buffer.toString();
269                     httpCon.setRequestMethod("POST"); // this is POST
270                     httpCon.setDoOutput(true);
271                     httpCon.setRequestProperty("Content-type", "application/x-www-form-urlencoded");
272 
273                     // A content-length header must be contained in a POST request
274                     httpCon.setRequestProperty("Content-length", Integer.toString(postString.length()));
275                     java.io.OutputStream out = new java.io.BufferedOutputStream(httpCon.getOutputStream());
276                     out.write(postString.getBytes());
277                     out.close();
278                 }
279                 input = httpCon.getInputStream();
280                 m_connection = null; // make sure a new m_connection is created next time
281                 return input;
282             }
283         }
284         input = m_connection.getInputStream();
285         m_connection = null; // make sure a new m_connection is created next time
286         return input;
287     }
288 
289     /***
290      *  Get the Validity object. This can either wrap the last modification
291      *  date or the expires information or...
292      *  If it is currently not possible to calculate such an information
293      *  <code>null</code> is returned.
294      */
295     public SourceValidity getValidity()
296     {
297         final long lm = getLastModified();
298         if (lm > 0)
299         {
300             if (lm == m_cachedLastModificationDate)
301                 return m_cachedValidity;
302 
303             m_cachedLastModificationDate = lm;
304             m_cachedValidity = new TimeStampValidity(lm);
305             return m_cachedValidity;
306         }
307         return null;
308     }
309 
310     /***
311      * Refresh this object and update the last modified date
312      * and content length.
313      */
314     public void refresh()
315     {
316         // reset m_connection
317         m_connection = null;
318         super.refresh();
319     }
320 
321     /***
322      * The mime-type of the content described by this object.
323      * If the source is not able to determine the mime-type by itself
324      * this can be null.
325      */
326     public String getMimeType()
327     {
328         return m_mimeType;
329     }
330 
331     /***
332      * The decoded userinfo for this source.
333      * null, if no userinfo exists
334      */
335     protected String getUserInfo()
336     {
337         if (m_url == null) return null;
338         String ui = m_url.getUserInfo();
339         if (ui == null) return null;
340 
341         try
342         {
343          ui = URLDecoder.decode(ui,"UTF-8");
344         }
345         catch (UnsupportedEncodingException e)
346         {
347          // Platform does not support UTF-8. This should never happen.
348          // e.printStackTrace();
349         }
350         return ui;
351     }
352 }