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.http5;
18  
19  import java.io.IOException;
20  import java.io.InputStream;
21  import java.net.URI;
22  
23  import org.apache.commons.vfs2.FileContentInfoFactory;
24  import org.apache.commons.vfs2.FileNotFoundException;
25  import org.apache.commons.vfs2.FileSystemException;
26  import org.apache.commons.vfs2.FileSystemOptions;
27  import org.apache.commons.vfs2.FileType;
28  import org.apache.commons.vfs2.RandomAccessContent;
29  import org.apache.commons.vfs2.provider.AbstractFileName;
30  import org.apache.commons.vfs2.provider.AbstractFileObject;
31  import org.apache.commons.vfs2.provider.GenericURLFileName;
32  import org.apache.commons.vfs2.util.RandomAccessMode;
33  import org.apache.hc.client5.http.classic.methods.HttpGet;
34  import org.apache.hc.client5.http.classic.methods.HttpHead;
35  import org.apache.hc.client5.http.classic.methods.HttpUriRequest;
36  import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
37  import org.apache.hc.client5.http.protocol.HttpClientContext;
38  import org.apache.hc.client5.http.utils.DateUtils;
39  import org.apache.hc.client5.http.utils.URIUtils;
40  import org.apache.hc.core5.http.ClassicHttpResponse;
41  import org.apache.hc.core5.http.Header;
42  import org.apache.hc.core5.http.HttpHeaders;
43  import org.apache.hc.core5.http.HttpResponse;
44  import org.apache.hc.core5.http.HttpStatus;
45  
46  /**
47   * A file object backed by Apache HttpComponents HttpClient v5.
48   *
49   * @param <FS> An {@link Http5FileSystem} subclass
50   *
51   * @since 2.5.0
52   */
53  public class Http5FileObject<FS extends Http5FileSystem> extends AbstractFileObject<FS> {
54  
55      /**
56       * URL charset string.
57       */
58      private final String urlCharset;
59  
60      /**
61       * Internal URI mapped to this {@code FileObject}.
62       * For example, the internal URI of {@code http4://example.com/a.txt} is {@code http://example.com/a.txt}.
63       */
64      private final URI internalURI;
65  
66      /**
67       * The last executed HEAD {@code HttpResponse} object.
68       */
69      private HttpResponse lastHeadResponse;
70  
71      /**
72       * Construct {@code Http4FileObject}.
73       *
74       * @param name file name
75       * @param fileSystem file system
76       * @throws FileSystemException if any error occurs
77       */
78      protected Http5FileObject(final AbstractFileName name, final FS fileSystem)
79              throws FileSystemException {
80          this(name, fileSystem, Http5FileSystemConfigBuilder.getInstance());
81      }
82  
83      /**
84       * Construct {@code Http4FileObject}.
85       *
86       * @param name file name
87       * @param fileSystem file system
88       * @param builder {@code Http4FileSystemConfigBuilder} object
89       * @throws FileSystemException if any error occurs
90       */
91      protected Http5FileObject(final AbstractFileName name, final FS fileSystem,
92              final Http5FileSystemConfigBuilder builder) throws FileSystemException {
93          super(name, fileSystem);
94          final FileSystemOptions fileSystemOptions = fileSystem.getFileSystemOptions();
95          urlCharset = builder.getUrlCharset(fileSystemOptions);
96          final String pathEncoded = ((GenericURLFileName) name).getPathQueryEncoded(getUrlCharset());
97          internalURI = URIUtils.resolve(fileSystem.getInternalBaseURI(), pathEncoded);
98      }
99  
100     @Override
101     protected void doDetach() throws Exception {
102         lastHeadResponse = null;
103     }
104 
105     @Override
106     protected long doGetContentSize() throws Exception {
107         if (lastHeadResponse == null) {
108             return 0L;
109         }
110 
111         final Header header = lastHeadResponse.getFirstHeader(HttpHeaders.CONTENT_LENGTH);
112 
113         if (header == null) {
114             // Assume 0 content-length
115             return 0;
116         }
117 
118         return Long.parseLong(header.getValue());
119     }
120 
121     @Override
122     protected InputStream doGetInputStream(final int bufferSize) throws Exception {
123         final HttpGet getRequest = new HttpGet(getInternalURI());
124         final ClassicHttpResponse httpResponse = executeHttpUriRequest(getRequest);
125         final int status = httpResponse.getCode();
126 
127         if (status == HttpStatus.SC_NOT_FOUND) {
128             throw new FileNotFoundException(getName());
129         }
130 
131         if (status != HttpStatus.SC_OK) {
132             throw new FileSystemException("vfs.provider.http/get.error", getName(), Integer.valueOf(status));
133         }
134 
135         return new MonitoredHttpResponseContentInputStream(httpResponse, bufferSize);
136     }
137 
138 
139     @Override
140     protected long doGetLastModifiedTime() throws Exception {
141         FileSystemException.requireNonNull(lastHeadResponse, "vfs.provider.http/last-modified.error", getName());
142 
143         final Header header = lastHeadResponse.getFirstHeader("Last-Modified");
144 
145         FileSystemException.requireNonNull(header, "vfs.provider.http/last-modified.error", getName());
146 
147         return DateUtils.parseDate(header.getValue()).getTime();
148     }
149 
150     @Override
151     protected RandomAccessContent doGetRandomAccessContent(final RandomAccessMode mode) throws Exception {
152         return new Http5RandomAccessContent<>(this, mode);
153     }
154 
155     @Override
156     protected FileType doGetType() throws Exception {
157         lastHeadResponse = executeHttpUriRequest(new HttpHead(getInternalURI()));
158         final int status = lastHeadResponse.getCode();
159 
160         if (status == HttpStatus.SC_OK
161                 || status == HttpStatus.SC_METHOD_NOT_ALLOWED /* method is not allowed, but resource exist */) {
162             return FileType.FILE;
163         }
164         if (status == HttpStatus.SC_NOT_FOUND || status == HttpStatus.SC_GONE) {
165             return FileType.IMAGINARY;
166         }
167         throw new FileSystemException("vfs.provider.http/head.error", getName(), Integer.valueOf(status));
168     }
169 
170     @Override
171     protected boolean doIsWriteable() throws Exception {
172         return false;
173     }
174 
175     @Override
176     protected String[] doListChildren() throws Exception {
177         throw new UnsupportedOperationException("Not implemented.");
178     }
179 
180     /**
181      * Execute the request using the given {@code httpRequest} and return a {@code ClassicHttpResponse} from the execution.
182      *
183      * @param httpRequest {@code HttpUriRequest} object
184      * @return {@code ClassicHttpResponse} from the execution
185      * @throws IOException if IO error occurs
186      */
187     protected ClassicHttpResponse executeHttpUriRequest(final HttpUriRequest httpRequest) throws IOException {
188         final CloseableHttpClient httpClient = (CloseableHttpClient) getAbstractFileSystem().getHttpClient();
189         final HttpClientContext httpClientContext = getAbstractFileSystem().getHttpClientContext();
190         return httpClient.execute(httpRequest, httpClientContext);
191     }
192 
193     @Override
194     protected FileContentInfoFactory getFileContentInfoFactory() {
195         return new Http5FileContentInfoFactory();
196     }
197 
198     /**
199      * Return the internal {@code URI} object mapped to this file object.
200      *
201      * @return the internal {@code URI} object mapped to this file object
202      */
203     protected URI getInternalURI() {
204         return internalURI;
205     }
206 
207     /**
208      * Return the last executed HEAD {@code HttpResponse} object.
209      *
210      * @return the last executed HEAD {@code HttpResponse} object
211      * @throws IOException if IO error occurs
212      */
213     HttpResponse getLastHeadResponse() throws IOException {
214         if (lastHeadResponse != null) {
215             return lastHeadResponse;
216         }
217 
218         return executeHttpUriRequest(new HttpHead(getInternalURI()));
219     }
220 
221     /**
222      * Return URL charset string.
223      * @return URL charset string
224      */
225     protected String getUrlCharset() {
226         return urlCharset;
227     }
228 
229 }