/* 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.access.dissemination;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.sql.Timestamp;
import java.util.Date;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.log4j.Logger;
import fedora.common.Constants;
import fedora.server.Context;
import fedora.server.Server;
import fedora.server.errors.DisseminationBindingInfoNotFoundException;
import fedora.server.errors.DisseminationException;
import fedora.server.errors.GeneralException;
import fedora.server.errors.InitializationException;
import fedora.server.errors.ServerException;
import fedora.server.errors.ServerInitializationException;
import fedora.server.security.Authorization;
import fedora.server.security.BackendPolicies;
import fedora.server.security.BackendSecurity;
import fedora.server.security.BackendSecuritySpec;
import fedora.server.storage.ContentManagerParams;
import fedora.server.storage.ExternalContentManager;
import fedora.server.storage.ServiceDeploymentReader;
import fedora.server.storage.types.DatastreamMediation;
import fedora.server.storage.types.DeploymentDSBindRule;
import fedora.server.storage.types.DeploymentDSBindSpec;
import fedora.server.storage.types.DisseminationBindingInfo;
import fedora.server.storage.types.MIMETypedStream;
import fedora.server.storage.types.MethodParmDef;
import fedora.server.utilities.DateUtility;
import fedora.server.utilities.ServerUtility;
/**
* A service for executing a dissemination given its binding information.
*
* @author Ross Wayland
*/
public class DisseminationService {
/** Logger for this class. */
private static final Logger LOG =
Logger.getLogger(DisseminationService.class.getName());
/** The Fedora Server instance */
private static Server s_server;
/**
* Signifies the special type of address location known as LOCAL. An address
* location of LOCAL implies that no remote host name is required for the
* address location and that the contents of the operation location are
* sufficient to execute the associated mechanism.
*/
private static final String LOCAL_ADDRESS_LOCATION = "LOCAL";
/** The expiration limit in minutes for removing entries from the database. */
private static int datastreamExpirationLimit = 0;
/**
* An incremental counter used to insure uniqueness of tempIDs used for
* datastream mediation.
*/
private static int counter = 0;
/** Datastream Mediation control flag. */
private static boolean doDatastreamMediation;
/** Configured Fedora server host */
private static String fedoraServerHost = null;
/** Configured Fedora server port */
private static String fedoraServerPort = null;
/** Configured Fedora application server context */
private static String fedoraAppServerContext = null;
/** Configured Fedora redirect port */
private static String fedoraServerRedirectPort = null;
private static String fedoraHome = null;
private static BackendSecuritySpec m_beSS = null;
private static BackendSecurity m_beSecurity;
private static ExternalContentManager s_ecm;
/** Make sure we have a server instance for error logging purposes. */
static {
try {
fedoraHome = Constants.FEDORA_HOME;
if (fedoraHome == null) {
throw new ServerInitializationException("[DisseminationService] Server failed to initialize: "
+ "FEDORA_HOME is undefined");
} else {
s_server = Server.getInstance(new File(fedoraHome), false);
fedoraServerHost = s_server.getParameter("fedoraServerHost");
fedoraServerPort = s_server.getParameter("fedoraServerPort");
fedoraAppServerContext = s_server.getParameter("fedoraAppServerContext");
fedoraServerRedirectPort =
s_server.getParameter("fedoraRedirectPort");
m_beSecurity =
(BackendSecurity) s_server
.getModule("fedora.server.security.BackendSecurity");
m_beSS = m_beSecurity.getBackendSecuritySpec();
String expireLimit =
s_server.getParameter("datastreamExpirationLimit");
if (expireLimit == null || expireLimit.equalsIgnoreCase("")) {
LOG
.info("datastreamExpirationLimit unspecified; defaulting to "
+ "300 seconds");
datastreamExpirationLimit = 300;
} else {
datastreamExpirationLimit =
new Integer(expireLimit).intValue();
LOG.info("datastreamExpirationLimit="
+ datastreamExpirationLimit);
}
String dsMediation =
s_server.getModule("fedora.server.access.Access")
.getParameter("doMediateDatastreams");
if (dsMediation == null || dsMediation.equalsIgnoreCase("")) {
LOG
.info("doMediateDatastreams unspecified; defaulting to false");
} else {
doDatastreamMediation =
new Boolean(dsMediation).booleanValue();
}
}
s_ecm = (ExternalContentManager)
s_server.getModule("fedora.server.storage.ExternalContentManager");
} catch (InitializationException ie) {
LOG.error("Initialization error", ie);
}
}
/** The hashtable containing information required for datastream mediation. */
protected static Hashtable<String, DatastreamMediation> dsRegistry = new Hashtable<String, DatastreamMediation>(1000);
/**
* <p>
* Constructs an instance of DisseminationService. Initializes two class
* variables that contain the IP address and port number of the Fedora
* server. The port number is obtained from the Fedora server config file
* and the IP address of the server is obtained dynamically. These variables
* are needed to perform the datastream proxy service for datastream
* requests.
* </p>
*/
public DisseminationService() {
}
/*
* public void checkState(Context context, String state, String dsID, String
* PID) throws ServerException { // Check Object State if (
* state.equalsIgnoreCase("D") && ( context.get("canUseDeletedObject")==null ||
* (!context.get("canUseDeletedObject").equals("true")) ) ) { throw new
* GeneralException("The requested dissemination for data object \""+PID+"\"
* is no " + "longer available. One of its datastreams (dsID=\""+dsID+"\")
* has been flagged for DELETION " + "by the repository administrator. "); }
* else if ( state.equalsIgnoreCase("I") && (
* context.get("canUseInactiveObject")==null ||
* (!context.get("canUseInactiveObject").equals("true")) ) ) { throw new
* GeneralException("The requested dissemination for data object \""+PID+"\"
* is no " + "longer available. One of its datastreams (dsID=\""+dsID+"\")
* has been flagged as INACTIVE " + "by the repository administrator. "); } }
*/
/**
* <p>
* Assembles a dissemination given an instance of <code>
* DisseminationBindingInfo</code>
* which has the dissemination-related information from the digital object
* and its associated Service Deployment object.
* </p>
*
* @param context
* The current context.
* @param PID
* The persistent identifier of the digital object.
* @param h_userParms
* A hashtable of user-supplied method parameters.
* @param dissBindInfoArray
* The associated dissemination binding information.
* @return A MIME-typed stream containing the result of the dissemination.
* @throws ServerException
* If unable to assemble the dissemination for any reason.
*/
public MIMETypedStream assembleDissemination(Context context,
String PID,
Hashtable<String, String> h_userParms,
DisseminationBindingInfo[] dissBindInfoArray,
String deploymentPID,
ServiceDeploymentReader bmReader,
String methodName)
throws ServerException {
LOG.debug("Started assembling dissemination");
String dissURL = null;
String protocolType = null;
DisseminationBindingInfo dissBindInfo = null;
MIMETypedStream dissemination = null;
boolean isRedirect = false;
if (LOG.isDebugEnabled()) {
printBindingInfo(dissBindInfoArray);
}
if (dissBindInfoArray != null && dissBindInfoArray.length > 0) {
String replaceString = null;
int numElements = dissBindInfoArray.length;
// Get row(s) of binding info and perform string substitution
// on DSBindingKey and method parameter values in WSDL
// Note: In case where more than one datastream matches the
// DSBindingKey or there are multiple DSBindingKeys for the
// method, multiple rows will be present; otherwise there is only
// a single row.
for (int i = 0; i < dissBindInfoArray.length; i++) {
((Authorization) s_server
.getModule("fedora.server.security.Authorization"))
.enforce_Internal_DSState(context,
dissBindInfoArray[i].dsID,
dissBindInfoArray[i].dsState);
dissBindInfo = dissBindInfoArray[i];
// Before doing anything, check whether we can replace any
// placeholders in the datastream url with parameter values from
// the request. This supports the special case where a
// datastream's URL is dependent on user parameters, such
// as when the datastream is actually a dissemination that
// takes parameters.
if (dissBindInfo.dsLocation != null
&& (dissBindInfo.dsLocation.startsWith("http://") || dissBindInfo.dsLocation
.startsWith("https://"))) {
String[] parts = dissBindInfo.dsLocation.split("=\\("); // regex for =(
if (parts.length > 1) {
StringBuffer replaced = new StringBuffer();
replaced.append(parts[0]);
for (int x = 1; x < parts.length; x++) {
replaced.append('=');
int rightParenPos = parts[x].indexOf(")");
if (rightParenPos != -1 && rightParenPos > 0) {
String key =
parts[x].substring(0, rightParenPos);
String val = h_userParms.get(key);
if (val != null) {
// We have a match... so insert the urlencoded value.
try {
replaced.append(URLEncoder
.encode(val, "UTF-8"));
} catch (UnsupportedEncodingException uee) {
// won't happen: java always supports UTF-8
}
if (rightParenPos < parts[x].length()) {
replaced.append(parts[x]
.substring(rightParenPos + 1));
}
} else {
replaced.append('(');
replaced.append(parts[x]);
}
} else {
replaced.append('(');
replaced.append(parts[x]);
}
}
dissBindInfo.dsLocation = replaced.toString();
}
}
// Match DSBindingKey pattern in WSDL which is a string of the form:
// (DSBindingKey). Rows in DisseminationBindingInfo are sorted
// alphabetically on binding key.
String bindingKeyPattern =
"\\(" + dissBindInfo.DSBindKey + "\\)";
if (i == 0) {
// If addressLocation has a value of "LOCAL", this indicates
// the associated operationLocation requires no addressLocation.
// i.e., the operationLocation contains all information necessary
// to perform the dissemination request. This is a special case
// used when the web services are generally mechanisms like cgi-scripts,
// java servlets, and simple HTTP GETs. Using the value of LOCAL
// in the address location also enables one to have different methods
// serviced by different hosts. In true web services like SOAP, the
// addressLocation specifies the host name of the service and all
// methods are served from that single host location.
if (dissBindInfo.AddressLocation
.equalsIgnoreCase(LOCAL_ADDRESS_LOCATION)) {
dissURL = dissBindInfo.OperationLocation;
} else {
dissURL =
dissBindInfo.AddressLocation
+ dissBindInfo.OperationLocation;
/*
* Substitute real app server context if we detect '/fedora'.
* This is necessary here because DOTranslator does not scrub
* URLs that result from concatenating fragments from different
* locations in the file
*/
dissURL = dissURL.replaceAll(
fedoraServerHost + ":" + fedoraServerPort + "/fedora/",
fedoraServerHost + ":" + fedoraServerPort + "/" + fedoraAppServerContext + "/");
}
protocolType = dissBindInfo.ProtocolType;
}
// Assess beSecurity for backend service and for datastreams that may be parameters for the
// backend service.
//
// dsMediatedCallbackHost - when dsMediation is in effect, all M, X, and E type datastreams
// are encoded as callbacks to the Fedora server to obtain the
// datastream's contents. dsMediatedCallbackHost contains protocol,
// host, and port used for this type of backendservice-to-fedora callback.
// The specifics of protocol, host, and port are obtained from the
// beSecurity configuration file.
// dsMediatedServletPath - when dsMediation is in effect, all M, X, and E type datastreams
// are encoded as callbacks to the Fedora server to obtain the
// datastream's contents. dsMediatedServletPath contains the servlet
// path info for this type of backendservice-to-fedora callback.
// The specifics of servlet path are obtained from the beSecurity configuration
// file and determines whether the backedservice-to-fedora callback
// will use authentication or not.
// callbackRole - contains the role of the backend service (the deploymentPID of the service).
String callbackRole = deploymentPID;
Hashtable<String, String> beHash =
m_beSS.getSecuritySpec(callbackRole, methodName);
boolean callbackBasicAuth =
new Boolean(beHash.get("callbackBasicAuth"))
.booleanValue();
boolean callbackSSL =
new Boolean(beHash.get("callbackSSL"))
.booleanValue();
String dsMediatedServletPath = null;
if (callbackBasicAuth) {
dsMediatedServletPath = "/" + fedoraAppServerContext + "/getDSAuthenticated?id=";
} else {
dsMediatedServletPath = "/" + fedoraAppServerContext + "/getDS?id=";
}
String dsMediatedCallbackHost = null;
if (callbackSSL) {
dsMediatedCallbackHost =
"https://" + fedoraServerHost + ":"
+ fedoraServerRedirectPort;
} else {
dsMediatedCallbackHost =
"http://" + fedoraServerHost + ":"
+ fedoraServerPort;
}
String datastreamResolverServletURL =
dsMediatedCallbackHost + dsMediatedServletPath;
if (LOG.isDebugEnabled()) {
LOG
.debug("******************Checking backend service dsLocation: "
+ dissBindInfo.dsLocation);
LOG
.debug("******************Checking backend service dsControlGroupType: "
+ dissBindInfo.dsControlGroupType);
LOG
.debug("******************Checking backend service callbackBasicAuth: "
+ callbackBasicAuth);
LOG
.debug("******************Checking backend service callbackSSL: "
+ callbackSSL);
LOG
.debug("******************Checking backend service callbackRole: "
+ callbackRole);
LOG
.debug("******************DatastreamResolverServletURL: "
+ datastreamResolverServletURL);
}
String currentKey = dissBindInfo.DSBindKey;
String nextKey = "";
if (i != numElements - 1) {
// Except for last row, get the value of the next binding key
// to compare with the value of the current binding key.
nextKey = dissBindInfoArray[i + 1].DSBindKey;
}
LOG.debug("currentKey: '" + currentKey + "', nextKey: '"
+ nextKey + "'");
// In most cases, there is only a single datastream that matches a
// given DSBindingKey so the substitution process is to just replace
// the occurrence of (BINDING_KEY) with the value of the datastream
// location. However, when multiple datastreams match the same
// DSBindingKey, the occurrence of (BINDING_KEY) is replaced with the
// value of the datastream location and the value +(BINDING_KEY) is
// appended so that subsequent datastreams matching the binding key
// will be substituted. The end result is that the binding key will
// be replaced by a series of datastream locations separated by a
// plus(+) sign. For example, in the case where 3 datastreams match
// the binding key for PHOTO:
//
// file=(PHOTO) becomes
// file=dslocation1+dslocation2+dslocation3
//
// It is the responsibility of the Service Deployment to know how to
// handle an input parameter with multiple datastream locations.
//
// In the case of a method containing multiple binding keys,
// substitutions are performed on each binding key. For example, in
// the case where there are 2 binding keys named PHOTO and WATERMARK
// where each matches a single datastream:
//
// image=(PHOTO)&watermark=(WATERMARK) becomes
// image=dslocation1&watermark=dslocation2
//
// In the case with multiple binding keys and multiple datastreams,
// the substitution might appear like the following:
//
// image=(PHOTO)&watermark=(WATERMARK) becomes
// image=dslocation1+dslocation2&watermark=dslocation3
if (nextKey.equalsIgnoreCase(currentKey) & i != numElements) {
// Case where binding keys are equal which means that multiple
// datastreams matched the same binding key.
if (doDatastreamMediation
&& !dissBindInfo.dsControlGroupType
.equalsIgnoreCase("R")) {
// Use Datastream Mediation (except for redirected datastreams).
replaceString =
datastreamResolverServletURL
+ registerDatastreamLocation(dissBindInfo.dsLocation,
dissBindInfo.dsControlGroupType,
callbackRole,
methodName)
+ "+(" + dissBindInfo.DSBindKey + ")";
} else {
// Bypass Datastream Mediation.
if (dissBindInfo.dsControlGroupType
.equalsIgnoreCase("M")
|| dissBindInfo.dsControlGroupType
.equalsIgnoreCase("X")) {
// Use the Default Disseminator syntax to resolve the internal
// datastream location for Managed and XML datastreams.
replaceString =
resolveInternalDSLocation(context,
dissBindInfo.dsLocation,
dissBindInfo.dsCreateDT,
dsMediatedCallbackHost)
+ "+("
+ dissBindInfo.DSBindKey
+ ")";;
} else {
replaceString =
dissBindInfo.dsLocation + "+("
+ dissBindInfo.DSBindKey + ")";
}
if (dissBindInfo.dsControlGroupType
.equalsIgnoreCase("R")
&& dissBindInfo.AddressLocation
.equals(LOCAL_ADDRESS_LOCATION)) {
isRedirect = true;
}
}
} else {
// Case where there are one or more binding keys.
if (doDatastreamMediation
&& !dissBindInfo.dsControlGroupType
.equalsIgnoreCase("R")) {
// Use Datastream Mediation (except for Redirected datastreams)
replaceString =
datastreamResolverServletURL
+ registerDatastreamLocation(dissBindInfo.dsLocation,
dissBindInfo.dsControlGroupType,
callbackRole,
methodName); //this is generic, should be made specific per service
} else {
// Bypass Datastream Mediation.
if (dissBindInfo.dsControlGroupType
.equalsIgnoreCase("M")
|| dissBindInfo.dsControlGroupType
.equalsIgnoreCase("X")) {
// Use the Default Disseminator syntax to resolve the internal
// datastream location for Managed and XML datastreams.
replaceString =
resolveInternalDSLocation(context,
dissBindInfo.dsLocation,
dissBindInfo.dsCreateDT,
dsMediatedCallbackHost);
} else {
replaceString = dissBindInfo.dsLocation;
}
if (dissBindInfo.dsControlGroupType
.equalsIgnoreCase("R")
&& dissBindInfo.AddressLocation
.equals(LOCAL_ADDRESS_LOCATION)) {
isRedirect = true;
}
}
}
try {
// If the operationLocation contains datastreamInputParms
// URLEncode each parameter before substitution. Otherwise, the
// operationLocation has no parameters (i.e., it is a simple URL )
// so bypass URLencoding.
if (dissURL.indexOf("=(") != -1) {
dissURL =
substituteString(dissURL,
bindingKeyPattern,
URLEncoder
.encode(replaceString,
"UTF-8"));
} else {
dissURL =
substituteString(dissURL,
bindingKeyPattern,
replaceString);
}
} catch (UnsupportedEncodingException uee) {
String message =
"[DisseminationService] An error occured. The error "
+ "was \"" + uee.getClass().getName()
+ "\" . The Reason was \""
+ uee.getMessage() + "\" . String value: "
+ replaceString + " . ";
LOG.error(message);
throw new GeneralException(message);
}
LOG.debug("Replaced dissURL: " + dissURL.toString()
+ " DissBindingInfo index: " + i);
}
DeploymentDSBindSpec dsBindSpec = bmReader.getServiceDSInputSpec(null);
DeploymentDSBindRule rules[] = dsBindSpec.dsBindRules;
for (DeploymentDSBindRule element : rules) {
String rulePattern = "(" + element.bindingKeyName + ")";
if (dissURL.indexOf(rulePattern) != -1) {
throw new DisseminationException(null, "Data Object " + PID
+ " missing required datastream: "
+ element.bindingKeyName, null, null, null);
}
}
// Substitute method parameter values in dissemination URL
Enumeration<String> e = h_userParms.keys();
while (e.hasMoreElements()) {
String name = null;
String value = null;
try {
name = URLEncoder.encode(e.nextElement(), "UTF-8");
value =
URLEncoder.encode(h_userParms.get(name),
"UTF-8");
} catch (UnsupportedEncodingException uee) {
String message =
"[DisseminationService] An error occured. The error "
+ "was \"" + uee.getClass().getName()
+ "\" . The Reason was \""
+ uee.getMessage()
+ "\" . Parameter name: " + name + " . "
+ "Parameter value: " + value + " .";
LOG.error(message);
throw new GeneralException(message);
}
String pattern = "\\(" + name + "\\)";
dissURL = substituteString(dissURL, pattern, value);
LOG.debug("User parm substituted in URL: " + dissURL);
}
// FIXME Need a more elegant means of handling optional userInputParm
// method parameters that are not supplied by the invoking client;
// for now, any optional parms that were not supplied are removed from
// the outgoing URL. This works because parms are validated in
// DefaultAccess to insure all required parms are present and all parm
// names match parm names defined for the specific method. The only
// unsubstituted parms left in the operationLocation string at this point
// are those for optional parameters that the client omitted in the
// initial request so they can safely be removed from the outgoing
// dissemination URL. This step is only needed when optional parameters
// are not supplied by the client.
if (dissURL.indexOf("(") != -1) {
dissURL = stripParms(dissURL);
LOG.debug("Non-supplied optional userInputParm values removed "
+ "from URL: " + dissURL);
}
if (dissURL.indexOf("(") != -1) {
String datastreamName =
dissURL.substring(dissURL.indexOf("(") + 1, dissURL
.indexOf(")"));
throw new DisseminationException(null,
"Data Object "
+ PID
+ " missing required datastream: "
+ datastreamName,
null,
null,
null);
}
// Resolve content referenced by dissemination result.
LOG.debug("ProtocolType: " + protocolType);
if (protocolType.equalsIgnoreCase("http")) {
if (isRedirect) {
// The dsControlGroupType of Redirect("R") is a special control type
// used primarily for streaming media. Datastreams of this type are
// not mediated (proxied by Fedora) and their physical dsLocation is
// simply redirected back to the client. Therefore, the contents
// of the MIMETypedStream returned for dissemination requests will
// contain the raw URL of the dsLocation and will be assigned a
// special fedora-specific MIME type to identify the stream as
// a MIMETypedStream whose contents contain a URL to which the client
// should be redirected.
InputStream is = null;
try {
is =
new ByteArrayInputStream(dissURL
.getBytes("UTF-8"));
} catch (UnsupportedEncodingException uee) {
String message =
"[DisseminationService] An error has occurred. "
+ "The error was a \""
+ uee.getClass().getName()
+ "\" . The " + "Reason was \""
+ uee.getMessage()
+ "\" . String value: " + dissURL
+ " . ";
LOG.error(message);
throw new GeneralException(message);
}
LOG.debug("Finished assembling dissemination");
dissemination =
new MIMETypedStream("application/fedora-redirect",
is,
null);
} else {
// For all non-redirected disseminations, Fedora captures and returns
// the MIMETypedStream resulting from the dissemination request.
//ExternalContentManager externalContentManager = (ExternalContentManager)
// s_server.getModule("fedora.server.storage.ExternalContentManager");
LOG.debug("Finished assembling dissemination");
LOG.debug("URL: " + dissURL);
// See if backend service reference is to fedora server itself or an external location.
// We must examine URL to see if this is referencing a remote backend service or is
// simply a callback to the fedora server. If the reference is remote, then use
// the role of backend service deployment PID. If the referenc is to the fedora server,
// use the special role of "fedoraInternalCall-1" to denote that the callback will come from the
// fedora server itself.
String beServiceRole = null;
if (ServerUtility.isURLFedoraServer(dissURL)) {
beServiceRole = BackendPolicies.FEDORA_INTERNAL_CALL;
} else {
beServiceRole = deploymentPID;
}
// Get basicAuth and SSL info about the backend service and use this info to configure the
// "call" to the backend service.
Hashtable<String, String> beHash =
m_beSS.getSecuritySpec(beServiceRole, methodName);
boolean beServiceCallSSL =
new Boolean(beHash.get("callSSL"))
.booleanValue();
String beServiceCallUsername = "";
String beServiceCallPassword = "";
boolean beServiceCallBasicAuth =
new Boolean(beHash.get("callBasicAuth"))
.booleanValue();
if (beServiceCallBasicAuth) {
beServiceCallUsername = beHash.get("callUsername");
beServiceCallPassword = beHash.get("callPassword");
}
/*
* //fixup: if
* (BackendPolicies.FEDORA_INTERNAL_CALL.equals(beServiceRole)) {
* if (beServiceCallSSL) { if (dissURL.startsWith("http:")) {
* dissURL = dissURL.replaceFirst("http:", "https:"); } if
* (dissURL.indexOf(":"+fedoraServerPort+"/") >= 0) {
* dissURL = dissURL.replaceFirst(":"+fedoraServerPort+"/",
* ":"+fedoraServerRedirectPort+"/"); } } else { if
* (dissURL.startsWith("https:")) { dissURL =
* dissURL.replaceFirst("https:", "http:"); } if
* (dissURL.indexOf(":"+fedoraServerRedirectPort+"/") >= 0) {
* dissURL =
* dissURL.replaceFirst(":"+fedoraServerRedirectPort+"/",
* ":"+fedoraServerPort+"/"); } } if
* (beServiceCallBasicAuth) { if (dissURL.indexOf("getDS?") >=
* 0) { dissURL = dissURL.replaceFirst("getDS\\?",
* "getDSAuthenticated\\?"); } } else { if
* (dissURL.indexOf("getDSAuthenticated?") >= 0) { dissURL =
* dissURL.replaceFirst("getDSAuthenticated\\?",
* "getDS\\?"); } } }
*/
if (LOG.isDebugEnabled()) {
LOG
.debug("******************getDisseminationContent beServiceRole: "
+ beServiceRole);
LOG
.debug("******************getDisseminationContent beServiceCallBasicAuth: "
+ beServiceCallBasicAuth);
LOG
.debug("******************getDisseminationContent beServiceCallSSL: "
+ beServiceCallSSL);
LOG
.debug("******************getDisseminationContent beServiceCallUsername: "
+ beServiceCallUsername);
LOG
.debug("******************getDisseminationContent beServiceCallPassword: "
+ beServiceCallPassword);
LOG
.debug("******************getDisseminationContent dissURL: "
+ dissURL);
}
// Dispatch backend service URL request authenticating as necessary based on beSecurity configuration
ContentManagerParams params = new ContentManagerParams(
dissURL, null, beServiceCallUsername,
beServiceCallPassword);
params.setBypassBackend(true);
params.setContext(context);
dissemination = s_ecm.getExternalContent(params);
}
} else if (protocolType.equalsIgnoreCase("soap")) {
// FIXME!! future handling of soap bindings.
String message =
"[DisseminationService] Protocol type: " + protocolType
+ "NOT yet implemented";
LOG.error(message);
throw new DisseminationException(message);
} else if (protocolType.equalsIgnoreCase("file")) {
ContentManagerParams params = new ContentManagerParams(dissURL);
params.setContext(context);
dissemination = s_ecm.getExternalContent(params);
}
else {
String message =
"[DisseminationService] Protocol type: " + protocolType
+ "NOT supported.";
LOG.error(message);
throw new DisseminationException(message);
}
} else {
// DisseminationBindingInfo was empty so there was no information
// provided to construct a dissemination.
String message =
"[DisseminationService] Dissemination Binding "
+ "Info contained no data";
LOG.error(message);
throw new DisseminationBindingInfoNotFoundException(message);
}
return dissemination;
}
/**
* <p>
* Datastream locations are considered privileged information by the Fedora
* repository. To prevent disclosing physical datastream locations to
* external mechanism services, a proxy is used to disguise the datastream
* locations. This method generates a temporary ID that maps to the physical
* datastream location and registers this information in a memory resident
* hashtable for subsequent resolution of the physical datastream location.
* The servlet <code>DatastreamResolverServlet</code> provides the proxy
* resolution service for datastreams.
* </p>
* <p>
* </p>
* <p>
* The format of the tempID is derived from <code>java.sql.Timestamp</code>
* with an arbitrary counter appended to the end to insure uniqueness. The
* syntax is of the form:
* <ul>
* <p>
* YYYY-MM-DD HH:mm:ss.mmm:dddddd where
* </p>
* <ul>
* <li>YYYY - year (1900-8099)</li>
* <li>MM - month (01-12)</li>
* <li>DD - day (01-31)</li>
* <li>hh - hours (0-23)</li>
* <li>mm - minutes (0-59)</li>
* <li>ss - seconds (0-59)</li>
* <li>mmm - milliseconds (0-999)</li>
* <li>dddddd - incremental counter (0-999999)</li>
* </ul>
* </ul>
*
* @param dsLocation
* The physical location of the datastream.
* @param dsControlGroupType
* The type of the datastream.
* @return A temporary ID used to reference the physical location of the
* specified datastream
* @throws ServerException
* If an error occurs in registering a datastream location.
*/
public String registerDatastreamLocation(String dsLocation,
String dsControlGroupType,
String beServiceCallbackRole,
String methodName)
throws ServerException {
String tempID = null;
Timestamp timeStamp = null;
if (counter > 999999) {
counter = 0;
}
long currentTime = new Timestamp(new Date().getTime()).getTime();
long expireLimit =
currentTime - (long) datastreamExpirationLimit * 1000;
try {
// Remove any datastream registrations that have expired.
// The expiration limit can be adjusted using the Fedora config parameter
// named "datastreamExpirationLimit" which is in seconds.
for (Enumeration<String> e = dsRegistry.keys(); e.hasMoreElements();) {
String key = e.nextElement();
timeStamp = Timestamp.valueOf(extractTimestamp(key));
if (expireLimit > timeStamp.getTime()) {
dsRegistry.remove(key);
LOG.debug("DatastreamMediationKey removed from Hash: "
+ key);
}
}
// Register datastream.
if (tempID == null) {
timeStamp = new Timestamp(new Date().getTime());
tempID = timeStamp.toString() + ":" + counter++;
DatastreamMediation dm = new DatastreamMediation();
dm.mediatedDatastreamID = tempID;
dm.dsLocation = dsLocation;
dm.dsControlGroupType = dsControlGroupType;
dm.methodName = methodName;
// See if datastream reference is to fedora server itself or an external location.
// M and X type datastreams always reference fedora server. With E type datastreams
// we must examine URL to see if this is referencing a remote datastream or is
// simply a callback to the fedora server. If the reference is remote, then use
// the role of the backend service that will make a callback for this datastream.
// If the referenc s to the fedora server, use the special role of "fedoraInternalCall-1" to
// denote that the callback will come from the fedora server itself.
String beServiceRole = null;
if (ServerUtility.isURLFedoraServer(dsLocation)
|| dsControlGroupType.equals("M")
|| dsControlGroupType.equals("X")) {
beServiceRole = BackendPolicies.FEDORA_INTERNAL_CALL;
} else {
beServiceRole = beServiceCallbackRole;
}
// Store beSecurity info in hash
Hashtable<String, String> beHash =
m_beSS.getSecuritySpec(beServiceRole, methodName);
boolean beServiceCallbackBasicAuth =
new Boolean(beHash.get("callbackBasicAuth"))
.booleanValue();
boolean beServiceCallBasicAuth =
new Boolean(beHash.get("callBasicAuth"))
.booleanValue();
boolean beServiceCallbackSSL =
new Boolean(beHash.get("callbackSSL"))
.booleanValue();
boolean beServiceCallSSL =
new Boolean(beHash.get("callSSL"))
.booleanValue();
String beServiceCallUsername =
beHash.get("callUsername");
String beServiceCallPassword =
beHash.get("callPassword");
if (LOG.isDebugEnabled()) {
LOG
.debug("******************Registering datastream dsLocation: "
+ dsLocation);
LOG
.debug("******************Registering datastream dsControlGroupType: "
+ dsControlGroupType);
LOG
.debug("******************Registering datastream beServiceRole: "
+ beServiceRole);
LOG
.debug("******************Registering datastream beServiceCallbackBasicAuth: "
+ beServiceCallbackBasicAuth);
LOG
.debug("******************Registering datastream beServiceCallBasicAuth: "
+ beServiceCallBasicAuth);
LOG
.debug("******************Registering datastream beServiceCallbackSSL: "
+ beServiceCallbackSSL);
LOG
.debug("******************Registering datastream beServiceCallSSL: "
+ beServiceCallSSL);
LOG
.debug("******************Registering datastream beServiceCallUsername: "
+ beServiceCallUsername);
LOG
.debug("******************Registering datastream beServiceCallPassword: "
+ beServiceCallPassword);
}
dm.callbackRole = beServiceRole;
dm.callUsername = beServiceCallUsername;
dm.callPassword = beServiceCallPassword;
dm.callbackBasicAuth = beServiceCallbackBasicAuth;
dm.callBasicAuth = beServiceCallBasicAuth;
dm.callbackSSL = beServiceCallbackSSL;
dm.callSSL = beServiceCallSSL;
dsRegistry.put(tempID, dm);
LOG.debug("DatastreammediationKey added to Hash: " + tempID);
}
} catch (Throwable th) {
throw new DisseminationException("[DisseminationService] register"
+ "DatastreamLocation: "
+ "returned an error. The underlying error was a "
+ th.getClass().getName() + " The message " + "was \""
+ th.getMessage() + "\" .");
}
// Replace the blank between date and time with the character "T".
return tempID.replaceAll(" ", "T");
}
/**
* <p>
* The tempID that is used for datastream mediation consists of a <code>
* Timestamp</code>
* plus a counter appended to the end to insure uniqueness. This method is a
* utility method used to extract the Timestamp portion from the tempID by
* stripping off the arbitrary counter at the end of the string.
* </p>
*
* @param tempID
* The tempID to be extracted.
* @return The extracted Timestamp value as a string.
*/
public String extractTimestamp(String tempID) {
StringBuffer sb = new StringBuffer();
sb.append(tempID);
sb.replace(tempID.lastIndexOf(":"), tempID.length(), "");
return sb.toString();
}
/**
* <p>
* Performs simple string replacement using regular expressions. All
* matching occurrences of the pattern string will be replaced in the input
* string by the replacement string.
*
* @param inputString
* The source string.
* @param patternString
* The regular expression pattern.
* @param replaceString
* The replacement string.
* @return The source string with substitutions.
*/
private String substituteString(String inputString,
String patternString,
String replaceString) {
Pattern pattern = Pattern.compile(patternString);
Matcher m = pattern.matcher(inputString);
return m.replaceAll(replaceString);
}
/**
* <p>
* Removes any optional userInputParms which remain in the dissemination
* URL. This occurs when a method has optional parameters and the user does
* not supply a value for one or more of the optional parameters. The result
* is a syntax similar to "parm=(PARM_BIND_KEY)". This method removes these
* non-supplied optional parameters from the string.
* </p>
*
* @param dissURL
* String to be processed.
* @return An edited string with parameters removed where no value was
* specified for any optional parameters.
*/
private String stripParms(String dissURL) {
// if no parameters, simply return passed in string.
if (dissURL.indexOf("?") == -1) {
return dissURL;
}
String requestURI = dissURL.substring(0, dissURL.indexOf("?") + 1);
String parmString =
dissURL.substring(dissURL.indexOf("?") + 1, dissURL.length());
String[] parms = parmString.split("&");
StringBuffer sb = new StringBuffer();
for (String element : parms) {
int len = element.length() - 1;
if (element.lastIndexOf(")") != len) {
sb.append(element + "&");
}
}
int index = sb.lastIndexOf("&");
if (index != -1 && index + 1 == sb.length()) {
sb.replace(index, sb.length(), "");
}
return requestURI + sb.toString();
}
/**
* <p>
* Converts the internal dsLocation used by managed and XML type datastreams
* to the corresponding Default Dissemination request that will return the
* datastream contents.
* </p>
*
* @param internalDSLocation -
* dsLocation of the Managed or XML type datastream.
* @param PID -
* the persistent identifier of the digital object.
* @return - A URL corresponding to the Default Dissemination request for
* the specified datastream.
* @throws ServerException -
* If anything goes wrong during the conversion attempt.
*/
private String resolveInternalDSLocation(Context context,
String internalDSLocation,
Date dsCreateDT,
String callbackHost)
throws ServerException {
if (callbackHost == null || callbackHost.equals("")) {
throw new DisseminationException("[DisseminationService] was unable to "
+ "resolve the base URL of the Fedora Server. The URL specified was: \""
+ callbackHost
+ "\". This information is required by the Dissemination Service.");
}
String[] s = internalDSLocation.split("\\+");
String dsLocation = null;
if (s.length == 3) {
dsLocation =
callbackHost + "/" + fedoraAppServerContext + "/get/" + s[0] + "/" + s[1] + "/"
+ DateUtility.convertDateToString(dsCreateDT);
} else {
String message =
"[DisseminationService] An error has occurred. "
+ "The internal dsLocation: \""
+ internalDSLocation + "\" is "
+ "not in the required format of: "
+ "\"doPID+DSID+DSVERSIONID\" .";
LOG.error(message);
throw new GeneralException(message);
}
LOG.debug("********** Resolving Internal Datastream dsLocation: "
+ dsLocation);
return dsLocation;
}
public static void printBindingInfo(DisseminationBindingInfo[] info) {
for (int i = 0; i < info.length; i++) {
LOG.debug("DisseminationBindingInfo[" + i + "]:");
LOG.debug(" DSBindKey : " + info[i].DSBindKey);
LOG.debug(" dsLocation : " + info[i].dsLocation);
LOG.debug(" dsControlGroupType : " + info[i].dsControlGroupType);
LOG.debug(" dsID : " + info[i].dsID);
LOG.debug(" dsVersionID : " + info[i].dsVersionID);
LOG.debug(" AddressLocation : " + info[i].AddressLocation);
LOG.debug(" OperationLocation : " + info[i].OperationLocation);
LOG.debug(" ProtocolType : " + info[i].ProtocolType);
LOG.debug(" dsState : " + info[i].dsState);
LOG.debug(" dsCreateDT : " + info[i].dsCreateDT);
for (int j = 0; j < info[i].methodParms.length; j++) {
MethodParmDef def = info[i].methodParms[j];
LOG.debug(" MethodParamDef[" + j + "]:");
LOG.debug(" parmName : " + def.parmName);
LOG.debug(" parmDefaultValue : " + def.parmDefaultValue);
LOG.debug(" parmRequired : " + def.parmRequired);
LOG.debug(" parmLabel : " + def.parmLabel);
LOG.debug(" parmPassBy : " + def.parmPassBy);
for (String element : def.parmDomainValues) {
LOG.debug(" parmDomainValue : " + element);
}
}
}
}
}