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

org.efaps.db.store.JCRStoreResource Maven / Gradle / Ivy

Go to download

eFaps is a framework used to map objects with or without attached files to a relational database and optional file systems (only for attaches files). Configurable access control can be provided down to object and attribute level depending on implementation and use case. Depending on requirements, events (like triggers) allow to implement business logic and to separate business logic from user interface. The framework includes integrations (e.g. webdav, full text search) and a web application as 'simple' configurable user interface. Some best practises, example web application modules (e.g. team work module) support administrators and implementers using this framework.

There is a newer version: 3.2.0
Show newest version
/*
 * Copyright 2003 - 2011 The eFaps Team
 *
 * 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.
 *
 * Revision:        $Rev: 6961 $
 * Last Changed:    $Date: 2011-08-18 12:11:27 -0500 (Thu, 18 Aug 2011) $
 * Last Changed By: $Author: [email protected] $
 */

package org.efaps.db.store;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Calendar;

import javax.jcr.AccessDeniedException;
import javax.jcr.Binary;
import javax.jcr.InvalidItemStateException;
import javax.jcr.ItemExistsException;
import javax.jcr.LoginException;
import javax.jcr.NoSuchWorkspaceException;
import javax.jcr.Node;
import javax.jcr.Property;
import javax.jcr.ReferentialIntegrityException;
import javax.jcr.Repository;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.SimpleCredentials;
import javax.jcr.lock.LockException;
import javax.jcr.nodetype.ConstraintViolationException;
import javax.jcr.nodetype.NoSuchNodeTypeException;
import javax.jcr.nodetype.NodeType;
import javax.jcr.version.VersionException;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.transaction.xa.XAException;
import javax.transaction.xa.Xid;

import org.efaps.db.Context;
import org.efaps.db.Instance;
import org.efaps.db.transaction.ConnectionResource;
import org.efaps.db.wrapper.SQLSelect;
import org.efaps.util.EFapsException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Store Resource that uses the Content Repository for Java Technology API (JCR).
 *
 * @author The eFaps Team
 * @version $Id: JCRStoreResource.java 6961 2011-08-18 17:11:27Z [email protected] $
 */
public class JCRStoreResource
    extends AbstractStoreResource
{
    /**
     * Name of the table the content is stored in.
     */
    public static final String TABLENAME_STORE = "T_CMGENSTOREJCR";

    /**
     * Name of the column the content is stored in.
     */
    public static final String COLNAME_IDENTIFIER = "IDENTIFIER";

    /**
     * Logging instance used in this class.
     */
    private static final Logger LOG  = LoggerFactory.getLogger(JCRStoreResource.class);

    /**
     * Property Name to define if the type if is used to define a sub
     * directory.
     */
    private static final String PROPERTY_WORKSPACENAME = "JCRWorkSpaceName";

    /**
     * Property Name to define if the file will be deleted on deletion of the related object.
     */
    private static final String PROPERTY_ENABLEDELETION = "JCREnableDeletion";

    /**
     * Property Name to define if the file will be deleted on deletion of the related object.
     */
    private static final String PROPERTY_USERNAME = "JCRUserName";

    /**
     * Property Name to define if the file will be deleted on deletion of the related object.
     */
    private static final String PROPERTY_PASSWORD = "JCRPassword";


    /**
     * The repository for this JCR Store Resource.
     */
    private Repository repository;

    /**
     * Identifier for the node to be accessed.
     */
    private String identifier;

    /**
     * Session for JCR access.
     */
    private Session session;

    /**
     * {@inheritDoc}
     */
    @Override
    public void initialize(final Instance _instance,
                           final Store _store)
        throws EFapsException
    {
        super.initialize(_instance, _store);
        try {
            final InitialContext ctx = new InitialContext();
            this.repository = (Repository) ctx.lookup(getStore().getProperty(Store.PROPERTY_JNDINAME));
            if (JCRStoreResource.LOG.isDebugEnabled()) {
                final String name = this.repository.getDescriptor(Repository.REP_NAME_DESC);
                JCRStoreResource.LOG.debug("Successfully retrieved '%s' repository from JNDI", new Object[]{ name });
            }
            String username = getProperties().get(JCRStoreResource.PROPERTY_USERNAME);
            if (username == null) {
                username = Context.getThreadContext().getPerson().getName();
            }
            String passwd = getProperties().get(JCRStoreResource.PROPERTY_PASSWORD);
            if (passwd == null) {
                passwd = "efaps";
            }
            this.session = this.repository.login(new SimpleCredentials(username, passwd.toCharArray()),
                            getProperties().get(JCRStoreResource.PROPERTY_WORKSPACENAME));
        } catch (final NamingException e) {
            throw new EFapsException(JCRStoreResource.class, "initialize.NamingException", e);
        } catch (final LoginException e) {
            throw new EFapsException(JCRStoreResource.class, "initialize.LoginException", e);
        } catch (final NoSuchWorkspaceException e) {
            throw new EFapsException(JCRStoreResource.class, "initialize.NoSuchWorkspaceException", e);
        } catch (final RepositoryException e) {
            throw new EFapsException(JCRStoreResource.class, "initialize.RepositoryException", e);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected int add2Select(final SQLSelect _select)
    {
        _select.column(2, "ID").column(2, JCRStoreResource.COLNAME_IDENTIFIER)
            .leftJoin(JCRStoreResource.TABLENAME_STORE, 2, "ID", 0, "ID");
        return 1;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void getAdditionalInfo(final ResultSet _rs)
        throws SQLException
    {
        final String identiferTmp = _rs.getString(6);
        if (identiferTmp != null && !identiferTmp.isEmpty()) {
            this.identifier = identiferTmp.trim();
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void insertDefaults()
        throws EFapsException
    {
        super.insertDefaults();
        if (!getExist()[1] && getGeneralID() != null) {
            try {
                final ConnectionResource res = Context.getThreadContext().getConnectionResource();
                final Connection con = res.getConnection();
                Context.getDbType().newInsert(JCRStoreResource.TABLENAME_STORE, "ID", false)
                                .column("ID", getGeneralID())
                                .column(JCRStoreResource.COLNAME_IDENTIFIER, "NEW")
                                .execute(con);
                res.commit();
            } catch (final SQLException e) {
                throw new EFapsException(JCRStoreResource.class, "insertDefaults", e);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public long write(final InputStream _in,
                     final long _size,
                     final String _fileName)
        throws EFapsException
    {
        final JCRBinary bin = new JCRBinary(_in);
        long size = _size;
        try {
            final Node rootNode = this.session.getRootNode();
            final Node fileNode = rootNode.addNode(getInstance().getOid(), NodeType.NT_FILE);
            final Node resNode = fileNode.addNode(Property.JCR_CONTENT, NodeType.NT_RESOURCE);
            resNode.setProperty(Property.JCR_DATA, bin);
            resNode.setProperty(Property.JCR_LAST_MODIFIED, Calendar.getInstance());
            resNode.setProperty(Property.JCR_LAST_MODIFIED_BY, Context.getThreadContext().getPerson().getName());
            setIdentifer(fileNode.getIdentifier());
            // if size is unkown!
            if (size < 0)  {
                final byte[] buffer = new byte[1024];
                int length = 1;
                size = 0;
                final OutputStream out = new ByteArrayOutputStream();
                while (length > 0)  {
                    length = _in.read(buffer);
                    if (length > 0)  {
                        out.write(buffer, 0, length);
                        size += length;
                    }
                }
            }
        } catch (final RepositoryException e) {
            throw new EFapsException(JCRStoreResource.class, "write.RepositoryException", e);
        } catch (final IOException e) {
            throw new EFapsException(JCRStoreResource.class, "write.IOException", e);
        }
        setFileInfo(_fileName, size);
        return size;
    }

    /**
     * Set the identifier in the eFaps DataBase.
     * @param _identifier   identifer to set
     * @throws EFapsException on error
     */
    protected void setIdentifer(final String _identifier)
        throws EFapsException
    {
        if (!_identifier.equals(this.identifier)) {

            ConnectionResource res = null;
            try {
                res = Context.getThreadContext().getConnectionResource();

                final StringBuffer cmd = new StringBuffer().append("update ")
                                .append(JCRStoreResource.TABLENAME_STORE).append(" set ")
                                .append(JCRStoreResource.COLNAME_IDENTIFIER).append("=? ")
                                .append("where ID =").append(getGeneralID());

                final PreparedStatement stmt = res.getConnection().prepareStatement(cmd.toString());
                try {
                    stmt.setString(1, _identifier);
                    stmt.execute();
                } finally {
                    stmt.close();
                }
                res.commit();
                this.identifier = _identifier;
            } catch (final EFapsException e) {
                res.abort();
                throw e;
            } catch (final SQLException e) {
                res.abort();
                throw new EFapsException(JDBCStoreResource.class, "write.SQLException", e);
            }
        }
    }

    /**
     * A JCR Store resource does not use compression from eFaps Side.
     * @return Compress.NONE
     */
    @Override
    protected Compress getCompress()
    {
        return Compress.NONE;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public InputStream read()
        throws EFapsException
    {
        InputStream input = null;
        try {
            final Node fileNode = this.session.getNodeByIdentifier(this.identifier);
            final Node resNode = fileNode.getNode(Property.JCR_CONTENT);
            final Property data = resNode.getProperty(Property.JCR_DATA);
            final Binary bin = data.getBinary();
            input = new JCRStoreResourceInputStream(this, bin);
        } catch (final RepositoryException e) {
            throw new EFapsException(JCRStoreResource.class, "read.RepositoryException", e);
        } catch (final IOException e) {
            throw new EFapsException(JCRStoreResource.class, "read.IOException", e);
        }
        return input;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void delete()
        throws EFapsException
    {
        if ("TRUE".equalsIgnoreCase(getProperties().get(JCRStoreResource.PROPERTY_ENABLEDELETION))) {
            try {
                final Node fileNode = this.session.getNodeByIdentifier(this.identifier);
                fileNode.remove();
            } catch (final RepositoryException e) {
                throw new EFapsException(JCRStoreResource.class, "delete.RepositoryException", e);
            }
        }
    }

    /**
     * The method is called from the transaction manager if the complete
     * transaction is completed.
* * @param _xid global transaction identifier (not used, because each * file with the file id gets a new VFS store resource * instance) * @param _onePhase true if it is a one phase commitment transaction * (not used) * @throws XAException if any exception occurs (catch on * {@link java.lang.Throwable}) */ @Override public void commit(final Xid _xid, final boolean _onePhase) throws XAException { try { if (this.session.hasPendingChanges()) { this.session.save(); } this.session.logout(); } catch (final AccessDeniedException e) { throw new XAException("AccessDeniedException"); } catch (final ItemExistsException e) { throw new XAException("ItemExistsException"); } catch (final ReferentialIntegrityException e) { throw new XAException("ReferentialIntegrityException"); } catch (final ConstraintViolationException e) { throw new XAException("AccessDeniedException"); } catch (final InvalidItemStateException e) { throw new XAException("InvalidItemStateException"); } catch (final VersionException e) { throw new XAException("VersionException"); } catch (final LockException e) { throw new XAException(XAException.XA_RBDEADLOCK); } catch (final NoSuchNodeTypeException e) { throw new XAException("NoSuchNodeTypeException"); } catch (final RepositoryException e) { throw new XAException("RepositoryException"); } } /** * Tells the resource manager to forget about a heuristically completed * transaction branch. * * @param _xid global transaction identifier (not used, because each file * with the file id gets a new VFS store resource instance) */ @Override public void forget(final Xid _xid) { if (JCRStoreResource.LOG.isDebugEnabled()) { JCRStoreResource.LOG.debug("forget (xid = " + _xid + ")"); } } /** * Obtains the current transaction timeout value set for this XAResource * instance. * * @return always 0 */ public int getTransactionTimeout() { if (JCRStoreResource.LOG.isDebugEnabled()) { JCRStoreResource.LOG.debug("getTransactionTimeout"); } return 0; } /** * Ask the resource manager to prepare for a transaction commit of the * transaction specified in xid (used for 2-phase commits). * * @param _xid Xid * @return always 0, because not 2 phase commit */ public int prepare(final Xid _xid) { if (JCRStoreResource.LOG.isDebugEnabled()) { JCRStoreResource.LOG.debug("prepare (xid=" + _xid + ")"); } return 0; } /** * Obtains a list of prepared transaction branches from a resource manager. * * @param _flag flag * @return always null */ public Xid[] recover(final int _flag) { if (JCRStoreResource.LOG.isDebugEnabled()) { JCRStoreResource.LOG.debug("recover (flag = " + _flag + ")"); } return null; } /** * On rollback no save is send to the session.. * * @param _xid global transaction identifier (not used, because each * file with the file id gets a new VFS store resource * instance) * @throws XAException if any exception occurs (catch on * {@link java.lang.Throwable}) */ @Override public void rollback(final Xid _xid) throws XAException { this.session.logout(); } /** * Sets the current transaction timeout value for this XAResource instance. * * @param _seconds number of seconds * @return always true */ public boolean setTransactionTimeout(final int _seconds) { if (JCRStoreResource.LOG.isDebugEnabled()) { JCRStoreResource.LOG.debug("setTransactionTimeout (seconds = " + _seconds + ")"); } return true; } /** * Implementation of Binary from JCR. */ private static class JCRBinary implements Binary { /** * The InpuStrema this Binary belongs to. */ private InputStream stream; /** * @param _in InputStream */ public JCRBinary(final InputStream _in) { this.stream = _in; } /** * Returns an {@link InputStream} representation of this value. Each call to * getStream() returns a new stream. The API consumer is * responsible for calling close() on the returned stream. *

* If {@link #dispose()} has been called on this Binary * object, then this method will throw the runtime exception * {@link java.lang.IllegalStateException}. * * @return A stream representation of this value. * @throws RepositoryException if an error occurs. */ @Override public InputStream getStream() throws RepositoryException { return this.stream; } /** * Reads successive bytes from the specified position in this * Binary into the passed byte array until either the byte * array is full or the end of the Binary is encountered. *

* If {@link #dispose()} has been called on this Binary * object, then this method will throw the runtime exception * {@link java.lang.IllegalStateException}. * * @param _b the buffer into which the data is read. * @param _position the position in this Binary from which to start reading * bytes. * @return the number of bytes read into the buffer, or -1 if there is no * more data because the end of the Binary has been reached. * @throws IOException if an I/O error occurs. * @throws NullPointerException if b is null. * @throws IllegalArgumentException if offset is negative. * @throws RepositoryException if another error occurs. */ @Override public int read(final byte[] _b, final long _position) throws IOException, RepositoryException { return this.stream.read(_b); } /** * Returns the size of this Binary value in bytes. *

* If {@link #dispose()} has been called on this Binary * object, then this method will throw the runtime exception * {@link java.lang.IllegalStateException}. * * @return the size of this value in bytes. * @throws RepositoryException if an error occurs. */ @Override public long getSize() throws RepositoryException { return 0; } /** * Releases all resources associated with this Binary object * and informs the repository that these resources may now be reclaimed. * An application should call this method when it is finished with the * Binary object. */ @Override public void dispose() { try { this.stream.close(); } catch (final IOException e) { JCRStoreResource.LOG.error("Error on disposal of inpustream.", e); } } } /** * ResourceInputStream implementation. */ private class JCRStoreResourceInputStream extends StoreResourceInputStream { /** * @param _store Strore this InputStream belongs to * @param _bin Binary * @throws IOException on error * @throws RepositoryException on error */ protected JCRStoreResourceInputStream(final AbstractStoreResource _store, final Binary _bin) throws IOException, RepositoryException { super(_store, _bin.getStream()); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy