/* The contents of this file are subject to the license and copyright terms
* detailed in the license directory at the root of the source tree (also
* available online at http://fedora-commons.org/license/).
*/
package fedora.server.storage;
import java.io.File;
import java.net.URI;
import java.net.URL;
import java.util.Hashtable;
import java.util.Map;
import javax.activation.MimetypesFileTypeMap;
import org.apache.commons.httpclient.Header;
import org.apache.log4j.Logger;
import fedora.common.http.HttpInputStream;
import fedora.common.http.WebClient;
import fedora.server.Context;
import fedora.server.Module;
import fedora.server.ReadOnlyContext;
import fedora.server.Server;
import fedora.server.errors.GeneralException;
import fedora.server.errors.HttpServiceNotFoundException;
import fedora.server.errors.ModuleInitializationException;
import fedora.server.errors.authorization.AuthzException;
import fedora.server.security.Authorization;
import fedora.server.security.BackendPolicies;
import fedora.server.security.BackendSecurity;
import fedora.server.security.BackendSecuritySpec;
import fedora.server.storage.types.MIMETypedStream;
import fedora.server.storage.types.Property;
import fedora.server.utilities.ServerUtility;
/**
* Provides a mechanism to obtain external HTTP-accessible content.
*
* @author Ross Wayland
* @version $Id$
*
*/
public class DefaultExternalContentManager
extends Module
implements ExternalContentManager {
/** Logger for this class. */
private static final Logger LOG =
Logger.getLogger(DefaultExternalContentManager.class.getName());
private static final String DEFAULT_MIMETYPE="text/plain";
private String m_userAgent;
private String fedoraServerHost;
private String fedoraServerPort;
private String fedoraServerRedirectPort;
private WebClient m_http;
/**
* Creates a new DefaultExternalContentManager.
*
* @param moduleParameters
* The name/value pair map of module parameters.
* @param server
* The server instance.
* @param role
* The module role name.
* @throws ModuleInitializationException
* If initialization values are invalid or initialization fails for
* some other reason.
*/
public DefaultExternalContentManager(Map<String, String> moduleParameters,
Server server,
String role)
throws ModuleInitializationException {
super(moduleParameters, server, role);
}
/**
* Initializes the Module based on configuration parameters. The
* implementation of this method is dependent on the schema used to define
* the parameter names for the role of
* <code>fedora.server.storage.DefaultExternalContentManager</code>.
*
* @throws ModuleInitializationException
* If initialization values are invalid or initialization fails for
* some other reason.
*/
@Override
public void initModule() throws ModuleInitializationException {
try {
Server s_server = getServer();
m_userAgent = getParameter("userAgent");
if (m_userAgent == null) {
m_userAgent = "Fedora";
}
fedoraServerPort = s_server.getParameter("fedoraServerPort");
fedoraServerHost = s_server.getParameter("fedoraServerHost");
fedoraServerRedirectPort =
s_server.getParameter("fedoraRedirectPort");
m_http = new WebClient();
m_http.USER_AGENT = m_userAgent;
} catch (Throwable th) {
throw new ModuleInitializationException("[DefaultExternalContentManager] "
+ "An external content manager "
+ "could not be instantiated. The underlying error was a "
+ th.getClass()
.getName()
+ "The message was \""
+ th.getMessage()
+ "\".",
getRole());
}
}
/*
* Retrieves the external content.
* Currently the protocols <code>file</code> and
* <code>http[s]</code> are supported.
*
* @see
* fedora.server.storage.ExternalContentManager#getExternalContent(fedora
* .server.storage.ContentManagerParams)
*/
public MIMETypedStream getExternalContent(ContentManagerParams params)
throws GeneralException, HttpServiceNotFoundException{
LOG.debug("in getExternalContent(), url=" + params.getUrl());
try {
if(params.getProtocol().equals("file")){
return getFromFilesystem(params);
}
if (params.getProtocol().equals("http") || params.getProtocol().equals("https")){
return getFromWeb(params);
}
throw new GeneralException("protocol for retrieval of external content not supported. URL: " + params.getUrl());
} catch (Exception ex) {
// catch anything but generalexception
ex.printStackTrace();
throw new HttpServiceNotFoundException("[" + this.getClass().getSimpleName() + "] "
+ "returned an error. The underlying error was a "
+ ex.getClass().getName()
+ " The message "
+ "was \""
+ ex.getMessage() + "\" . ",ex);
}
}
/**
* Get a MIMETypedStream for the given URL. If user or password are
* <code>null</code>, basic authentication will not be attempted.
*/
private MIMETypedStream get(String url, String user, String pass, String knownMimeType)
throws GeneralException {
LOG.debug("DefaultExternalContentManager.get(" + url + ")");
try {
HttpInputStream response = m_http.get(url, true, user, pass);
String mimeType =
response.getResponseHeaderValue("Content-Type",
knownMimeType);
Property[] headerArray =
toPropertyArray(response.getResponseHeaders());
return new MIMETypedStream(mimeType, response, headerArray);
} catch (Exception e) {
throw new GeneralException("Error getting " + url, e);
}
}
/**
* Convert the given HTTP <code>Headers</code> to an array of
* <code>Property</code> objects.
*/
private static Property[] toPropertyArray(Header[] headers) {
Property[] props = new Property[headers.length];
for (int i = 0; i < headers.length; i++) {
props[i] = new Property();
props[i].name = headers[i].getName();
props[i].value = headers[i].getValue();
}
return props;
}
/**
* Creates a property array out of the MIME type and the length of the
* provided file.
*
* @param file
* the file containing the content.
* @return an array of properties containing content-length and
* content-type.
*/
private static Property[] getPropertyArray(File file, String mimeType) {
Property[] props = new Property[2];
Property clen = new Property("Content-Length",Long.toString(file.length()));
Property ctype = new Property("Content-Type", mimeType);
props[0] = clen;
props[1] = ctype;
return props;
}
/**
* Get a MIMETypedStream for the given URL. If user or password are
* <code>null</code>, basic authentication will not be attempted.
*
* @param params
* @return
* @throws HttpServiceNotFoundException
* @throws GeneralException
*/
private MIMETypedStream getFromFilesystem(ContentManagerParams params)
throws HttpServiceNotFoundException,GeneralException {
LOG.debug("in getFile(), url=" + params.getUrl());
try {
URL fileUrl = new URL(params.getUrl());
File cFile = new File(fileUrl.toURI()).getCanonicalFile();
// security check
URI cURI = cFile.toURI();
LOG.info("Checking resolution security on " + cURI);
Authorization authModule = (Authorization) getServer().getModule(
"fedora.server.security.Authorization");
if (authModule == null) {
throw new GeneralException(
"Missing required Authorization module");
}
authModule.enforceRetrieveFile(params.getContext(), cURI.toString());
// end security check
String mimeType = params.getMimeType();
// if mimeType was not given, try to determine it automatically
if (mimeType == null || mimeType.equalsIgnoreCase("")){
mimeType = determineMimeType(cFile);
}
return new MIMETypedStream(mimeType,fileUrl.openStream(),getPropertyArray(cFile,mimeType));
}
catch(AuthzException ae){
LOG.error(ae.getMessage(),ae);
throw new HttpServiceNotFoundException("Policy blocked datastream resolution",ae);
}
catch (GeneralException me) {
LOG.error(me.getMessage(),me);
throw me;
} catch (Throwable th) {
th.printStackTrace(System.err);
// catch anything but generalexception
LOG.error(th.getMessage(),th);
throw new HttpServiceNotFoundException("[FileExternalContentManager] "
+ "returned an error. The underlying error was a "
+ th.getClass().getName()
+ " The message "
+ "was \""
+ th.getMessage() + "\" . ",th);
}
}
/**
* Retrieves external content via http or https.
*
* @param url
* The url pointing to the content.
* @param context
* The Map containing parameters.
* @param mimeType
* The default MIME type to be used in case no MIME type can be
* detected.
* @return A MIMETypedStream
* @throws ModuleInitializationException
* @throws GeneralException
*/
private MIMETypedStream getFromWeb(ContentManagerParams params)
throws ModuleInitializationException, GeneralException {
String username = params.getUsername();
String password = params.getPassword();
boolean backendSSL = false;
String url = params.getUrl();
if (ServerUtility.isURLFedoraServer(url) && !params.isBypassBackend()) {
BackendSecuritySpec m_beSS;
BackendSecurity m_beSecurity =
(BackendSecurity) getServer()
.getModule("fedora.server.security.BackendSecurity");
try {
m_beSS = m_beSecurity.getBackendSecuritySpec();
} catch (Exception e) {
throw new ModuleInitializationException(
"Can't intitialize BackendSecurity module (in default access) from Server.getModule",
getRole());
}
Hashtable<String, String> beHash =
m_beSS.getSecuritySpec(BackendPolicies.FEDORA_INTERNAL_CALL);
username = (String) beHash.get("callUsername");
password = (String) beHash.get("callPassword");
backendSSL =
new Boolean((String) beHash.get("callSSL"))
.booleanValue();
if (backendSSL) {
if (params.getProtocol().equals("http:")) {
url = url.replaceFirst("http:", "https:");
}
url =
url.replaceFirst(":" + fedoraServerPort + "/",
":" + fedoraServerRedirectPort
+ "/");
}
if (LOG.isDebugEnabled()) {
LOG.debug("************************* backendUsername: "
+ username + " backendPassword: "
+ password + " backendSSL: " + backendSSL);
LOG.debug("************************* doAuthnGetURL: " + url);
}
}
return get(url, username, password, params.getMimeType());
}
/**
* Determines the mime type of a given file
*
* @param file for which the mime type needs to be detected
* @return the detected mime type
*/
private String determineMimeType(File file){
String mimeType = new MimetypesFileTypeMap().getContentType(file);
// if mimeType detection failed, fall back to the default
if (mimeType == null || mimeType.equalsIgnoreCase("")){
mimeType = DEFAULT_MIMETYPE;
}
return mimeType;
}
}