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