org.artificer.repository.jcr.JCRArtifactPersister.java Source code

Java tutorial

Introduction

Here is the source code for org.artificer.repository.jcr.JCRArtifactPersister.java

Source

/*
 * Copyright 2013 JBoss Inc
 *
 * 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 org.artificer.repository.jcr;

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.artificer.common.ArtifactContent;
import org.artificer.common.ArtifactType;
import org.artificer.common.ArtificerConfig;
import org.artificer.common.ArtificerException;
import org.artificer.common.ArtificerModelUtils;
import org.artificer.common.audit.AuditEntryTypes;
import org.artificer.common.audit.AuditItemTypes;
import org.artificer.common.error.ArtificerConflictException;
import org.artificer.common.error.ArtificerServerException;
import org.artificer.common.visitors.ArtifactVisitorHelper;
import org.artificer.integration.ExtensionFactory;
import org.artificer.integration.artifactbuilder.ArtifactBuilder;
import org.artificer.integration.artifactbuilder.RelationshipContext;
import org.artificer.repository.jcr.audit.ArtifactJCRNodeDiff;
import org.artificer.repository.jcr.audit.ArtifactJCRNodeDiffer;
import org.artificer.repository.jcr.i18n.Messages;
import org.artificer.repository.jcr.mapper.ArtifactToJCRNodeVisitor;
import org.artificer.repository.jcr.util.JCRUtils;
import org.oasis_open.docs.s_ramp.ns.s_ramp_v1.BaseArtifactType;
import org.oasis_open.docs.s_ramp.ns.s_ramp_v1.DocumentArtifactType;
import org.oasis_open.docs.s_ramp.ns.s_ramp_v1.ExtendedArtifactType;
import org.oasis_open.docs.s_ramp.ns.s_ramp_v1.ExtendedDocument;
import org.oasis_open.docs.s_ramp.ns.s_ramp_v1.XmlDocument;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.jcr.Binary;
import javax.jcr.ItemExistsException;
import javax.jcr.Node;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.ValueFormatException;
import javax.jcr.lock.LockException;
import javax.jcr.nodetype.ConstraintViolationException;
import javax.jcr.version.VersionException;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;
import java.util.UUID;

/**
 * Helper class - breaks up the work of persisting an artifact into composable phases.  The phases are necessary
 * to simultaneously support single and batch actions.  They also separate saving the artifacts and their relationships,
 * allowing relationship resolution to query for all available artifacts (including the new ones) in the repo.
 *
 * @author Brett Meyer
 * @author eric.wittmann@redhat.com
 */
public final class JCRArtifactPersister {

    private static Logger log = LoggerFactory.getLogger(JCRArtifactPersister.class);

    private final BaseArtifactType primaryArtifact;
    private final ArtifactContent artifactContent;
    private final List<ArtifactBuilder> artifactBuilders;
    private final ClassificationHelper classificationHelper;
    private final Session session;
    private final ArtifactToJCRNodeVisitor.JCRReferenceFactory referenceFactory;

    private Node primaryArtifactNode;
    private List<BaseArtifactType> derivedArtifacts;

    public JCRArtifactPersister(BaseArtifactType primaryArtifact, ArtifactContent artifactContent,
            ClassificationHelper classificationHelper, Session session) throws Exception {
        this(primaryArtifact, artifactContent, classificationHelper, new JCRReferenceFactoryImpl(session), session);
    }

    public JCRArtifactPersister(BaseArtifactType primaryArtifact, ArtifactContent artifactContent,
            ClassificationHelper classificationHelper,
            ArtifactToJCRNodeVisitor.JCRReferenceFactory referenceFactory, Session session) throws Exception {
        this.primaryArtifact = primaryArtifact;
        this.artifactContent = artifactContent;
        this.classificationHelper = classificationHelper;
        this.session = session;
        artifactBuilders = ExtensionFactory.createArtifactBuilders(primaryArtifact, artifactContent);
        this.referenceFactory = referenceFactory;
    }

    public void persistArtifact() throws Exception {
        if (StringUtils.isBlank(primaryArtifact.getUuid())) {
            primaryArtifact.setUuid(UUID.randomUUID().toString());
        }

        runArtifactBuilders();

        primaryArtifactNode = persistPrimaryArtifact();
        referenceFactory.trackNode(primaryArtifact.getUuid(), primaryArtifactNode);

        persistDerivedArtifacts();
    }

    public void updateArtifactContent(Node primaryArtifactNode) throws Exception {
        this.primaryArtifactNode = primaryArtifactNode;
        ArtifactType artifactType = ArtifactType.valueOf(primaryArtifact);

        runArtifactBuilders();

        JCRUtils.uploadFile(session, primaryArtifactNode.getPath(), artifactContent.getInputStream(), true);
        JCRUtils.setArtifactContentMimeType(primaryArtifactNode, artifactType.getMimeType());
        persistDocumentProperties(primaryArtifactNode, artifactType);

        // Update the JCR node with any properties included in the meta-data
        ArtifactToJCRNodeVisitor visitor = new ArtifactToJCRNodeVisitor(artifactType, primaryArtifactNode,
                referenceFactory, classificationHelper);
        visitor.setProcessRelationships(false);
        ArtifactVisitorHelper.visitArtifact(visitor, primaryArtifact);
        visitor.throwError();

        persistDerivedArtifacts();
    }

    public void persistArtifactRelationships() throws Exception {
        if (ArtificerModelUtils.isDocumentArtifact(primaryArtifact)) {
            RelationshipContext relationshipContext = new JCRRelationshipContext(session);
            for (ArtifactBuilder artifactBuilder : artifactBuilders) {
                artifactBuilder.buildRelationships(relationshipContext);
            }

            persistDerivedArtifactsRelationships();
        }

        persistPrimaryArtifactRelationships();
    }

    private void runArtifactBuilders() throws Exception {
        // NOTE: The artifact builders *must* be run *before* persisting the primary artifact, and *must* be done
        // regardless if it's a doc artifact.  Some builders are responsible for setting custom properties on the
        // primary artifact.  See JavaClassArtifactBuilder as an example.
        derivedArtifacts = new ArrayList<BaseArtifactType>();
        for (ArtifactBuilder artifactBuilder : artifactBuilders) {
            artifactBuilder.buildArtifacts(primaryArtifact, artifactContent);
            derivedArtifacts.addAll(artifactBuilder.getDerivedArtifacts());
        }
    }

    private Node persistPrimaryArtifact() throws Exception {
        String uuid = primaryArtifact.getUuid();
        ArtifactType artifactType = ArtifactType.valueOf(primaryArtifact);
        String name = primaryArtifact.getName();
        String artifactPath = MapToJCRPath.getArtifactPath(uuid);

        log.debug(Messages.i18n.format("UPLOADING_TO_JCR", name));

        Node artifactNode = null;
        try {
            boolean isDocumentArtifact = ArtificerModelUtils.isDocumentArtifact(primaryArtifact);
            if (!isDocumentArtifact) {
                artifactNode = JCRUtils.createNode(session.getRootNode(), artifactPath, JCRConstants.NT_FOLDER,
                        JCRConstants.SRAMP_NON_DOCUMENT_TYPE);
            } else {
                // Some versions of ModeShape do not allow 'null' Binary values, so we must give a valid IS.
                InputStream is = artifactContent == null ? new ByteArrayInputStream(new byte[0])
                        : artifactContent.getInputStream();
                artifactNode = JCRUtils.uploadFile(session, artifactPath, is, false);
                JCRUtils.setArtifactContentMimeType(artifactNode, artifactType.getMimeType());
            }
        } catch (ItemExistsException e) {
            throw ArtificerConflictException.artifactConflict(uuid);
        }

        String jcrMixinName = artifactType.getArtifactType().getApiType().value();
        jcrMixinName = JCRConstants.SRAMP_ + StringUtils.uncapitalize(jcrMixinName);
        artifactNode.addMixin(jcrMixinName);
        // BaseArtifactType
        artifactNode.setProperty(JCRConstants.SRAMP_UUID, uuid);
        artifactNode.setProperty(JCRConstants.SRAMP_ARTIFACT_MODEL, artifactType.getArtifactType().getModel());
        artifactNode.setProperty(JCRConstants.SRAMP_ARTIFACT_TYPE, artifactType.getArtifactType().getType());
        // Extended
        if (ExtendedArtifactType.class.isAssignableFrom(artifactType.getArtifactType().getTypeClass())) {
            artifactNode.setProperty(JCRConstants.SRAMP_EXTENDED_TYPE, artifactType.getExtendedType());
        }
        // Extended Document
        if (ExtendedDocument.class.isAssignableFrom(artifactType.getArtifactType().getTypeClass())) {
            artifactNode.setProperty(JCRConstants.SRAMP_EXTENDED_TYPE, artifactType.getExtendedType());
        }

        persistDocumentProperties(artifactNode, artifactType);

        // Update the JCR node with any properties included in the meta-data
        ArtifactToJCRNodeVisitor visitor = new ArtifactToJCRNodeVisitor(artifactType, artifactNode,
                referenceFactory, classificationHelper);
        visitor.setProcessRelationships(false);
        ArtifactVisitorHelper.visitArtifact(visitor, primaryArtifact);
        visitor.throwError();

        log.debug(Messages.i18n.format("SAVED_JCR_NODE", name, uuid));
        if (ArtificerConfig.isAuditingEnabled()) {
            auditCreateArtifact(artifactNode);
        }

        return artifactNode;
    }

    private void persistDocumentProperties(Node artifactNode, ArtifactType artifactType) throws Exception {
        // Document
        if (DocumentArtifactType.class.isAssignableFrom(artifactType.getArtifactType().getTypeClass())) {
            artifactNode.setProperty(JCRConstants.SRAMP_CONTENT_TYPE, artifactType.getMimeType());
            artifactNode.setProperty(JCRConstants.SRAMP_CONTENT_SIZE,
                    artifactNode.getProperty(JCRConstants.JCR_CONTENT_DATA).getLength());
            Binary binary = artifactNode.getProperty(JCRConstants.JCR_CONTENT_DATA).getBinary();
            if (binary != null) {
                String sha1Hash;
                if (binary instanceof org.modeshape.jcr.api.Binary) {
                    // Optimization -- reuse the hash generated by ModeShape
                    sha1Hash = ((org.modeshape.jcr.api.Binary) binary).getHexHash();
                } else {
                    InputStream inputStream = null;
                    try {
                        inputStream = binary.getStream();
                        sha1Hash = DigestUtils.shaHex(inputStream);
                    } finally {
                        if (inputStream != null) {
                            IOUtils.closeQuietly(inputStream);
                        }
                    }
                }
                artifactNode.setProperty(JCRConstants.SRAMP_CONTENT_HASH, sha1Hash);
            }
        }
        // XMLDocument
        if (primaryArtifact instanceof XmlDocument) {
            artifactNode.setProperty(JCRConstants.SRAMP_CONTENT_ENCODING,
                    ((XmlDocument) primaryArtifact).getContentEncoding());
        }
    }

    private void persistPrimaryArtifactRelationships() throws ArtificerException {
        try {
            // Update the JCR node again, this time with any relationships resolved by the linker
            ArtifactToJCRNodeVisitor visitor = new ArtifactToJCRNodeVisitor(ArtifactType.valueOf(primaryArtifact),
                    primaryArtifactNode, referenceFactory, classificationHelper);
            ArtifactVisitorHelper.visitArtifact(visitor, primaryArtifact);
            visitor.throwError();
        } catch (ArtificerException e) {
            throw e;
        } catch (Throwable t) {
            throw new ArtificerServerException(t);
        }
    }

    private void persistDerivedArtifacts() throws ArtificerException {
        try {
            // Persist each of the derived nodes
            for (BaseArtifactType derivedArtifact : derivedArtifacts) {
                if (derivedArtifact.getUuid() == null) {
                    throw new ArtificerServerException(
                            Messages.i18n.format("MISSING_DERIVED_UUID", derivedArtifact.getName()));
                }
                ArtifactType derivedArtifactType = ArtifactType.valueOf(derivedArtifact);
                String jcrMixinName = derivedArtifactType.getArtifactType().getApiType().value();
                if (derivedArtifactType.isExtendedType()) {
                    jcrMixinName = "extendedDerivedArtifactType";
                    derivedArtifactType.setExtendedDerivedType(true);
                }
                jcrMixinName = JCRConstants.SRAMP_ + StringUtils.uncapitalize(jcrMixinName);

                // Create the JCR node and set some basic properties first.
                String nodeName = derivedArtifact.getUuid();
                Node derivedArtifactNode = primaryArtifactNode.addNode(nodeName,
                        JCRConstants.SRAMP_DERIVED_PRIMARY_TYPE);
                derivedArtifactNode.addMixin(jcrMixinName);
                derivedArtifactNode.setProperty(JCRConstants.SRAMP_UUID, derivedArtifact.getUuid());
                derivedArtifactNode.setProperty(JCRConstants.SRAMP_ARTIFACT_MODEL,
                        derivedArtifactType.getArtifactType().getModel());
                derivedArtifactNode.setProperty(JCRConstants.SRAMP_ARTIFACT_TYPE,
                        derivedArtifactType.getArtifactType().getType());
                // Extended
                if (ExtendedArtifactType.class
                        .isAssignableFrom(derivedArtifactType.getArtifactType().getTypeClass())) {
                    // read the encoding from the header
                    derivedArtifactNode.setProperty(JCRConstants.SRAMP_EXTENDED_TYPE,
                            derivedArtifactType.getExtendedType());
                }

                // It's definitely derived.
                derivedArtifactNode.setProperty(JCRConstants.SRAMP_DERIVED, true);

                // Create the visitor that will be used to write the artifact information to the JCR node
                ArtifactToJCRNodeVisitor visitor = new ArtifactToJCRNodeVisitor(derivedArtifactType,
                        derivedArtifactNode, null, classificationHelper);
                visitor.setProcessRelationships(false);
                ArtifactVisitorHelper.visitArtifact(visitor, derivedArtifact);
                visitor.throwError();

                // Audit the create event for the derived node
                if (ArtificerConfig.isAuditingEnabled() && ArtificerConfig.isDerivedArtifactAuditingEnabled()) {
                    auditCreateArtifact(derivedArtifactNode);
                }

                log.debug(Messages.i18n.format("SAVED_DERIVED_ARTY_TO_JCR", derivedArtifact.getName(),
                        derivedArtifact.getUuid()));

                referenceFactory.trackNode(derivedArtifact.getUuid(), derivedArtifactNode);
            }
        } catch (ArtificerException e) {
            throw e;
        } catch (Throwable t) {
            throw new ArtificerServerException(t);
        }
    }

    private void persistDerivedArtifactsRelationships() throws ArtificerException {
        try {
            // Persist each of the derived nodes
            for (BaseArtifactType derivedArtifact : derivedArtifacts) {
                ArtifactType derivedArtifactType = ArtifactType.valueOf(derivedArtifact);
                if (derivedArtifactType.isExtendedType()) {
                    derivedArtifactType.setExtendedDerivedType(true);
                }
                Node derivedArtifactNode = primaryArtifactNode.getNode(derivedArtifact.getUuid());

                // Create the visitor that will be used to write the artifact information to the JCR node
                ArtifactToJCRNodeVisitor visitor = new ArtifactToJCRNodeVisitor(derivedArtifactType,
                        derivedArtifactNode, referenceFactory, classificationHelper);
                ArtifactVisitorHelper.visitArtifact(visitor, derivedArtifact);
                visitor.throwError();

                log.debug(Messages.i18n.format("SAVED_RELATIONSHIPS", derivedArtifact.getName()));
            }
        } catch (ArtificerException e) {
            throw e;
        } catch (Throwable t) {
            throw new ArtificerServerException(t);
        }
    }

    public Node getPrimaryArtifactNode() {
        return primaryArtifactNode;
    }

    /**
     * Saves binary content from the given JCR content (jcr:content) node to a temporary
     * file.
     * @param jcrContentNode
     * @throws Exception
     */
    public static File saveToTempFile(Node jcrContentNode) throws Exception {
        File file = File.createTempFile("artificer", ".jcr");
        Binary binary = null;
        InputStream content = null;
        OutputStream tempFileOS = null;

        try {
            binary = jcrContentNode.getProperty(JCRConstants.JCR_DATA).getBinary();
            content = binary.getStream();
            tempFileOS = new FileOutputStream(file);
            IOUtils.copy(content, tempFileOS);
        } finally {
            IOUtils.closeQuietly(content);
            IOUtils.closeQuietly(tempFileOS);
            if (binary != null)
                binary.dispose();
        }

        return file;
    }

    /**
     * Audits an artifact create event.  This will add an audit entry as a child of the
     * new artifact JCR node of type "artifact:add".  In addition, the initial state of
     * all properties, classifiers, and relationships will be recorded.
     * @param artifactNode
     * @throws RepositoryException
     */
    public static void auditCreateArtifact(Node artifactNode) throws RepositoryException {
        ArtifactJCRNodeDiffer differ = new ArtifactJCRNodeDiffer(artifactNode);
        Node auditEntryNode = createAuditEntryNode(artifactNode, AuditEntryTypes.ARTIFACT_ADD);
        Node propAddedNode = createAuditItemNode(auditEntryNode, AuditItemTypes.PROPERTY_ADDED);
        for (Entry<String, String> entry : differ.getProperties().entrySet()) {
            propAddedNode.setProperty(entry.getKey(), entry.getValue());
        }
        if (!differ.getClassifiers().isEmpty()) {
            Node classifierAddedNode = createAuditItemNode(auditEntryNode, AuditItemTypes.CLASSIFIERS_ADDED);
            int idx = 0;
            for (String classifier : differ.getClassifiers()) {
                classifierAddedNode.setProperty("classifier-" + idx++, classifier);
            }
        }
    }

    /**
     * Audits an artifact update event.  This will add an audit entry as a child of the
     * new artifact JCR node of type "artifact:update".  In addition, any changes to
     * properties, classifiers, or relationships will be added as audit items to the
     * audit entry.
     * @param artifactNode
     * @throws RepositoryException
     */
    public static void auditUpdateArtifact(ArtifactJCRNodeDiffer differ, Node artifactNode)
            throws RepositoryException {
        Node auditEntryNode = createAuditEntryNode(artifactNode, AuditEntryTypes.ARTIFACT_UPDATE);

        ArtifactJCRNodeDiff diff = differ.diff(artifactNode);
        if (!diff.getAddedProperties().isEmpty()) {
            Node propAddedNode = createAuditItemNode(auditEntryNode, AuditItemTypes.PROPERTY_ADDED);
            for (Entry<String, String> entry : diff.getAddedProperties().entrySet()) {
                propAddedNode.setProperty(entry.getKey(), entry.getValue());
            }
        }
        if (!diff.getUpdatedProperties().isEmpty()) {
            Node propChangedNode = createAuditItemNode(auditEntryNode, AuditItemTypes.PROPERTY_CHANGED);
            for (Entry<String, String> entry : diff.getUpdatedProperties().entrySet()) {
                propChangedNode.setProperty(entry.getKey(), entry.getValue());
            }
        }
        if (!diff.getDeletedProperties().isEmpty()) {
            Node propRemovedNode = createAuditItemNode(auditEntryNode, AuditItemTypes.PROPERTY_REMOVED);
            for (String propName : diff.getDeletedProperties()) {
                propRemovedNode.setProperty(propName, "");
            }
        }
        if (!diff.getAddedClassifiers().isEmpty()) {
            Node classifiersAddedNode = createAuditItemNode(auditEntryNode, AuditItemTypes.CLASSIFIERS_ADDED);
            int idx = 0;
            for (String classifier : diff.getAddedClassifiers()) {
                classifiersAddedNode.setProperty("classifier-" + idx++, classifier);
            }
        }
        if (!diff.getDeletedClassifiers().isEmpty()) {
            Node classifiersRemovedNode = createAuditItemNode(auditEntryNode, AuditItemTypes.CLASSIFIERS_REMOVED);
            int idx = 0;
            for (String classifier : diff.getDeletedClassifiers()) {
                classifiersRemovedNode.setProperty("classifier-" + idx++, classifier);
            }
        }
    }

    /**
     * Creates a JCR node for a single audit entry for a single artifact.  This is called when
     * recording audit information for an artifact.
     * @param artifactNode
     * @throws ValueFormatException
     * @throws VersionException
     * @throws LockException
     * @throws ConstraintViolationException
     * @throws RepositoryException
     */
    public static Node createAuditEntryNode(Node artifactNode, String type) throws ValueFormatException,
            VersionException, LockException, ConstraintViolationException, RepositoryException {
        String auditUuid = UUID.randomUUID().toString();
        Node auditEntryNode = artifactNode.addNode("audit:" + auditUuid, JCRConstants.SRAMP_AUDIT_ENTRY);

        auditEntryNode.setProperty("audit:uuid", auditUuid);
        auditEntryNode.setProperty("audit:sortId", System.currentTimeMillis());
        auditEntryNode.setProperty("audit:type", type);

        return auditEntryNode;
    }

    /**
     * Creates the audit item node as a child of the given audit entry.
     * @param auditEntryNode
     * @throws RepositoryException
     * @throws ConstraintViolationException
     * @throws LockException
     * @throws VersionException
     * @throws ValueFormatException
     */
    public static Node createAuditItemNode(Node auditEntryNode, String auditItemType) throws ValueFormatException,
            VersionException, LockException, ConstraintViolationException, RepositoryException {
        String auditItemNodeName = "audit:" + auditItemType.replace(':', '_');
        Node auditItemNode = auditEntryNode.addNode(auditItemNodeName, JCRConstants.SRAMP_AUDIT_ITEM);
        auditItemNode.setProperty("audit:type", auditItemType);
        return auditItemNode;
    }

}