All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.treetank.service.jaxrx.implementation.DatabaseRepresentation Maven / Gradle / Ivy

The newest version!
/**
 * Copyright (c) 2011, University of Konstanz, Distributed Systems Group
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 * * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 * * Neither the name of the University of Konstanz nor the
 * names of its contributors may be used to endorse or promote products
 * derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL  BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package org.treetank.service.jaxrx.implementation; // NOPMD we need all these imports, declaring with * is

// pointless

import static org.treetank.data.IConstants.ROOT_NODE;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.StreamingOutput;

import org.jaxrx.core.JaxRxException;
import org.jaxrx.core.QueryParameter;
import org.treetank.access.NodeReadTrx;
import org.treetank.access.NodeWriteTrx;
import org.treetank.access.NodeWriteTrx.HashKind;
import org.treetank.access.conf.ResourceConfiguration;
import org.treetank.access.conf.SessionConfiguration;
import org.treetank.access.conf.StandardSettings;
import org.treetank.api.IBucketWriteTrx;
import org.treetank.api.IDataFactory;
import org.treetank.api.IMetaEntryFactory;
import org.treetank.api.INodeReadTrx;
import org.treetank.api.INodeWriteTrx;
import org.treetank.api.ISession;
import org.treetank.api.IStorage;
import org.treetank.axis.AbsAxis;
import org.treetank.data.NodeMetaPageFactory;
import org.treetank.data.TreeNodeFactory;
import org.treetank.exception.TTException;
import org.treetank.io.IBackend.IBackendFactory;
import org.treetank.revisioning.IRevisioning;
import org.treetank.service.jaxrx.util.RESTResponseHelper;
import org.treetank.service.jaxrx.util.RESTXMLShredder;
import org.treetank.service.jaxrx.util.RestXPathProcessor;
import org.treetank.service.jaxrx.util.WorkerHelper;
import org.treetank.service.xml.serialize.XMLSerializer;
import org.treetank.service.xml.serialize.XMLSerializer.XMLSerializerBuilder;
import org.treetank.service.xml.shredder.EShredderInsert;
import org.treetank.service.xml.shredder.XMLShredder;
import org.treetank.service.xml.xpath.XPathAxis;

/**
 * This class is the TreeTank DB connection for RESTful Web Services processing.
 * When a RESTful WS database request occurs it will be forwarded to TreeTank to
 * manage the request. Here XML files can be shredded and serialized to build
 * the client response. Further more it supports XPath queries.
 * 
 * @author Patrick Lang, Lukas Lewandowski, Sebastian Graf University of
 *         Konstanz
 * 
 */
public class DatabaseRepresentation {

    /** Path to storage. */
    private final IStorage mDatabase;

    /**
     * This field the begin result element of a XQuery or XPath expression.
     */
    private final static transient String beginResult = "";

    /**
     * This field the end result element of a XQuery or XPath expression.
     */
    private final static transient String endResult = "";

    /**
     * Often used 'yes' {@link String}.
     */
    private final static transient String YESSTRING = "yes";

    /**
     * Factory to build pages and meta structures
     */
    private final static IDataFactory NODEFACTORY = new TreeNodeFactory();
    private final static IMetaEntryFactory METAFAC = new NodeMetaPageFactory();

    private final IBackendFactory mStorageFac;

    private final IRevisioning mRevision;

    public DatabaseRepresentation(final IStorage pDatabase, final IBackendFactory pStorageFac,
        final IRevisioning pRevision) throws TTException {
        mDatabase = pDatabase;
        mStorageFac = pStorageFac;
        mRevision = pRevision;
    }

    /**
     * This method is responsible to create a new database.
     * 
     * @param inputStream
     *            The stream containing the XML document that has to be stored.
     * @param resourceName
     *            The name of the new database.
     * @throws JaxRxException
     *             The exception occurred.
     */
    public void createResource(final InputStream inputStream, final String resourceName)
        throws JaxRxException {
        synchronized (resourceName) {
            if (inputStream == null) {
                throw new JaxRxException(400, "Bad user request");
            } else {
                try {
                    shred(inputStream, resourceName);
                } catch (final TTException exce) {
                    throw new JaxRxException(exce);
                }
            }
        }
    }

    /**
     * This method is responsible to deliver the whole database. Additional
     * parameters can be set (wrap, revision, output) which change the response
     * view.
     * 
     * @param resourceName
     *            The name of the requested database.
     * @param queryParams
     *            The optional query parameters.
     * @return The XML database resource, depending on the query parameters.
     * @throws JaxRxException
     *             The exception occurred.
     */
    public StreamingOutput getResource(final String resourceName,
        final Map queryParams) throws JaxRxException {
        final StreamingOutput streamingOutput = new StreamingOutput() {
            @Override
            public void write(final OutputStream output) throws IOException, JaxRxException {
                final String revision = queryParams.get(QueryParameter.REVISION);
                final String wrap = queryParams.get(QueryParameter.WRAP);
                final String nodeId = queryParams.get(QueryParameter.OUTPUT);
                final boolean wrapResult = (wrap == null) ? false : wrap.equalsIgnoreCase(YESSTRING);
                final boolean nodeid = (nodeId == null) ? false : nodeId.equalsIgnoreCase(YESSTRING);
                try {
                    if (revision == null) {
                        serialize(resourceName, null, nodeid, output, wrapResult);
                    } else {
                        // pattern which have to match against the input
                        final Pattern pattern = Pattern.compile("[0-9]+[-]{1}[1-9]+");

                        final Matcher matcher = pattern.matcher(revision);

                        if (matcher.matches()) {
                            getModificHistory(resourceName, revision, nodeid, output, wrapResult);
                        } else {
                            serialize(resourceName, Long.valueOf(revision), nodeid, output, wrapResult);
                        }
                    }
                } catch (final NumberFormatException exce) {
                    throw new JaxRxException(400, exce.getMessage());
                } catch (final TTException exce) {
                    throw new JaxRxException(exce);
                }
            }
        };
        return streamingOutput;
    }

    /**
     * This method is responsible to perform queries on a special database.
     * (XPath queries).
     * 
     * @param resource
     *            The name of the database instance.
     * @param query
     *            The XPath expression.
     * @param otherParams
     *            Further query parameters (output, wrap, revision) which change
     *            the response.
     * @return The result of the XPath query expression.
     */
    public StreamingOutput performQueryOnResource(final String resource, final String query,
        final Map otherParams) {
        final StreamingOutput streamingOutput = new StreamingOutput() {
            @Override
            public void write(final OutputStream output) throws IOException, JaxRxException {
                final String revision = otherParams.get(QueryParameter.REVISION);
                final String wrap = otherParams.get(QueryParameter.WRAP);
                final String nodeId = otherParams.get(QueryParameter.OUTPUT);
                final boolean wrapResult = (wrap == null) ? true : wrap.equalsIgnoreCase(YESSTRING);
                final boolean nodeid = (nodeId == null) ? false : nodeId.equalsIgnoreCase(YESSTRING);
                final Long rev = revision == null ? null : Long.valueOf(revision);
                final RestXPathProcessor xpathProcessor = new RestXPathProcessor(mDatabase);
                try {
                    xpathProcessor.getXpathResource(resource, query, nodeid, rev, output, wrapResult);
                } catch (final TTException exce) {
                    throw new JaxRxException(exce);
                }
            }
        };
        return streamingOutput;
    }

    /**
     * This method is responsible to deliver a list of available resources and
     * collections supported by TreeTank's REST implementation.
     * 
     * @return The list of available databases and collections wrapped in an XML
     *         document.
     * @throws JaxRxException
     *             The exception occurred.
     */
    public StreamingOutput getResourcesNames() throws JaxRxException {
        return RESTResponseHelper.buildResponseOfDomLR(mDatabase, mStorageFac, mRevision);
    }

    /**
     * This method is responsible to add a new XML document to a collection.
     * 
     * @param input
     *            The new XML document packed in an {@link InputStream}.
     * @param resource
     *            The name of the collection.
     * @throws JaxRxException
     *             The exception occurred.
     */
    public void add(final InputStream input, final String resource) throws JaxRxException {
        synchronized (resource) {
            try {
                shred(input, resource);
            } catch (final TTException exce) {
                throw new JaxRxException(exce);
            }
        }

    }

    /**
     * This method is responsible to delete an existing database.
     * 
     * @param resourceName
     *            The name of the database.
     * @throws WebApplicationException
     *             The exception occurred.
     */
    public void deleteResource(final String resourceName) throws WebApplicationException {
        synchronized (resourceName) {
            try {
                mDatabase.truncateResource(new SessionConfiguration(resourceName, null));
            } catch (TTException e) {
                throw new WebApplicationException(e);
            }
        }
    }

    /**
     * This method is responsible to save the XML file, which is in an {@link InputStream}, as a TreeTank
     * object.
     * 
     * @param xmlInput
     *            The XML file in an {@link InputStream}.
     * @param resource
     *            The name of the resource.
     * @return true when the shredding process has been successful. false otherwise.
     * @throws TTException
     */
    public final boolean shred(final InputStream xmlInput, final String resource) throws TTException {
        boolean allOk;
        INodeWriteTrx wtx = null;
        IBucketWriteTrx pWtx = null;
        ISession session = null;
        boolean abort = false;
        try {
            if (!mDatabase.existsResource(resource)) {
                Properties properties =
                    StandardSettings.getProps(mDatabase.getLocation().getAbsolutePath(), resource);

                mDatabase.createResource(new ResourceConfiguration(properties, mStorageFac, mRevision,
                    NODEFACTORY, METAFAC));
            }

            session = mDatabase.getSession(new SessionConfiguration(resource, StandardSettings.KEY));
            pWtx = session.beginBucketWtx();
            wtx = new NodeWriteTrx(session, pWtx, HashKind.Rolling);
            wtx.moveTo(ROOT_NODE);
            final XMLShredder shredder =
                new XMLShredder(wtx, RESTXMLShredder.createReader(xmlInput), EShredderInsert.ADDASFIRSTCHILD);
            shredder.call();
            allOk = true;
        } catch (final Exception exce) {
            abort = true;
            throw new JaxRxException(exce);
        } finally {
            WorkerHelper.closeWTX(abort, wtx, session);
        }
        return allOk;
    }

    /**
     * This method is responsible to build an {@link OutputStream} containing an
     * XML file out of the TreeTank file.
     * 
     * @param resource
     *            The name of the resource that will be offered as XML.
     * @param nodeid
     *            To response the resource with a restid for each node ( true) or without (
     *            false).
     * @param revision
     *            The revision of the requested resource. If null,
     *            than response the latest revision.
     * @return The {@link OutputStream} containing the serialized XML file.
     * @throws IOException
     * @throws TTException
     * @throws WebApplicationException
     */
    private final OutputStream serialize(final String resource, final Long revision, final boolean nodeid,
        final OutputStream output, final boolean wrapResult) throws IOException, JaxRxException, TTException {

        if (mDatabase.existsResource(resource)) {
            try {
                if (wrapResult) {
                    output.write(beginResult.getBytes());
                    serializIt(resource, revision, output, nodeid);
                    output.write(endResult.getBytes());
                } else {
                    serializIt(resource, revision, output, nodeid);
                }
            } catch (final Exception exce) {
                throw new JaxRxException(exce);
            }
        } else {
            throw new JaxRxException(404, "Not found");
        }

        return output;
    }

    /**
     * This method reads the existing database, and offers the last revision id
     * of the database
     * 
     * @param resourceName
     *            The name of the existing database.
     * @return The {@link OutputStream} containing the result
     * @throws WebApplicationException
     *             The Exception occurred.
     * @throws TTException
     */
    public long getLastRevision(final String resourceName) throws JaxRxException, TTException {

        long lastRevision;
        if (mDatabase.existsResource(resourceName)) {
            ISession session = null;
            try {
                session = mDatabase.getSession(new SessionConfiguration(resourceName, StandardSettings.KEY));
                lastRevision = session.getMostRecentVersion();
            } catch (final Exception globExcep) {
                throw new JaxRxException(globExcep);
            } finally {
                session.close();
            }
        } else {
            throw new JaxRxException(404, "Resource not found");
        }
        return lastRevision;

    }

    /**
     * This method reads the existing database, and offers all modifications of
     * the two given revisions
     * 
     * @param resourceName
     *            The name of the existing database.
     * @param revisionRange
     *            Contains the range of revisions
     * @param nodeid
     *            To response the resource with a restid for each node ( true) or without (
     *            false).
     * @param output
     *            The OutputStream reference which have to be modified and
     *            returned
     * @param wrap
     *            true if the results have to be wrapped. false otherwise.
     * @return The {@link OutputStream} containing the result
     * @throws TTException
     * @throws WebApplicationException
     *             The Exception occurred.
     */
    public OutputStream getModificHistory(final String resourceName, // NOPMD this method needs alls these
        // functions
        final String revisionRange, final boolean nodeid, final OutputStream output, final boolean wrap)
        throws JaxRxException, TTException {

        // extract both revision from given String value
        final StringTokenizer tokenizer = new StringTokenizer(revisionRange, "-");
        final long revision1 = Long.valueOf(tokenizer.nextToken());
        final long revision2 = Long.valueOf(tokenizer.nextToken());

        if (revision1 < revision2 && revision2 <= getLastRevision(resourceName)) {

            // variables for highest rest-id in respectively revision
            long maxRestidRev1 = 0;
            long maxRestidRev2 = 0;

            // Connection to treetank, creating a session
            AbsAxis axis = null;
            INodeReadTrx rtx = null;
            ISession session = null;
            // List for all restIds of modifications
            final List modificRestids = new LinkedList();

            // List of all restIds of revision 1
            final List restIdsRev1 = new LinkedList();

            try {
                session = mDatabase.getSession(new SessionConfiguration(resourceName, StandardSettings.KEY));

                // get highest rest-id from given revision 1
                rtx = new NodeReadTrx(session.beginBucketRtx(revision1));
                axis = new XPathAxis(rtx, ".//*");

                while (axis.hasNext()) {
                    if (rtx.getNode().getDataKey() > maxRestidRev1) {
                        maxRestidRev1 = rtx.getNode().getDataKey();
                    }
                    // stores all restids from revision 1 into a list
                    restIdsRev1.add(rtx.getNode().getDataKey());
                }
                rtx.moveTo(ROOT_NODE);
                rtx.close();

                // get highest rest-id from given revision 2
                rtx = new NodeReadTrx(session.beginBucketRtx(revision2));
                axis = new XPathAxis(rtx, ".//*");

                while (axis.hasNext()) {
                    final Long nodeKey = rtx.getNode().getDataKey();
                    if (nodeKey > maxRestidRev2) {
                        maxRestidRev2 = rtx.getNode().getDataKey();
                    }
                    if (nodeKey > maxRestidRev1) {
                        /*
                         * writes all restids of revision 2 higher than the
                         * highest restid of revision 1 into the list
                         */
                        modificRestids.add(nodeKey);
                    }
                    /*
                     * removes all restids from restIdsRev1 that appears in
                     * revision 2 all remaining restids in the list can be seen
                     * as deleted nodes
                     */
                    restIdsRev1.remove(nodeKey);
                }
                rtx.moveTo(ROOT_NODE);
                rtx.close();

                rtx = new NodeReadTrx(session.beginBucketRtx(revision1));

                // linked list for holding unique restids from revision 1
                final List restIdsRev1New = new LinkedList();

                /*
                 * Checks if a deleted node has a parent node that was deleted
                 * too. If so, only the parent node is stored in new list to
                 * avoid double print of node modification
                 */
                for (Long nodeKey : restIdsRev1) {
                    rtx.moveTo(nodeKey);
                    final long parentKey = rtx.getNode().getParentKey();
                    if (!restIdsRev1.contains(parentKey)) {
                        restIdsRev1New.add(nodeKey);
                    }
                }
                rtx.moveTo(ROOT_NODE);
                rtx.close();

                if (wrap) {
                    output.write(beginResult.getBytes());
                }
                /*
                 * Shred modified restids from revision 2 to xml fragment Just
                 * modifications done by post commands
                 */
                rtx = new NodeReadTrx(session.beginBucketRtx(revision2));

                for (Long nodeKey : modificRestids) {
                    rtx.moveTo(nodeKey);
                    WorkerHelper.serializeXML(session, output, false, nodeid, nodeKey, revision2).call();
                }
                rtx.moveTo(ROOT_NODE);
                rtx.close();

                /*
                 * Shred modified restids from revision 1 to xml fragment Just
                 * modifications done by put and deletes
                 */
                rtx = new NodeReadTrx(session.beginBucketRtx(revision1));
                for (Long nodeKey : restIdsRev1New) {
                    rtx.moveTo(nodeKey);
                    WorkerHelper.serializeXML(session, output, false, nodeid, nodeKey, revision1).call();
                }
                if (wrap) {
                    output.write(endResult.getBytes());
                }

                rtx.moveTo(ROOT_NODE);

            } catch (final Exception globExcep) {
                throw new JaxRxException(globExcep);
            } finally {
                WorkerHelper.closeRTX(rtx, session);
            }
        } else {
            throw new JaxRxException(400, "Bad user request");
        }

        return output;
    }

    /**
     * The XML serializer to a given tnk file.
     * 
     * @param resource
     *            The resource that has to be serialized.
     * @param revision
     *            The revision of the document.
     * @param output
     *            The output stream where we write the XML file.
     * @param nodeid
     *            true when you want the result nodes with node
     *            id's. false otherwise.
     * @throws WebApplicationException
     *             The exception occurred.
     * @throws TTException
     */
    private void serializIt(final String resource, final Long revision, final OutputStream output,
        final boolean nodeid) throws JaxRxException, TTException {
        // Connection to treetank, creating a session
        ISession session = null;
        // INodeReadTrx rtx = null;
        try {
            session = mDatabase.getSession(new SessionConfiguration(resource, StandardSettings.KEY));
            // and creating a transaction
            // if (revision == null) {
            // rtx = session.beginReadTransaction();
            // } else {
            // rtx = session.beginReadTransaction(revision);
            // }
            final XMLSerializerBuilder builder;
            if (revision == null)
                builder = new XMLSerializerBuilder(session, output);
            else
                builder = new XMLSerializerBuilder(session, output, revision);
            builder.setREST(nodeid);
            builder.setID(nodeid);
            builder.setDeclaration(false);
            final XMLSerializer serializer = builder.build();
            serializer.call();
        } catch (final Exception exce) {
            throw new JaxRxException(exce);
        } finally {
            // closing the treetank storage
            WorkerHelper.closeRTX(null, session);
        }
    }

    /**
     * This method reverts the latest revision data to the requested.
     * 
     * @param resourceName
     *            The name of the XML resource.
     * @param backToRevision
     *            The revision value, which has to be set as the latest.
     * @throws WebApplicationException
     * @throws TTException
     */
    public void revertToRevision(final String resourceName, final long backToRevision) throws JaxRxException,
        TTException {
        ISession session = null;
        INodeWriteTrx wtx = null;
        boolean abort = false;
        try {
            session = mDatabase.getSession(new SessionConfiguration(resourceName, StandardSettings.KEY));
            wtx = new NodeWriteTrx(session, session.beginBucketWtx(), HashKind.Rolling);
            wtx.revertTo(backToRevision);
            wtx.commit();
        } catch (final TTException exce) {
            abort = true;
            throw new JaxRxException(exce);
        } finally {
            WorkerHelper.closeWTX(abort, wtx, session);
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy