Package org.fcrepo.server.access.dissemination

Source Code of org.fcrepo.server.access.dissemination.DisseminationService

/* 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 org.fcrepo.server.access.dissemination;

import java.io.ByteArrayInputStream;
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.fcrepo.server.Context;
import org.fcrepo.server.Server;
import org.fcrepo.server.errors.DisseminationBindingInfoNotFoundException;
import org.fcrepo.server.errors.DisseminationException;
import org.fcrepo.server.errors.GeneralException;
import org.fcrepo.server.errors.ServerException;
import org.fcrepo.server.security.Authorization;
import org.fcrepo.server.security.BackendPolicies;
import org.fcrepo.server.security.BackendSecurity;
import org.fcrepo.server.security.BackendSecuritySpec;
import org.fcrepo.server.storage.ContentManagerParams;
import org.fcrepo.server.storage.ExternalContentManager;
import org.fcrepo.server.storage.ServiceDeploymentReader;
import org.fcrepo.server.storage.types.DatastreamMediation;
import org.fcrepo.server.storage.types.DeploymentDSBindRule;
import org.fcrepo.server.storage.types.DeploymentDSBindSpec;
import org.fcrepo.server.storage.types.DisseminationBindingInfo;
import org.fcrepo.server.storage.types.MIMETypedStream;
import org.fcrepo.server.storage.types.MethodParmDef;
import org.fcrepo.server.utilities.ServerUtility;
import org.fcrepo.utilities.DateUtility;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* A service for executing a dissemination given its binding information.
*
* @author Ross Wayland
*/
public class DisseminationService {

  private static final Logger logger = LoggerFactory
      .getLogger(DisseminationService.class);

  /**
   * 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 boolean m_doDatastreamMediation;
  private boolean m_useNewUrlEncodingTest;

  /** Configured Fedora server host */
  private String m_fedoraServerHost = null;

  /** Configured Fedora server port */
  private String m_fedoraServerPort = null;

  /** Configured Fedora application server context */
  private String m_fedoraAppServerContext = null;

  /** Configured Fedora redirect port */
  private String m_fedoraServerRedirectPort = null;

  private BackendSecuritySpec m_beSS = null;

  private final BackendSecurity m_beSecurity;

  private final ExternalContentManager m_ecm;

  private final Authorization m_authorization;

  /** 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(Server server) {
    m_fedoraServerHost = server.getParameter("fedoraServerHost");
    m_fedoraServerPort = server.getParameter("fedoraServerPort");
    m_fedoraAppServerContext = server
        .getParameter("fedoraAppServerContext");
    m_fedoraServerRedirectPort = server.getParameter("fedoraRedirectPort");
    m_beSecurity = (BackendSecurity) server
        .getModule("org.fcrepo.server.security.BackendSecurity");
    m_beSS = m_beSecurity.getBackendSecuritySpec();
    String expireLimit = server.getParameter("datastreamExpirationLimit");
    if (expireLimit == null || expireLimit.equalsIgnoreCase("")) {
      logger.info("datastreamExpirationLimit unspecified; defaulting to "
          + "300 seconds");
      datastreamExpirationLimit = 300;
    } else {
      datastreamExpirationLimit = new Integer(expireLimit).intValue();
      logger.info("datastreamExpirationLimit="
          + datastreamExpirationLimit);
    }
    String dsMediation = server
        .getModule("org.fcrepo.server.access.Access").getParameter(
            "doMediateDatastreams");
    if (dsMediation == null || dsMediation.equalsIgnoreCase("")) {
      logger.info("doMediateDatastreams unspecified; defaulting to false");
    } else {
      m_doDatastreamMediation = new Boolean(dsMediation).booleanValue();
    }

    String useNewUrlEncodingTest = server
        .getModule("org.fcrepo.server.access.Access").getParameter(
            "useNewUrlEncoding");
    if (useNewUrlEncodingTest == null || useNewUrlEncodingTest.equalsIgnoreCase("")) {
      logger.info("useNewUrlEncodingTest unspecified; defaulting to false");
    } else {
      m_useNewUrlEncodingTest = new Boolean(useNewUrlEncodingTest).booleanValue();
    }
   
    m_ecm = server.getBean(
        "org.fcrepo.server.storage.ExternalContentManager",
        ExternalContentManager.class);
    m_authorization = server
        .getBean("org.fcrepo.server.security.Authorization",
            Authorization.class);
  }

  /**
   * <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 {

    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
          // request.
          logger.debug("Finished assembling dissemination, 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");
          }

          if (logger.isDebugEnabled()) {
            logger.debug(
                "******************getDisseminationContent beServiceRole: {}",
                beServiceRole);
            logger.debug(
                "******************getDisseminationContent beServiceCallBasicAuth: {}",
                beServiceCallBasicAuth);
            logger.debug(
                "******************getDisseminationContent beServiceCallSSL: {}",
                beServiceCallSSL);
            logger.debug(
                "******************getDisseminationContent beServiceCallUsername: {}",
                beServiceCallUsername);
            logger.debug(
                "******************getDisseminationContent beServiceCallPassword: {}",
                beServiceCallPassword);
            logger.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 = m_ecm.getExternalContent(params);
        }

      } else if (protocolType.equalsIgnoreCase("soap")) {
        // FIXME!! future handling of soap bindings.
        String message = "[DisseminationService] Protocol type: "
            + protocolType + "NOT yet implemented";
        logger.error(message);
        throw new DisseminationException(message);

      } else if (protocolType.equalsIgnoreCase("file")) {
        ContentManagerParams params = new ContentManagerParams(dissURL);
        params.setContext(context);
        dissemination = m_ecm.getExternalContent(params);
      } else {
        String message = "[DisseminationService] Protocol type: "
            + protocolType + "NOT supported.";
        logger.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";
      logger.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);
          logger.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 (logger.isDebugEnabled()) {
          logger.debug(
              "******************Registering datastream dsLocation: {}",
              dsLocation);
          logger.debug(
              "******************Registering datastream dsControlGroupType: {}",
              dsControlGroupType);
          logger.debug(
              "******************Registering datastream beServiceRole: {}",
              beServiceRole);
          logger.debug(
              "******************Registering datastream beServiceCallbackBasicAuth: {}",
              beServiceCallbackBasicAuth);
          logger.debug(
              "******************Registering datastream beServiceCallBasicAuth: {}",
              beServiceCallBasicAuth);
          logger.debug(
              "******************Registering datastream beServiceCallbackSSL: {}",
              beServiceCallbackSSL);
          logger.debug(
              "******************Registering datastream beServiceCallSSL: {}",
              beServiceCallSSL);
          logger.debug(
              "******************Registering datastream beServiceCallUsername: {}",
              beServiceCallUsername);
          logger.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);
        logger.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 + "/" + m_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\" .";
      logger.error(message);
      throw new GeneralException(message);
    }
    logger.debug("********** Resolving Internal Datastream dsLocation: "
        + dsLocation);
    return dsLocation;
  }

  public static void printBindingInfo(DisseminationBindingInfo[] info) {
    for (int i = 0; i < info.length; i++) {
      logger.debug("DisseminationBindingInfo[" + i + "]:");
      logger.debug("  DSBindKey          : " + info[i].DSBindKey);
      logger.debug("  dsLocation         : " + info[i].dsLocation);
      logger.debug("  dsControlGroupType : " + info[i].dsControlGroupType);
      logger.debug("  dsID               : " + info[i].dsID);
      logger.debug("  dsVersionID        : " + info[i].dsVersionID);
      logger.debug("  AddressLocation    : " + info[i].AddressLocation);
      logger.debug("  OperationLocation  : " + info[i].OperationLocation);
      logger.debug("  ProtocolType       : " + info[i].ProtocolType);
      logger.debug("  dsState            : " + info[i].dsState);
      logger.debug("  dsCreateDT         : " + info[i].dsCreateDT);
      for (int j = 0; j < info[i].methodParms.length; j++) {
        MethodParmDef def = info[i].methodParms[j];
        logger.debug("  MethodParamDef[" + j + "]:");
        logger.debug("    parmName         : " + def.parmName);
        logger.debug("    parmDefaultValue : " + def.parmDefaultValue);
        logger.debug("    parmRequired     : " + def.parmRequired);
        logger.debug("    parmLabel        : " + def.parmLabel);
        logger.debug("    parmPassBy       : " + def.parmPassBy);
        for (String element : def.parmDomainValues) {
          logger.debug("    parmDomainValue  : " + element);
        }
      }
    }
  }
}
TOP

Related Classes of org.fcrepo.server.access.dissemination.DisseminationService

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.