/* (c) 2014 Open Source Geospatial Foundation - all rights reserved
* (c) 2001 - 2013 OpenPlans
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.ows;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.net.URLConnection;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Enumeration;
import java.util.Locale;
import java.util.TimeZone;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.geoserver.ows.util.EncodingInfo;
import org.geoserver.ows.util.XmlCharsetDetector;
import org.geotools.data.DataUtilities;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.AbstractController;
/**
* Controller which publishes files through a web interface from the classpath
* <p>
* To use this controller, it should be mapped to a particular url in the url mapping of the spring
* dispatcher servlet. Example:
*
* <pre>
* <code>
* <bean id="filePublisher" class="org.geoserver.ows.FilePublisher"/>
* <bean id="dispatcherMappings"
* <property name="alwaysUseFullPath" value="true"/>
* class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
* <property name="mappings">
* <prop key="/schemas/** /*.xsd">filePublisher</prop>
* <prop key="/schemas/** /*.dtd">filePublisher</prop>
* <prop key="/styles/*">filePublisher</prop>
* </property>
* </bean>
* </code>
* </pre>
*
* @author Justin Deoliveira, The Open Planning Project
* @author Andrea Aime - GeoSolutions
*/
public abstract class AbstractURLPublisher extends AbstractController {
protected ModelAndView handleRequestInternal(HttpServletRequest request,
HttpServletResponse response) throws Exception {
URL url = getUrl(request);
// if not found return a 404
if (url == null) {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return null;
}
File file = DataUtilities.urlToFile(url);
if(file != null && file.exists() && file.isDirectory()) {
String uri = request.getRequestURI();
uri += uri.endsWith("/") ? "index.html" : "/index.html";
response.addHeader("Location", uri);
response.sendError(HttpServletResponse.SC_MOVED_TEMPORARILY);
return null;
}
if (file != null && checkNotModified(request, file.lastModified())) {
response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
return null;
}
// set the mime if known by the servlet container, set nothing otherwise
// (Tomcat behaves like this when it does not recognize the file format)
String mime = getServletContext().getMimeType(new File(url.getFile()).getName());
if (mime != null) {
response.setContentType(mime);
}
// set the content length and content type
URLConnection connection = null;
InputStream input = null;
try {
connection = url.openConnection();
long length = connection.getContentLength();
if (length > 0 && length <= Integer.MAX_VALUE) {
response.setContentLength((int) length);
}
long lastModified = connection.getLastModified();
if (lastModified > 0) {
response.setHeader("Last-Modified", lastModified(lastModified));
}
// Guessing the charset (and closing the stream)
EncodingInfo encInfo = null;
OutputStream output = null;
final byte[] b4 = new byte[4];
int count = 0;
// open the output
input = connection.getInputStream();
// Read the first four bytes, and determine charset encoding
count = input.read(b4);
encInfo = XmlCharsetDetector.getEncodingName(b4, count);
response.setCharacterEncoding(encInfo.getEncoding() != null ? encInfo.getEncoding()
: "UTF-8");
//count < 1 -> empty file
if (count > 0) {
// send out the first four bytes read
output = response.getOutputStream();
output.write(b4, 0, count);
// copy the content to the output
byte[] buffer = new byte[8192];
int n = -1;
while ((n = input.read(buffer)) != -1) {
output.write(buffer, 0, n);
}
}
} finally {
if (input != null)
input.close();
}
return null;
}
private boolean checkNotModified(HttpServletRequest request, long timeStamp) {
Enumeration headers = request.getHeaders("If-Modified-Since");
String header = headers != null && headers.hasMoreElements() ? headers.nextElement().toString() : null;
if (header != null && header.length() > 0) {
long ifModSinceSeconds = lastModified(header);
// the HTTP header has second precision
long timeStampSeconds = 1000 * (timeStamp / 1000);
return ifModSinceSeconds >= timeStampSeconds;
}
return false;
}
static String lastModified(long timeStamp) {
SimpleDateFormat format = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss",
Locale.ENGLISH);
format.setTimeZone(TimeZone.getTimeZone("GMT"));
return format.format(new Date(timeStamp)) + " GMT";
}
static long lastModified(String timeStamp) {
long ifModifiedSince = Long.MIN_VALUE;
try {
SimpleDateFormat fmt = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss",
Locale.ENGLISH);
fmt.setTimeZone(TimeZone.getTimeZone("GMT"));
ifModifiedSince = fmt.parse(timeStamp).getTime();
} catch (ParseException pe) {
// dang
}
// the HTTP header has second precision
return 1000 * (ifModifiedSince / 1000);
}
/**
* Retrieves the resource URL from the specified request
* @param request
*
* @throws IOException
*/
protected abstract URL getUrl(HttpServletRequest request) throws IOException;
}