/**
* Copyright (C) 2010 Orbeon, Inc.
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU Lesser General Public License as published by the Free Software Foundation; either version
* 2.1 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* The full text of the license is available at http://www.gnu.org/copyleft/lesser.html
*/
package org.orbeon.oxf.processor;
import org.orbeon.oxf.common.OXFException;
import org.orbeon.oxf.http.StatusCode;
import org.orbeon.oxf.pipeline.api.PipelineContext;
import org.orbeon.oxf.resources.ResourceNotFoundException;
import org.orbeon.oxf.resources.URLFactory;
import org.orbeon.oxf.util.Mediatypes;
import org.orbeon.oxf.util.NetUtils;
import org.orbeon.oxf.util.URLRewriterUtils;
import org.orbeon.oxf.externalcontext.ExternalContext;
import org.orbeon.oxf.xml.XPathUtils;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.List;
/**
* Serve resources to the response.
*/
public class ResourceServer extends ProcessorImpl {
public static final String RESOURCE_SERVER_NAMESPACE_URI = "http://www.orbeon.com/oxf/resource-server";
public static final long ONE_YEAR_IN_MILLISECONDS = 365L * 24 * 60 * 60 * 1000;
public ResourceServer() {
addInputInfo(new ProcessorInputOutputInfo(INPUT_CONFIG, RESOURCE_SERVER_NAMESPACE_URI));
}
public void start(PipelineContext context) {
try {
// Read config input into a String, cache if possible
final org.orbeon.dom.Node configNode = readCacheInputAsDOM4J(context, INPUT_CONFIG);
// Get config URL first
String urlString = XPathUtils.selectStringValueNormalize(configNode, "url");
// For backward compatibility, try to get path element
if (urlString == null) {
urlString = XPathUtils.selectStringValueNormalize(configNode, "path");
// There must be a configuration
if (urlString == null)
throw new OXFException("Missing configuration.");
}
final List<URLRewriterUtils.PathMatcher> pathMatchers = URLRewriterUtils.getPathMatchers();
serveResource(urlString, URLRewriterUtils.isVersionedURL(urlString, pathMatchers));
} catch (Exception e) {
throw new OXFException(e);
}
}
public static void serveResource(String urlString, boolean isVersioned) throws IOException {
final ExternalContext externalContext = NetUtils.getExternalContext();
final ExternalContext.Response response = externalContext.getResponse();
// Remove version from the path if it is versioned
urlString = URLRewriterUtils.decodeResourceURI(urlString, isVersioned);
// Use the default protocol to read the file as a resource
if (!urlString.startsWith("oxf:"))
urlString = "oxf:" + urlString;
InputStream urlConnectionInputStream = null;
try {
// Open resource and set headers
try {
final URL newURL = URLFactory.createURL(urlString);
// Open the connection
final URLConnection urlConnection = newURL.openConnection();
urlConnectionInputStream = urlConnection.getInputStream();
// Get length and last modified
final int length = urlConnection.getContentLength();
final long lastModified = NetUtils.getLastModified(urlConnection);
// Set Last-Modified, required for caching and conditional get
if (isVersioned) {
// Use expiration far in the future
response.setResourceCaching(lastModified, System.currentTimeMillis() + ONE_YEAR_IN_MILLISECONDS);
} else {
// Use standard expiration policy
response.setResourceCaching(lastModified, 0);
}
// Check If-Modified-Since and don't return content if condition is met
if (!response.checkIfModifiedSince(externalContext.getRequest(), lastModified)) {
response.setStatus(StatusCode.NotModified());
return;
}
// Lookup and set the content type
final String contentType = Mediatypes.findMediatypeForPathJava(urlString);
if (contentType != null)
response.setContentType(contentType);
if (length > 0)
response.setContentLength(length);
} catch (IOException e) {
response.setStatus(StatusCode.NotFound());
return;
} catch (ResourceNotFoundException e) {
// Note: we should really not get this exception here, but an IOException
// However we do actually get it, and so do the same we do for IOException.
response.setStatus(StatusCode.NotFound());
return;
}
// Copy stream to output
NetUtils.copyStream(urlConnectionInputStream, response.getOutputStream());
} finally {
// Make sure the stream is closed in all cases so as to not lock the file on disk
if (urlConnectionInputStream != null) {
urlConnectionInputStream.close();
}
}
}
}