001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.vfs2.provider.http; 018 019import java.io.IOException; 020import java.io.InputStream; 021import java.net.HttpURLConnection; 022 023import org.apache.commons.httpclient.Header; 024import org.apache.commons.httpclient.HttpClient; 025import org.apache.commons.httpclient.HttpMethod; 026import org.apache.commons.httpclient.URIException; 027import org.apache.commons.httpclient.methods.GetMethod; 028import org.apache.commons.httpclient.methods.HeadMethod; 029import org.apache.commons.httpclient.util.DateUtil; 030import org.apache.commons.httpclient.util.URIUtil; 031import org.apache.commons.vfs2.FileContentInfoFactory; 032import org.apache.commons.vfs2.FileNotFoundException; 033import org.apache.commons.vfs2.FileSystemException; 034import org.apache.commons.vfs2.FileSystemOptions; 035import org.apache.commons.vfs2.FileType; 036import org.apache.commons.vfs2.RandomAccessContent; 037import org.apache.commons.vfs2.provider.AbstractFileName; 038import org.apache.commons.vfs2.provider.AbstractFileObject; 039import org.apache.commons.vfs2.provider.URLFileName; 040import org.apache.commons.vfs2.util.MonitorInputStream; 041import org.apache.commons.vfs2.util.RandomAccessMode; 042 043/** 044 * A file object backed by Apache Commons HttpClient. 045 * <p> 046 * TODO - status codes. 047 * 048 * @param <FS> An {@link HttpFileSystem} subclass 049 */ 050public class HttpFileObject<FS extends HttpFileSystem> extends AbstractFileObject<FS> { 051 /** 052 * An InputStream that cleans up the HTTP connection on close. 053 */ 054 static class HttpInputStream extends MonitorInputStream { 055 private final GetMethod method; 056 057 public HttpInputStream(final GetMethod method) throws IOException { 058 super(method.getResponseBodyAsStream()); 059 this.method = method; 060 } 061 062 /** 063 * Called after the stream has been closed. 064 */ 065 @Override 066 protected void onClose() throws IOException { 067 method.releaseConnection(); 068 } 069 } 070 071 private final String urlCharset; 072 private final String userAgent; 073 private final boolean followRedirect; 074 075 private HeadMethod method; 076 077 protected HttpFileObject(final AbstractFileName name, final FS fileSystem) { 078 this(name, fileSystem, HttpFileSystemConfigBuilder.getInstance()); 079 } 080 081 protected HttpFileObject(final AbstractFileName name, final FS fileSystem, 082 final HttpFileSystemConfigBuilder builder) { 083 super(name, fileSystem); 084 final FileSystemOptions fileSystemOptions = fileSystem.getFileSystemOptions(); 085 urlCharset = builder.getUrlCharset(fileSystemOptions); 086 userAgent = builder.getUserAgent(fileSystemOptions); 087 followRedirect = builder.getFollowRedirect(fileSystemOptions); 088 } 089 090 /** 091 * Detaches this file object from its file resource. 092 */ 093 @Override 094 protected void doDetach() throws Exception { 095 method = null; 096 } 097 098 /** 099 * Returns the size of the file content (in bytes). 100 */ 101 @Override 102 protected long doGetContentSize() throws Exception { 103 final Header header = method.getResponseHeader("content-length"); 104 if (header == null) { 105 // Assume 0 content-length 106 return 0; 107 } 108 return Long.parseLong(header.getValue()); 109 } 110 111 /** 112 * Creates an input stream to read the file content from. Is only called if {@link #doGetType} returns 113 * {@link FileType#FILE}. 114 * <p> 115 * It is guaranteed that there are no open output streams for this file when this method is called. 116 * <p> 117 * The returned stream does not have to be buffered. 118 */ 119 @Override 120 protected InputStream doGetInputStream() throws Exception { 121 final GetMethod getMethod = new GetMethod(); 122 setupMethod(getMethod); 123 final int status = getAbstractFileSystem().getClient().executeMethod(getMethod); 124 if (status == HttpURLConnection.HTTP_NOT_FOUND) { 125 throw new FileNotFoundException(getName()); 126 } 127 if (status != HttpURLConnection.HTTP_OK) { 128 throw new FileSystemException("vfs.provider.http/get.error", getName(), Integer.valueOf(status)); 129 } 130 131 return new HttpInputStream(getMethod); 132 } 133 134 /** 135 * Returns the last modified time of this file. 136 * <p> 137 * This implementation throws an exception. 138 */ 139 @Override 140 protected long doGetLastModifiedTime() throws Exception { 141 final Header header = method.getResponseHeader("last-modified"); 142 if (header == null) { 143 throw new FileSystemException("vfs.provider.http/last-modified.error", getName()); 144 } 145 return DateUtil.parseDate(header.getValue()).getTime(); 146 } 147 148 @Override 149 protected RandomAccessContent doGetRandomAccessContent(final RandomAccessMode mode) throws Exception { 150 return new HttpRandomAccessContent(this, mode); 151 } 152 153 /** 154 * Determines the type of this file. Must not return null. The return value of this method is cached, so the 155 * implementation can be expensive. 156 */ 157 @Override 158 protected FileType doGetType() throws Exception { 159 // Use the HEAD method to probe the file. 160 final int status = this.getHeadMethod().getStatusCode(); 161 if (status == HttpURLConnection.HTTP_OK 162 || status == HttpURLConnection.HTTP_BAD_METHOD /* method is bad, but resource exist */) { 163 return FileType.FILE; 164 } else if (status == HttpURLConnection.HTTP_NOT_FOUND || status == HttpURLConnection.HTTP_GONE) { 165 return FileType.IMAGINARY; 166 } else { 167 throw new FileSystemException("vfs.provider.http/head.error", getName(), Integer.valueOf(status)); 168 } 169 } 170 171 @Override 172 protected boolean doIsWriteable() throws Exception { 173 return false; 174 } 175 176 /** 177 * Lists the children of this file. 178 */ 179 @Override 180 protected String[] doListChildren() throws Exception { 181 throw new Exception("Not implemented."); 182 } 183 184 protected String encodePath(final String decodedPath) throws URIException { 185 return URIUtil.encodePath(decodedPath); 186 } 187 188 @Override 189 protected FileContentInfoFactory getFileContentInfoFactory() { 190 return new HttpFileContentInfoFactory(); 191 } 192 193 protected boolean getFollowRedirect() { 194 return followRedirect; 195 } 196 197 protected String getUserAgent() { 198 return userAgent; 199 } 200 201 HeadMethod getHeadMethod() throws IOException { 202 if (method != null) { 203 return method; 204 } 205 method = new HeadMethod(); 206 setupMethod(method); 207 final HttpClient client = getAbstractFileSystem().getClient(); 208 client.executeMethod(method); 209 method.releaseConnection(); 210 return method; 211 } 212 213 protected String getUrlCharset() { 214 return urlCharset; 215 } 216 217 /** 218 * Prepares a HttpMethod object. 219 * 220 * @param method The object which gets prepared to access the file object. 221 * @throws FileSystemException if an error occurs. 222 * @throws URIException if path cannot be represented. 223 * @since 2.0 (was package) 224 */ 225 protected void setupMethod(final HttpMethod method) throws FileSystemException, URIException { 226 final String pathEncoded = ((URLFileName) getName()).getPathQueryEncoded(this.getUrlCharset()); 227 method.setPath(pathEncoded); 228 method.setFollowRedirects(this.getFollowRedirect()); 229 method.setRequestHeader("User-Agent", this.getUserAgent()); 230 } 231 232 /* 233 * protected Map doGetAttributes() throws Exception { TreeMap map = new TreeMap(); 234 * 235 * Header contentType = method.getResponseHeader("content-type"); if (contentType != null) { HeaderElement[] element 236 * = contentType.getValues(); if (element != null && element.length > 0) { map.put("content-type", 237 * element[0].getName()); } } 238 * 239 * map.put("content-encoding", method.getResponseCharSet()); return map; } 240 */ 241}