View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.vfs2.provider.http;
18  
19  import java.io.IOException;
20  import java.io.InputStream;
21  import java.net.HttpURLConnection;
22  
23  import org.apache.commons.httpclient.Header;
24  import org.apache.commons.httpclient.HttpClient;
25  import org.apache.commons.httpclient.HttpMethod;
26  import org.apache.commons.httpclient.URIException;
27  import org.apache.commons.httpclient.methods.GetMethod;
28  import org.apache.commons.httpclient.methods.HeadMethod;
29  import org.apache.commons.httpclient.util.DateUtil;
30  import org.apache.commons.httpclient.util.URIUtil;
31  import org.apache.commons.vfs2.FileContentInfoFactory;
32  import org.apache.commons.vfs2.FileNotFoundException;
33  import org.apache.commons.vfs2.FileSystemException;
34  import org.apache.commons.vfs2.FileSystemOptions;
35  import org.apache.commons.vfs2.FileType;
36  import org.apache.commons.vfs2.RandomAccessContent;
37  import org.apache.commons.vfs2.provider.AbstractFileName;
38  import org.apache.commons.vfs2.provider.AbstractFileObject;
39  import org.apache.commons.vfs2.provider.URLFileName;
40  import org.apache.commons.vfs2.util.MonitorInputStream;
41  import org.apache.commons.vfs2.util.RandomAccessMode;
42  
43  /**
44   * A file object backed by Apache Commons HttpClient.
45   * <p>
46   * TODO - status codes.
47   *
48   * @param <FS> An {@link HttpFileSystem} subclass
49   */
50  public class HttpFileObject<FS extends HttpFileSystem> extends AbstractFileObject<FS>
51  {
52      /**
53       * An InputStream that cleans up the HTTP connection on close.
54       */
55      static class HttpInputStream extends MonitorInputStream
56      {
57          private final GetMethod method;
58  
59          public HttpInputStream(final GetMethod method)
60              throws IOException
61          {
62              super(method.getResponseBodyAsStream());
63              this.method = method;
64          }
65  
66          /**
67           * Called after the stream has been closed.
68           */
69          @Override
70          protected void onClose() throws IOException
71          {
72              method.releaseConnection();
73          }
74      }
75      private final String urlCharset;
76      private final String userAgent;
77      private final boolean followRedirect;
78  
79      private HeadMethod method;
80  
81      protected HttpFileObject(final AbstractFileName name, final FS fileSystem)
82      {
83          this(name, fileSystem, HttpFileSystemConfigBuilder.getInstance());
84      }
85  
86      protected HttpFileObject(final AbstractFileName name, final FS fileSystem,
87                               final HttpFileSystemConfigBuilder builder)
88      {
89          super(name, fileSystem);
90          final FileSystemOptions fileSystemOptions = fileSystem.getFileSystemOptions();
91          urlCharset = builder.getUrlCharset(fileSystemOptions);
92          userAgent = builder.getUserAgent(fileSystemOptions);
93          followRedirect = builder.getFollowRedirect(fileSystemOptions);
94      }
95  
96      /**
97       * Detaches this file object from its file resource.
98       */
99      @Override
100     protected void doDetach() throws Exception
101     {
102         method = null;
103     }
104 
105     /**
106      * Returns the size of the file content (in bytes).
107      */
108     @Override
109     protected long doGetContentSize() throws Exception
110     {
111         final Header header = method.getResponseHeader("content-length");
112         if (header == null)
113         {
114             // Assume 0 content-length
115             return 0;
116         }
117         return Long.parseLong(header.getValue());
118     }
119 
120     /**
121      * Creates an input stream to read the file content from.  Is only called
122      * if {@link #doGetType} returns {@link FileType#FILE}.
123      * <p>
124      * It is guaranteed that there are no open output streams for this file
125      * when this method is called.
126      * <p>
127      * The returned stream does not have to be buffered.
128      */
129     @Override
130     protected InputStream doGetInputStream() throws Exception
131     {
132         final GetMethod getMethod = new GetMethod();
133         setupMethod(getMethod);
134         final int status = getAbstractFileSystem().getClient().executeMethod(getMethod);
135         if (status == HttpURLConnection.HTTP_NOT_FOUND)
136         {
137             throw new FileNotFoundException(getName());
138         }
139         if (status != HttpURLConnection.HTTP_OK)
140         {
141             throw new FileSystemException("vfs.provider.http/get.error", getName(), Integer.valueOf(status));
142         }
143 
144         return new HttpInputStream(getMethod);
145     }
146 
147     /**
148      * Returns the last modified time of this file.
149      * <p>
150      * This implementation throws an exception.
151      */
152     @Override
153     protected long doGetLastModifiedTime() throws Exception
154     {
155         final Header header = method.getResponseHeader("last-modified");
156         if (header == null)
157         {
158             throw new FileSystemException("vfs.provider.http/last-modified.error", getName());
159         }
160         return DateUtil.parseDate(header.getValue()).getTime();
161     }
162 
163     @Override
164     protected RandomAccessContent doGetRandomAccessContent(final RandomAccessMode mode) throws Exception
165     {
166         return new HttpRandomAccessContent(this, mode);
167     }
168 
169     /**
170      * Determines the type of this file.  Must not return null.  The return
171      * value of this method is cached, so the implementation can be expensive.
172      */
173     @Override
174     protected FileType doGetType() throws Exception
175     {
176         // Use the HEAD method to probe the file.
177         final int status = this.getHeadMethod().getStatusCode();
178         if (status == HttpURLConnection.HTTP_OK
179             || status == HttpURLConnection.HTTP_BAD_METHOD /* method is bad, but resource exist */)
180         {
181             return FileType.FILE;
182         }
183         else if (status == HttpURLConnection.HTTP_NOT_FOUND
184             || status == HttpURLConnection.HTTP_GONE)
185         {
186             return FileType.IMAGINARY;
187         }
188         else
189         {
190             throw new FileSystemException("vfs.provider.http/head.error", getName(), Integer.valueOf(status));
191         }
192     }
193 
194     @Override
195     protected boolean doIsWriteable() throws Exception
196     {
197         return false;
198     }
199 
200     /**
201      * Lists the children of this file.
202      */
203     @Override
204     protected String[] doListChildren() throws Exception
205     {
206         throw new Exception("Not implemented.");
207     }
208 
209     protected String encodePath(final String decodedPath) throws URIException
210     {
211         return URIUtil.encodePath(decodedPath);
212     }
213 
214 
215     @Override
216     protected FileContentInfoFactory getFileContentInfoFactory()
217     {
218         return new HttpFileContentInfoFactory();
219     }
220 
221     protected boolean getFollowRedirect()
222     {
223         return followRedirect;
224     }
225 
226     protected String getUserAgent()
227     {
228         return userAgent;
229     }
230 
231     HeadMethod getHeadMethod() throws IOException
232     {
233         if (method != null)
234         {
235             return method;
236         }
237         method = new HeadMethod();
238         setupMethod(method);
239         final HttpClient client = getAbstractFileSystem().getClient();
240         client.executeMethod(method);
241         method.releaseConnection();
242         return method;
243     }
244 
245     protected String getUrlCharset()
246     {
247         return urlCharset;
248     }
249 
250     /**
251      * Prepares a HttpMethod object.
252      *
253      * @param method The object which gets prepared to access the file object.
254      * @throws FileSystemException if an error occurs.
255      * @throws URIException if path cannot be represented.
256      * @since 2.0 (was package)
257      */
258     protected void setupMethod(final HttpMethod method) throws FileSystemException, URIException
259     {
260         final String pathEncoded = ((URLFileName) getName()).getPathQueryEncoded(this.getUrlCharset());
261         method.setPath(pathEncoded);
262         method.setFollowRedirects(this.getFollowRedirect());
263         method.setRequestHeader("User-Agent", this.getUserAgent());
264     }
265 
266     /*
267     protected Map doGetAttributes() throws Exception
268     {
269         TreeMap map = new TreeMap();
270 
271         Header contentType = method.getResponseHeader("content-type");
272         if (contentType != null)
273         {
274             HeaderElement[] element = contentType.getValues();
275             if (element != null && element.length > 0)
276             {
277                 map.put("content-type", element[0].getName());
278             }
279         }
280 
281         map.put("content-encoding", method.getResponseCharSet());
282         return map;
283     }
284     */
285 }