fedorax.server.module.storage.IrodsExternalContentManager.java Source code

Java tutorial

Introduction

Here is the source code for fedorax.server.module.storage.IrodsExternalContentManager.java

Source

/**
 * Copyright 2008 The University of North Carolina at Chapel Hill
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package fedorax.server.module.storage;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Map;

import javax.activation.MimetypesFileTypeMap;
import javax.management.MBeanServer;
import javax.management.MBeanServerFactory;

import org.apache.commons.httpclient.Header;

import org.fcrepo.common.http.HttpInputStream;
import org.fcrepo.common.http.WebClient;
import org.fcrepo.server.Module;
import org.fcrepo.server.Server;
import org.fcrepo.server.errors.GeneralException;
import org.fcrepo.server.errors.HttpServiceNotFoundException;
import org.fcrepo.server.errors.ModuleInitializationException;
import org.fcrepo.server.errors.ValidationException;
import org.fcrepo.server.errors.authorization.AuthzException;
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.types.MIMETypedStream;
import org.fcrepo.server.storage.types.Property;
import org.fcrepo.server.utilities.ServerUtility;
import org.fcrepo.server.validation.ValidationUtility;

import org.irods.jargon.core.connection.IRODSAccount;
import org.irods.jargon.core.exception.JargonException;
import org.irods.jargon.core.pub.IRODSFileSystem;
import org.irods.jargon.core.pub.io.IRODSFile;
import org.irods.jargon.core.pub.io.IRODSFileFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import edu.unc.lib.staging.Stages;
import edu.unc.lib.staging.StagingArea;
import edu.unc.lib.staging.StagingException;
import fedorax.server.module.storage.lowlevel.irods.IrodsLowlevelStorageModule;

/**
 * @author Gregory Jansen
 * 
 */
public class IrodsExternalContentManager extends Module implements ExternalContentManager {
    private static final Logger LOG = LoggerFactory.getLogger(IrodsExternalContentManager.class);

    static {
        // Register IRODS URL Protocol Handler (see metadata project)
        // by making a static reference to the class that loads it
        @SuppressWarnings("unused")
        String foo = IrodsLowlevelStorageModule.REGISTRY_NAME;
    }

    /**
     * Stages configuration JSON file
     */
    private File stagesConfiguration;

    public File getStagesConfiguration() {
        return stagesConfiguration;
    }

    public void setStagesConfiguration(File stagesConfiguration) {
        this.stagesConfiguration = stagesConfiguration;
    }

    private Stages stages;

    public Stages getStages() {
        return this.stages;
    }

    // injected properties
    private IRODSAccount irodsAccount;

    private int irodsReadBufferSize;

    public IRODSAccount getIrodsAccount() {
        return irodsAccount;
    }

    public void setIrodsAccount(IRODSAccount irodsAccount) {
        this.irodsAccount = irodsAccount;
    }

    public int getIrodsReadBufferSize() {
        return irodsReadBufferSize;
    }

    public void setIrodsReadBufferSize(int irodsReadBufferSize) {
        this.irodsReadBufferSize = irodsReadBufferSize;
    }

    // runtime stats
    int connectionsUsed = 0;
    int currentConnectionUsage = 0;
    boolean reuseConnections = false;

    // constants
    private static final String DEFAULT_MIMETYPE = "text/plain";

    // initialized properties
    private String fedoraServerPort = "80";
    private String fedoraServerRedirectPort = "443";
    private WebClient m_http;

    /**
     * @param moduleParameters
     * @param server
     * @param role
     * @throws ModuleInitializationException
     */
    public IrodsExternalContentManager(Map<String, String> moduleParameters, Server server, String role)
            throws ModuleInitializationException {
        super(moduleParameters, server, role);
    }

    /**
     * Initializes the Module based on configuration parameters. The
     * implementation of this method is dependent on the schema used to define
     * the parameter names for the role of
     * <code>fedora.server.storage.DefaultExternalContentManager</code>.
     * 
     * @throws ModuleInitializationException
     *             If initialization values are invalid or initialization fails
     *             for some other reason.
     */
    @Override
    public void initModule() throws ModuleInitializationException {
        try {
            Server s_server = getServer();
            if (s_server != null) {
                fedoraServerPort = s_server.getParameter("fedoraServerPort");
                fedoraServerRedirectPort = s_server.getParameter("fedoraRedirectPort");
            }
            m_http = new WebClient();

            StringBuilder sb = new StringBuilder();
            try (BufferedReader r = new BufferedReader(new FileReader(this.stagesConfiguration))) {
                for (String line = r.readLine(); line != null; line = r.readLine()) {
                    sb.append(line).append('\n');
                }
            }
            LOG.debug("local staging config:\n" + sb.toString());
            this.stages = new Stages(sb.toString(), new IRODSStageResolver(irodsAccount));
            for (StagingArea s : this.stages.getAllAreas().values()) {
                if (!s.isConnected()) {
                    this.stages.connect(s.getURI());
                    if (!s.isConnected()) {
                        LOG.warn("Cannot connect to staging area: " + s.getURI());
                    }
                }
            }

            // TODO register Stages as MBean
            //ObjectName name = new ObjectName("edu.unc.lib.cdr:type=Stages");
            //MBeanServer mbs = this.getMBeanServer();
            //mbs.registerMBean(this.stages, name);

        } catch (Throwable th) {
            th.printStackTrace();
            throw new ModuleInitializationException("[IrodsExternalContentManager] "
                    + "An external content manager " + "could not be instantiated. The underlying error was a "
                    + th.getClass() + "The message was \"" + th.getMessage() + "\".", getRole());
        }
    }

    @SuppressWarnings("unused")
    private MBeanServer getMBeanServer() {
        MBeanServer mbserver = null;
        ArrayList<MBeanServer> mbservers = MBeanServerFactory.findMBeanServer(null);

        if (mbservers.size() > 0) {
            mbserver = (MBeanServer) mbservers.get(0);
        }

        if (mbserver != null) {
            System.out.println("Found our MBean server");
        } else {
            mbserver = MBeanServerFactory.createMBeanServer();
        }

        return mbserver;
    }

    /*
     * Retrieves the external content. Currently the protocols <code>file</code>
     * and <code>http[s]</code> are supported.
     * 
     * @see
     * fedora.server.storage.ExternalContentManager#getExternalContent(fedora
     * .server.storage.ContentManagerParams)
     */
    public MIMETypedStream getExternalContent(ContentManagerParams params)
            throws GeneralException, HttpServiceNotFoundException {
        LOG.debug("in getExternalContent(), url=" + params.getUrl());

        String protocol = params.getProtocol();
        URI uri = URI.create(params.getUrl());

        boolean staged = false;
        try {
            LOG.debug("manifestURI: " + uri);
            LOG.debug("stages: " + this.stages);
            LOG.debug("stages size: " + this.stages.getAllAreas().size());
            URI storageURI = this.stages.getStorageURI(uri);
            LOG.debug("storageURI: " + storageURI);
            staged = true;
            protocol = storageURI.getScheme();
            uri = storageURI;
        } catch (StagingException e) {
            LOG.warn("Exception throw resolving local URL", e);
        }
        LOG.debug("protocol is " + protocol + ", uri is " + uri);
        if (protocol == null && uri.toString().startsWith("irods://")) {
            return getFromIrods(uri, params.getMimeType());
        } else if (protocol == null || protocol.equals("file")) {
            return getFromFilesystem(uri, params.getMimeType(), staged, params);
        } else if (protocol.equals("http") || protocol.equals("https")) {
            try {
                return getFromWeb(params);
            } catch (ModuleInitializationException e) {
                throw new GeneralException(e.getMessage() + "(" + params.getUrl() + ")", e);
            }
        } else if (protocol.equals("irods")) {
            return getFromIrods(uri, params.getMimeType());
        }
        throw new GeneralException(
                "protocol for retrieval of external content not supported. URL: " + params.getUrl());
    }

    /**
     * @param params
     * @return
     */
    private MIMETypedStream getFromIrods(URI uri, String mimeType)
            throws HttpServiceNotFoundException, GeneralException {
        try {
            LOG.debug("uri: " + uri);
            IRODSFileFactory ff = IRODSFileSystem.instance().getIRODSFileFactory(irodsAccount);
            IRODSFile file = ff.instanceIRODSFile(URLDecoder.decode(uri.getRawPath(), "UTF-8"));
            InputStream result = ff.instanceIRODSFileInputStream(file);
            final long start = System.currentTimeMillis();
            result = new BufferedInputStream(result, this.irodsReadBufferSize) {
                int bytes = 0;

                @Override
                public void close() throws IOException {
                    if (LOG.isInfoEnabled()) {
                        long time = System.currentTimeMillis() - start;
                        if (time > 0) {
                            LOG.info("closed irods stream: " + bytes + " bytes at " + (bytes / time) + " kb/sec");
                        }
                    }
                    super.close();
                }

                @Override
                public synchronized int read() throws IOException {
                    bytes++;
                    return super.read();
                }

                @Override
                public synchronized int read(byte[] b, int off, int len) throws IOException {
                    bytes = bytes + len;
                    return super.read(b, off, len);
                }

            };

            // if mimeType was not given, try to determine it automatically
            if (mimeType == null || mimeType.equalsIgnoreCase("")) {
                String irodsFilename = file.getName();
                if (irodsFilename != null) {
                    mimeType = new MimetypesFileTypeMap().getContentType(irodsFilename);
                }
                if (mimeType == null || mimeType.equalsIgnoreCase("")) {
                    mimeType = DEFAULT_MIMETYPE;
                }
            }
            return new MIMETypedStream(mimeType, result, getPropertyArray(mimeType), file.length());
            /*
             * } catch (AuthzException ae) { LOG.error(ae.getMessage(), ae);
             * throw new
             * HttpServiceNotFoundException("Policy blocked datastream resolution"
             * , ae); } catch (GeneralException me) { LOG.error(me.getMessage(),
             * me); throw me; }
             */

        } catch (JargonException e) {
            throw new GeneralException("Problem getting iRODS input stream", e);
        } catch (Throwable th) {
            th.printStackTrace(System.err);
            // catch anything but generalexception
            LOG.error(th.getMessage(), th);
            throw new HttpServiceNotFoundException(
                    "[FileExternalContentManager] " + "returned an error.  The underlying error was a "
                            + th.getClass().getName() + "  The message " + "was  \"" + th.getMessage() + "\"  .  ",
                    th);
        }
    }

    /**
     * @param mimeType
     * @return
     */
    private Property[] getPropertyArray(String mimeType) {
        Property[] props = new Property[1];
        Property ctype = new Property("Content-Type", mimeType);
        props[0] = ctype;
        return props;
    }

    /**
     * Get a MIMETypedStream for the given URL. If user or password are
     * <code>null</code>, basic authentication will not be attempted.
     */
    private MIMETypedStream get(String url, String user, String pass, String knownMimeType)
            throws GeneralException {
        LOG.debug("DefaultExternalContentManager.get(" + url + ")");
        try {
            HttpInputStream response = m_http.get(url, true, user, pass);
            String mimeType = response.getResponseHeaderValue("Content-Type", knownMimeType);
            Property[] headerArray = toPropertyArray(response.getResponseHeaders());
            return new MIMETypedStream(mimeType, response, headerArray);
        } catch (Exception e) {
            throw new GeneralException("Error getting " + url, e);
        }
    }

    /**
     * Convert the given HTTP <code>Headers</code> to an array of
     * <code>Property</code> objects.
     */
    private static Property[] toPropertyArray(Header[] headers) {

        Property[] props = new Property[headers.length];
        for (int i = 0; i < headers.length; i++) {
            props[i] = new Property();
            props[i].name = headers[i].getName();
            props[i].value = headers[i].getValue();
        }
        return props;
    }

    /**
     * Creates a property array out of the MIME type and the length of the
     * provided file.
     * 
     * @param file
     *            the file containing the content.
     * @return an array of properties containing content-length and
     *         content-type.
     */
    private static Property[] getPropertyArray(File file, String mimeType) {
        Property[] props = new Property[2];
        Property clen = new Property("Content-Length", Long.toString(file.length()));
        Property ctype = new Property("Content-Type", mimeType);
        props[0] = clen;
        props[1] = ctype;
        return props;
    }

    /**
     * Get a MIMETypedStream for the given URL. If user or password are
     * <code>null</code>, basic authentication will not be attempted.
     * 
     * @param params
     * @return
     * @throws HttpServiceNotFoundException
     * @throws GeneralException
     */
    private MIMETypedStream getFromFilesystem(URI uri, String mimeType, boolean staged, ContentManagerParams params)
            throws HttpServiceNotFoundException, GeneralException {
        LOG.debug("in getFile(), url=" + uri);

        try {
            File cFile = new File(uri.getPath()).getCanonicalFile();

            // security check, staged files are in known locations
            if (!staged) {
                URI cURI = cFile.toURI();
                LOG.info("Checking resolution security on " + cURI);
                Authorization authModule = (Authorization) getServer()
                        .getModule("fedora.server.security.Authorization");
                if (authModule == null) {
                    throw new GeneralException("Missing required Authorization module");
                }
                authModule.enforceRetrieveFile(params.getContext(), cURI.toString());
            }

            // if mimeType was not given, try to determine it automatically
            if (mimeType == null || mimeType.equalsIgnoreCase("")) {
                mimeType = determineMimeType(cFile);
            }
            InputStream in = new FileInputStream(cFile);
            return new MIMETypedStream(mimeType, in, getPropertyArray(cFile, mimeType));
        } catch (AuthzException ae) {
            LOG.error(ae.getMessage(), ae);
            throw new HttpServiceNotFoundException("Policy blocked datastream resolution", ae);
        } catch (GeneralException me) {
            LOG.error(me.getMessage(), me);
            throw me;
        } catch (Throwable th) {
            th.printStackTrace(System.err);
            // catch anything but generalexception
            LOG.error(th.getMessage(), th);
            throw new HttpServiceNotFoundException(
                    "[FileExternalContentManager] " + "returned an error.  The underlying error was a "
                            + th.getClass().getName() + "  The message " + "was  \"" + th.getMessage() + "\"  .  ",
                    th);
        }
    }

    /**
     * Retrieves external content via http or https.
     * 
     * @param url
     *            The url pointing to the content.
     * @param context
     *            The Map containing parameters.
     * @param mimeType
     *            The default MIME type to be used in case no MIME type can be
     *            detected.
     * @return A MIMETypedStream
     * @throws ModuleInitializationException
     * @throws GeneralException
     */
    private MIMETypedStream getFromWeb(ContentManagerParams params)
            throws ModuleInitializationException, GeneralException {
        String username = params.getUsername();
        String password = params.getPassword();
        boolean backendSSL = false;
        String url = params.getUrl();

        if (ServerUtility.isURLFedoraServer(url) && !params.isBypassBackend()) {
            BackendSecuritySpec m_beSS;
            BackendSecurity m_beSecurity = (BackendSecurity) getServer()
                    .getModule("fedora.server.security.BackendSecurity");
            try {
                m_beSS = m_beSecurity.getBackendSecuritySpec();
            } catch (Exception e) {
                throw new ModuleInitializationException(
                        "Can't intitialize BackendSecurity module (in default access) from Server.getModule",
                        getRole());
            }
            Hashtable<String, String> beHash = m_beSS.getSecuritySpec(BackendPolicies.FEDORA_INTERNAL_CALL);
            username = beHash.get("callUsername");
            password = beHash.get("callPassword");
            backendSSL = new Boolean(beHash.get("callSSL")).booleanValue();
            if (backendSSL) {
                if (params.getProtocol().equals("http:")) {
                    url = url.replaceFirst("http:", "https:");
                }
                url = url.replaceFirst(":" + fedoraServerPort + "/", ":" + fedoraServerRedirectPort + "/");
            }
            if (LOG.isDebugEnabled()) {
                LOG.debug("************************* backendUsername: " + username + "     backendPassword: "
                        + password + "     backendSSL: " + backendSSL);
                LOG.debug("************************* doAuthnGetURL: " + url);
            }

        }
        return get(url, username, password, params.getMimeType());
    }

    /**
     * Determines the mime type of a given file
     * 
     * @param file
     *            for which the mime type needs to be detected
     * @return the detected mime type
     */
    private String determineMimeType(File file) {
        String mimeType = new MimetypesFileTypeMap().getContentType(file);
        // if mimeType detection failed, fall back to the default
        if (mimeType == null || mimeType.equalsIgnoreCase("")) {
            mimeType = DEFAULT_MIMETYPE;
        }
        return mimeType;
    }

    @Override
    public void postInitModule() throws ModuleInitializationException {
        super.postInitModule();
        // check if Fedora is patched via ValidateURL utility thing
        try {
            ValidationUtility.validateURL("irods://example.com:1247/fooZone/home/foo", "M");
        } catch (ValidationException e1) {
            String msg = "Fedora Server is not patched to support the IrodsExternalContentManager";
            LOG.error(msg, e1);
            throw new ModuleInitializationException(msg, "fedora.server.storage.ExternalContentManager", e1);
        }
    }

}