edu.northwestern.jcr.adapter.fedora.persistence.FedoraConnectorREST.java Source code

Java tutorial

Introduction

Here is the source code for edu.northwestern.jcr.adapter.fedora.persistence.FedoraConnectorREST.java

Source

/*
 * Copyright 2009 Northwestern University
 *
 * Licensed under the Educational Community 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.osedu.org/licenses/ECL-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 edu.northwestern.jcr.adapter.fedora.persistence;

import java.io.File;

import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;

import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;
import java.util.regex.Matcher;

import java.net.URLEncoder;

import javax.jcr.RepositoryException;

import fedora.client.HttpInputStream;

import static org.apache.commons.httpclient.HttpStatus.SC_CREATED;
import static org.apache.commons.httpclient.HttpStatus.SC_OK;
import static org.apache.commons.httpclient.HttpStatus.SC_NO_CONTENT;
import org.apache.commons.httpclient.methods.DeleteMethod;
import org.apache.commons.httpclient.methods.PostMethod;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * <code>FedoraConnectorREST</code> accesses Fedora repository
 * and implements the abstract methods defined in {@link FedoraConnector}
 * using Fedora REST API. For a detailed explanation of Fedora REST API please 
 * refer to <a href="http://www.fedora-commons.org/documentation/3.2/REST%20API.html">Fedora Repository 3.2 Documentation: REST API</a>
 * 
 * @author Xin Xiang
 */
public class FedoraConnectorREST extends FedoraConnector {

    /** log4j logger. */
    private static Logger log = LoggerFactory.getLogger(FedoraConnectorREST.class);

    /**
     * Sends an HTTP DELETE request and returns the status code.
     *
     * @param url URL of the resource
     * @return status code
     */
    private int httpDelete(String url) {
        DeleteMethod deleteMethod = null;

        try {
            deleteMethod = new DeleteMethod(url);
            deleteMethod.setDoAuthentication(true);
            deleteMethod.getParams().setParameter("Connection", "Keep-Alive");
            fc.getHttpClient().executeMethod(deleteMethod);

            return deleteMethod.getStatusCode();
        } catch (Exception e) {
            e.printStackTrace();
            log.warn("failed to delete!");

            return -1;
        } finally {
            if (deleteMethod != null) {
                deleteMethod.releaseConnection();
            }
        }
    }

    /**
     * Sends an HTTP POST request and returns the status code.
     *
     * @param url URL of the service
     * @return status code
     */
    private int httpPost(String url) throws Exception {
        PostMethod postMethod = null;

        try {
            postMethod = new PostMethod(url);
            postMethod.setDoAuthentication(true);
            postMethod.getParams().setParameter("Connection", "Keep-Alive");
            postMethod.setContentChunked(true);
            fc.getHttpClient().executeMethod(postMethod);

            return postMethod.getStatusCode();
        } catch (Exception e) {
            String msg = "error connecting to the Fedora server";
            log.error(msg);
            throw new RepositoryException(msg, null);
        } finally {
            if (postMethod != null) {
                postMethod.releaseConnection();
            }
        }
    }

    /**
     * Creates a dummy Fedora object with default attributes.
     * @param pid pid the new object
     */
    public void createObject(String pid) throws Exception {
        String url;
        PostMethod postMethod = null;
        int statusCode;

        try {
            pid = URLEncoder.encode(pid, "UTF-8");
        } catch (Exception e) {

        }

        url = baseURL + "/objects/" + pid;
        log.debug("ingesting " + pid);
        statusCode = httpPost(url);
        if (statusCode != SC_OK && statusCode != SC_CREATED) {
            log.warn("status code: " + statusCode);
        }
        ;
    }

    /**
     * Deletes a digital object.
     * Wrapper of purgeObject in Fedora REST.
     *
     * @param pid pid of the object to be deleted
     */
    public void deleteObject(String pid) {
        String url;
        int statusCode;

        try {
            pid = URLEncoder.encode(pid, "UTF-8");
        } catch (Exception e) {

        }

        url = baseURL + "/objects/" + pid;
        statusCode = httpDelete(url);
        if (statusCode != SC_OK && statusCode != SC_NO_CONTENT) {
            log.warn("status code: " + statusCode);
        }
        ;
    }

    /**
     * Wrapper of findObjects in REST
     * Get a list of objects in Fedora repository
     *
     * @param query the pattern of pid
     * @return a list of pid that satisfy tha pattern
     */
    public String[] listObjects(String query) throws Exception {
        String response = "";
        String allResponses;
        Pattern pattern;
        Matcher matcher;
        String line;
        String pid;
        List<String> list = new ArrayList<String>();
        String sessionToken;

        try {
            response = fc.getResponseAsString(
                    "/objects?query=pid%7E" + query + "&maxResults=1024&resultFormat=xml&pid=true", true, false);
        } catch (Exception e) {
            return null;
        }

        allResponses = response;
        while (response.contains("<token>")) {
            sessionToken = response.substring(response.indexOf("<token>") + 7, response.indexOf("</token>"));

            try {
                response = fc.getResponseAsString("/objects?query=pid%7E" + query
                        + "&maxResults=1024&resultFormat=xml&pid=true&sessionToken=" + sessionToken, true, false);
            } catch (Exception e) {
                break;
            }

            allResponses += response;
        }

        pattern = Pattern.compile("<pid>[^<]+</pid>");
        matcher = pattern.matcher(allResponses);

        while (matcher.find()) {
            line = matcher.group();
            pid = line.substring(5, line.length() - 6);
            list.add(pid);
        }

        return (String[]) list.toArray(new String[0]);
    }

    /**
     * Wrappper of listDatastreams in REST.
     *
     * @param pid pid of the object
     * @return list of the <code>DataStream</code> objects
     */
    public DataStream[] listDataStreams(String pid) {
        DataStream dataStream;
        int i;
        String response = "";
        Pattern pattern, attributePattern;
        Matcher matcher, attributeMatcher;
        String line;
        String s = "";
        String dsid, label, mimeType;
        List<DataStream> list = new ArrayList<DataStream>();

        try {
            response = fc.getResponseAsString(
                    String.format("/objects/%s/datastreams.xml", URLEncoder.encode(pid, "UTF-8")), true, false);
        } catch (Exception e) {
            return null;
        }

        pattern = Pattern.compile("<datastream [^>]+/>");
        matcher = pattern.matcher(response);

        while (matcher.find()) {
            // iterate over all the datastream elements
            line = matcher.group();

            // get dsid, label and mime type respectively
            attributePattern = Pattern.compile("dsid=\"[^\"]+\"");
            attributeMatcher = attributePattern.matcher(line);
            if (attributeMatcher.find()) {
                s = attributeMatcher.group();
            }

            dsid = s.substring(6, s.length() - 1);

            attributePattern = Pattern.compile("label=\"[^\"]*\"");
            attributeMatcher = attributePattern.matcher(line);
            if (attributeMatcher.find()) {
                s = attributeMatcher.group();
            }
            label = s.substring(7, s.length() - 1);

            attributePattern = Pattern.compile("mimeType=\"[^\"]*\"");
            attributeMatcher = attributePattern.matcher(line);
            if (attributeMatcher.find()) {
                s = attributeMatcher.group();
            }
            mimeType = s.substring(10, s.length() - 1);

            if (mimeType == null || mimeType.equals("")) {
                // set default MIME type
                mimeType = "application/octet-stream";
            }

            // add the data stream object
            dataStream = new DataStream(dsid, label, mimeType);
            list.add(dataStream);

            log.debug(dsid + ", " + label + ", " + mimeType);
        }

        return list.toArray(new DataStream[0]);
    }

    /**
     * Wrapper of getDatastreamDissemination in REST.
     *
     * @param pid pid of the object
     * @param dsID id of the datastream
     * @return byte content of the data stream
     */
    public byte[] getDataStream(String pid, String dsID) {
        HttpInputStream inputStream;
        ReadableByteChannel channel;
        ByteBuffer buf;
        byte[] bytes;
        int numRead = 0;
        int length = 0;

        try {
            inputStream = fc.get(
                    String.format("/objects/%s/datastreams/%s/content", URLEncoder.encode(pid, "UTF-8"), dsID),
                    true, false);
        } catch (Exception e) {
            return null;
        }

        channel = Channels.newChannel(inputStream);
        // Create a direct ByteBuffer
        buf = ByteBuffer.allocateDirect(10 * 1024 * 1024);

        while (numRead >= 0) {
            // Read bytes from the channel
            try {
                numRead = channel.read(buf);
            } catch (Exception e) {
                return null;
            }

            if (numRead > 0) {
                length += numRead;
            }
        }

        bytes = new byte[length];
        // reset the position of the buffer to zero
        buf.rewind();
        buf.get(bytes);

        return bytes;
    }

    /**
     * Tests if a given digital object already exists in the Fedora repository.
     *
     * @param pid pid of the object to be tested
     * @return whether the object exists
     */
    public boolean existsObject(String pid) {
        String response = "";
        Pattern pattern;
        Matcher matcher;

        try {
            response = fc.getResponseAsString(String.format("/objects?query=pid%%7E%s&resultFormat=xml&pid=true",
                    URLEncoder.encode(pid, "UTF-8")), true, false);
        } catch (Exception e) {
            return false;
        }

        // System.out.println("response: " + response);

        pattern = Pattern.compile("<pid>[^<]+</pid>");
        matcher = pattern.matcher(response);

        return matcher.find();
    }

    /**
     * Tests if a given data stream already exists in the Fedora repository.
     *
     * @param pid pid of the object
     * @param dsID id of the datastream
     * @return whether the data stream exists
     */
    public boolean existsDataStream(String pid, String dsID) {
        String response = "";
        Pattern pattern, attributePattern;
        Matcher matcher, attributeMatcher;
        String line;
        String s = "";
        String id;

        try {
            response = fc.getResponseAsString(
                    String.format("/objects/%s/datastreams.xml", URLEncoder.encode(pid, "UTF-8")), true, false);
        } catch (Exception e) {
            return false;
        }

        pattern = Pattern.compile("<datastream [^>]+/>");
        matcher = pattern.matcher(response);

        while (matcher.find()) {
            // iterate over all the datastream elements
            line = matcher.group();

            // get dsid, label and mime type respectively
            attributePattern = Pattern.compile("dsid=\"[^\"]+\"");
            attributeMatcher = attributePattern.matcher(line);
            if (attributeMatcher.find()) {
                s = attributeMatcher.group();
            }

            id = s.substring(6, s.length() - 1);

            if (id.equals(dsID)) {
                return true;
            }
        }

        return false;
    }

    /**
     * CHANGE ME - use REST !!!
     * Modfies the default Dublin Core data stream.
     *
     * @param pid pid of the object
     * @param bytes byte content of the new data stream
     */
    public void modifyDCDataStream(String pid, byte[] bytes) {
        try {
            // make the SOAP call on API-M using the connection stub
            fc.getAPIM().modifyDatastreamByValue(pid, "DC", null, null, null, null, bytes, null, null, null, true);
        } catch (Exception e) {
            e.printStackTrace();
            System.err.println("failed to update DC stream!");
        }
    }

    /**
     * Adds a data stream.
     * Wrapper of addDatastream in Fedora REST.
     *
     * @param pid pid of the object
     * @param dsID id of the data stream
     * @param mimeType MIME type of the data stream content
     * @param fileName name of the file storing the data stream content
     */
    public void addDataStream(String pid, String dsID, String mimeType, String fileName) {
        String url;
        int statusCode;

        try {
            String dsLocation = fc.uploadFile(new File(fileName));
            log.debug("filed uploaded at " + dsLocation);

            if (mimeType == null) {
                // set default MIME type
                mimeType = "application/octet-stream";
            }

            url = baseURL + "/objects/" + URLEncoder.encode(pid, "UTF-8") + "/datastreams/" + dsID
                    + "?controlGroup=M&dsLabel=" + dsID + "&mimeType=" + URLEncoder.encode(mimeType, "UTF-8")
                    + "&dsLocation=" + URLEncoder.encode(dsLocation, "UTF-8");

            statusCode = httpPost(url);

            if (statusCode != SC_CREATED) {
                log.warn("status code: " + statusCode);
                log.warn("failed to add data stream!");
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.error("failed to add data stream!", e);
        }
    }

    /**
     * Deletes a data stream.
     * Wrapper of purgeDatastream in Fedora REST
     * 
     * @param pid pid of the object
     * @param dsID id of the data stream
     */
    public void deleteDataStream(String pid, String dsID) {
        String url;
        int statusCode;

        try {
            pid = URLEncoder.encode(pid, "UTF-8");
        } catch (Exception e) {

        }

        url = baseURL + "/objects/" + pid + "/datastreams/" + dsID;
        statusCode = httpDelete(url);
        if (statusCode != SC_OK) {
            log.warn("status code: " + statusCode);
        }
    }
}