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   * </p>
48   *
49   * @param <FS> An {@link HttpFileSystem} subclass
50   */
51  public class HttpFileObject<FS extends HttpFileSystem> extends AbstractFileObject<FS> {
52  
53      /**
54       * An InputStream that cleans up the HTTP connection on close.
55       */
56      static class HttpInputStream extends MonitorInputStream {
57          private final GetMethod method;
58  
59          public HttpInputStream(final GetMethod method) throws IOException {
60              super(method.getResponseBodyAsStream());
61              this.method = method;
62          }
63  
64          /**
65           * Called after the stream has been closed.
66           */
67          @Override
68          protected void onClose() throws IOException {
69              method.releaseConnection();
70          }
71      }
72  
73      private final String urlCharset;
74      private final String userAgent;
75      private final boolean followRedirect;
76  
77      private HeadMethod method;
78  
79      protected HttpFileObject(final AbstractFileName name, final FS fileSystem) {
80          this(name, fileSystem, HttpFileSystemConfigBuilder.getInstance());
81      }
82  
83      protected HttpFileObject(final AbstractFileName name, final FS fileSystem,
84              final HttpFileSystemConfigBuilder builder) {
85          super(name, fileSystem);
86          final FileSystemOptions fileSystemOptions = fileSystem.getFileSystemOptions();
87          urlCharset = builder.getUrlCharset(fileSystemOptions);
88          userAgent = builder.getUserAgent(fileSystemOptions);
89          followRedirect = builder.getFollowRedirect(fileSystemOptions);
90      }
91  
92      /**
93       * Detaches this file object from its file resource.
94       */
95      @Override
96      protected void doDetach() throws Exception {
97          method = null;
98      }
99  
100     /**
101      * Returns the size of the file content (in bytes).
102      */
103     @Override
104     protected long doGetContentSize() throws Exception {
105         final Header header = method.getResponseHeader("content-length");
106         if (header == null) {
107             // Assume 0 content-length
108             return 0;
109         }
110         return Long.parseLong(header.getValue());
111     }
112 
113     /**
114      * Creates an input stream to read the file content from. Is only called if {@link #doGetType} returns
115      * {@link FileType#FILE}.
116      * <p>
117      * It is guaranteed that there are no open output streams for this file when this method is called.
118      * </p>
119      * <p>
120      * The returned stream does not have to be buffered.
121      * </p>
122      */
123     @Override
124     protected InputStream doGetInputStream() throws Exception {
125         final GetMethod getMethod = new GetMethod();
126         setupMethod(getMethod);
127         final int status = getAbstractFileSystem().getClient().executeMethod(getMethod);
128         if (status == HttpURLConnection.HTTP_NOT_FOUND) {
129             throw new FileNotFoundException(getName());
130         }
131         if (status != HttpURLConnection.HTTP_OK) {
132             throw new FileSystemException("vfs.provider.http/get.error", getName(), Integer.valueOf(status));
133         }
134 
135         return new HttpInputStream(getMethod);
136     }
137 
138     /**
139      * Returns the last modified time of this file.
140      * <p>
141      * This implementation throws an exception.
142      * </p>
143      */
144     @Override
145     protected long doGetLastModifiedTime() throws Exception {
146         final Header header = method.getResponseHeader("last-modified");
147         FileSystemException.requireNonNull(header, "vfs.provider.http/last-modified.error", getName());
148         return DateUtil.parseDate(header.getValue()).getTime();
149     }
150 
151     @Override
152     protected RandomAccessContent doGetRandomAccessContent(final RandomAccessMode mode) throws Exception {
153         return new HttpRandomAccessContent<>(this, mode);
154     }
155 
156     /**
157      * Determines the type of this file. Must not return null. The return value of this method is cached, so the
158      * implementation can be expensive.
159      */
160     @Override
161     protected FileType doGetType() throws Exception {
162         // Use the HEAD method to probe the file.
163         final int status = this.getHeadMethod().getStatusCode();
164         if (status == HttpURLConnection.HTTP_OK
165                 || status == HttpURLConnection.HTTP_BAD_METHOD /* method is bad, but resource exist */) {
166             return FileType.FILE;
167         } else if (status == HttpURLConnection.HTTP_NOT_FOUND || status == HttpURLConnection.HTTP_GONE) {
168             return FileType.IMAGINARY;
169         } else {
170             throw new FileSystemException("vfs.provider.http/head.error", getName(), Integer.valueOf(status));
171         }
172     }
173 
174     @Override
175     protected boolean doIsWriteable() throws Exception {
176         return false;
177     }
178 
179     /**
180      * Lists the children of this file.
181      */
182     @Override
183     protected String[] doListChildren() throws Exception {
184         throw new Exception("Not implemented.");
185     }
186 
187     protected String encodePath(final String decodedPath) throws URIException {
188         return URIUtil.encodePath(decodedPath);
189     }
190 
191     @Override
192     protected FileContentInfoFactory getFileContentInfoFactory() {
193         return new HttpFileContentInfoFactory();
194     }
195 
196     protected boolean getFollowRedirect() {
197         return followRedirect;
198     }
199 
200     protected String getUserAgent() {
201         return userAgent;
202     }
203 
204     HeadMethod getHeadMethod() throws IOException {
205         if (method != null) {
206             return method;
207         }
208         method = new HeadMethod();
209         try {
210             setupMethod(method);
211             final HttpClient client = getAbstractFileSystem().getClient();
212             client.executeMethod(method);
213         } finally {
214             method.releaseConnection();
215         }
216         return method;
217     }
218 
219     protected String getUrlCharset() {
220         return urlCharset;
221     }
222 
223     /**
224      * Prepares a HttpMethod object.
225      *
226      * @param method The object which gets prepared to access the file object.
227      * @throws FileSystemException if an error occurs.
228      * @throws URIException if path cannot be represented.
229      * @since 2.0 (was package)
230      */
231     protected void setupMethod(final HttpMethod method) throws FileSystemException, URIException {
232         final String pathEncoded = ((URLFileName) getName()).getPathQueryEncoded(this.getUrlCharset());
233         method.setPath(pathEncoded);
234         method.setFollowRedirects(this.getFollowRedirect());
235         method.setRequestHeader("User-Agent", this.getUserAgent());
236     }
237 
238     /*
239      * protected Map doGetAttributes() throws Exception { TreeMap map = new TreeMap();
240      *
241      * Header contentType = method.getResponseHeader("content-type"); if (contentType != null) { HeaderElement[] element
242      * = contentType.getValues(); if (element != null && element.length > 0) { map.put("content-type",
243      * element[0].getName()); } }
244      *
245      * map.put("content-encoding", method.getResponseCharSet()); return map; }
246      */
247 }