logger.debug("Started assembling dissemination");
String dissURL = null;
String protocolType = null;
DisseminationBindingInfo dissBindInfo = null;
MIMETypedStream dissemination = null;
boolean isRedirect = false;
if (logger.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++) {
m_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(m_fedoraServerHost + ":"
+ m_fedoraServerPort + "/fedora/",
m_fedoraServerHost + ":" + m_fedoraServerPort
+ "/" + m_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 = "/" + m_fedoraAppServerContext
+ "/getDSAuthenticated?id=";
} else {
dsMediatedServletPath = "/" + m_fedoraAppServerContext
+ "/getDS?id=";
}
String dsMediatedCallbackHost = null;
if (callbackSSL) {
dsMediatedCallbackHost = "https://" + m_fedoraServerHost
+ ":" + m_fedoraServerRedirectPort;
} else {
dsMediatedCallbackHost = "http://" + m_fedoraServerHost
+ ":" + m_fedoraServerPort;
}
String datastreamResolverServletURL = dsMediatedCallbackHost
+ dsMediatedServletPath;
if (logger.isDebugEnabled()) {
logger.debug(
"******************Checking backend service dsLocation: {}",
dissBindInfo.dsLocation);
logger.debug(
"******************Checking backend service dsControlGroupType: {}",
dissBindInfo.dsControlGroupType);
logger.debug(
"******************Checking backend service callbackBasicAuth: {}",
callbackBasicAuth);
logger.debug(
"******************Checking backend service callbackSSL: {}",
callbackSSL);
logger.debug(
"******************Checking backend service callbackRole: {}",
callbackRole);
logger.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;
}
logger.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 (m_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 (m_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 {
// Here we choose between two different tests for deciding
// whether to URL-encode the datastream URL:
//Old method:
// If the operationLocation contains datastreamInputParms and also
// contains a "=(" sequence, then
// URLEncode each parameter before substitution. Otherwise, the
// operationLocation has no parameters (i.e., it is a simple URL )
// so bypass URLencoding.
// New Method:
// If the operationLocation contains datastreamInputParms
// URLEncode each parameter before substitution, except when
// the parameter comprises the first part of the the URL.
boolean useUrlEncoding = m_useNewUrlEncodingTest ? dissURL.indexOf("("
+ bindingKeyPattern + ")") > 0 : dissURL
.indexOf("=(") != -1;
if (useUrlEncoding) {
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 + " . ";
logger.error(message);
throw new GeneralException(message);
}
logger.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 + " .";
logger.error(message);
throw new GeneralException(message);
}
String pattern = "\\(" + name + "\\)";
dissURL = substituteString(dissURL, pattern, value);
logger.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);
logger.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.
logger.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 + " . ";
logger.error(message);
throw new GeneralException(message);
}
logger.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