Package org.jboss.dna.connector.store.jpa.model.basic

Source Code of org.jboss.dna.connector.store.jpa.model.basic.BasicRequestProcessor

/*
* JBoss DNA (http://www.jboss.org/dna)
* See the COPYRIGHT.txt file distributed with this work for information
* regarding copyright ownership.  Some portions may be licensed
* to Red Hat, Inc. under one or more contributor license agreements.
* See the AUTHORS.txt file in the distribution for a full listing of
* individual contributors.
*
* JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
* is licensed to you under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* JBoss DNA is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.dna.connector.store.jpa.model.basic;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import javax.persistence.NoResultException;
import javax.persistence.Query;
import net.jcip.annotations.Immutable;
import net.jcip.annotations.NotThreadSafe;
import org.jboss.dna.common.util.IoUtil;
import org.jboss.dna.common.util.Logger;
import org.jboss.dna.common.util.StringUtil;
import org.jboss.dna.connector.store.jpa.JpaConnectorI18n;
import org.jboss.dna.connector.store.jpa.model.common.NamespaceEntity;
import org.jboss.dna.connector.store.jpa.model.common.WorkspaceEntity;
import org.jboss.dna.connector.store.jpa.util.Namespaces;
import org.jboss.dna.connector.store.jpa.util.RequestProcessorCache;
import org.jboss.dna.connector.store.jpa.util.Serializer;
import org.jboss.dna.connector.store.jpa.util.Workspaces;
import org.jboss.dna.connector.store.jpa.util.Serializer.LargeValues;
import org.jboss.dna.graph.DnaLexicon;
import org.jboss.dna.graph.ExecutionContext;
import org.jboss.dna.graph.JcrLexicon;
import org.jboss.dna.graph.Location;
import org.jboss.dna.graph.property.Binary;
import org.jboss.dna.graph.property.Name;
import org.jboss.dna.graph.property.NameFactory;
import org.jboss.dna.graph.property.Path;
import org.jboss.dna.graph.property.PathFactory;
import org.jboss.dna.graph.property.PathNotFoundException;
import org.jboss.dna.graph.property.Property;
import org.jboss.dna.graph.property.PropertyType;
import org.jboss.dna.graph.property.Reference;
import org.jboss.dna.graph.property.ReferentialIntegrityException;
import org.jboss.dna.graph.property.UuidFactory;
import org.jboss.dna.graph.property.ValueFactories;
import org.jboss.dna.graph.property.ValueFactory;
import org.jboss.dna.graph.property.ValueFormatException;
import org.jboss.dna.graph.request.CloneWorkspaceRequest;
import org.jboss.dna.graph.request.CopyBranchRequest;
import org.jboss.dna.graph.request.CreateNodeRequest;
import org.jboss.dna.graph.request.CreateWorkspaceRequest;
import org.jboss.dna.graph.request.DeleteBranchRequest;
import org.jboss.dna.graph.request.DestroyWorkspaceRequest;
import org.jboss.dna.graph.request.GetWorkspacesRequest;
import org.jboss.dna.graph.request.InvalidRequestException;
import org.jboss.dna.graph.request.InvalidWorkspaceException;
import org.jboss.dna.graph.request.MoveBranchRequest;
import org.jboss.dna.graph.request.ReadAllChildrenRequest;
import org.jboss.dna.graph.request.ReadAllPropertiesRequest;
import org.jboss.dna.graph.request.ReadBlockOfChildrenRequest;
import org.jboss.dna.graph.request.ReadBranchRequest;
import org.jboss.dna.graph.request.ReadNextBlockOfChildrenRequest;
import org.jboss.dna.graph.request.ReadNodeRequest;
import org.jboss.dna.graph.request.ReadPropertyRequest;
import org.jboss.dna.graph.request.Request;
import org.jboss.dna.graph.request.UpdatePropertiesRequest;
import org.jboss.dna.graph.request.VerifyWorkspaceRequest;
import org.jboss.dna.graph.request.processor.RequestProcessor;

/**
* @author Randall Hauch
*/
@NotThreadSafe
public class BasicRequestProcessor extends RequestProcessor {

    protected final EntityManager entities;
    protected final ValueFactory<String> stringFactory;
    protected final PathFactory pathFactory;
    protected final NameFactory nameFactory;
    protected final UuidFactory uuidFactory;
    protected final Namespaces namespaces;
    protected final Workspaces workspaces;
    protected final UUID rootNodeUuid;
    protected final String rootNodeUuidString;
    protected final String nameOfDefaultWorkspace;
    protected final String[] predefinedWorkspaceNames;
    protected final boolean creatingWorkspacesAllowed;
    protected final Serializer serializer;
    protected final long largeValueMinimumSizeInBytes;
    protected final boolean compressData;
    protected final Logger logger;
    protected final RequestProcessorCache cache;
    protected final boolean enforceReferentialIntegrity;
    private final Set<Long> workspaceIdsWithChangedReferences = new HashSet<Long>();

    /**
     * @param sourceName
     * @param context
     * @param entityManager
     * @param rootNodeUuid
     * @param nameOfDefaultWorkspace
     * @param predefinedWorkspaceNames
     * @param largeValueMinimumSizeInBytes
     * @param creatingWorkspacesAllowed
     * @param compressData
     * @param enforceReferentialIntegrity
     */
    public BasicRequestProcessor( String sourceName,
                                  ExecutionContext context,
                                  EntityManager entityManager,
                                  UUID rootNodeUuid,
                                  String nameOfDefaultWorkspace,
                                  String[] predefinedWorkspaceNames,
                                  long largeValueMinimumSizeInBytes,
                                  boolean creatingWorkspacesAllowed,
                                  boolean compressData,
                                  boolean enforceReferentialIntegrity ) {
        super(sourceName, context);
        assert entityManager != null;
        assert rootNodeUuid != null;
        assert predefinedWorkspaceNames != null;
        this.entities = entityManager;
        ValueFactories valuesFactory = context.getValueFactories();
        this.stringFactory = valuesFactory.getStringFactory();
        this.pathFactory = valuesFactory.getPathFactory();
        this.nameFactory = valuesFactory.getNameFactory();
        this.uuidFactory = valuesFactory.getUuidFactory();
        this.namespaces = new Namespaces(entityManager);
        this.workspaces = new Workspaces(entityManager);
        this.rootNodeUuid = rootNodeUuid;
        this.rootNodeUuidString = this.rootNodeUuid.toString();
        this.nameOfDefaultWorkspace = nameOfDefaultWorkspace;
        this.creatingWorkspacesAllowed = creatingWorkspacesAllowed;
        this.largeValueMinimumSizeInBytes = largeValueMinimumSizeInBytes;
        this.compressData = compressData;
        this.enforceReferentialIntegrity = enforceReferentialIntegrity;
        this.serializer = new Serializer(context, true);
        this.logger = getExecutionContext().getLogger(getClass());
        this.cache = new RequestProcessorCache(this.pathFactory);
        this.predefinedWorkspaceNames = predefinedWorkspaceNames;

        // Start the transaction ...
        this.entities.getTransaction().begin();
    }

    /**
     * {@inheritDoc}
     *
     * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.CreateNodeRequest)
     */
    @Override
    public void process( CreateNodeRequest request ) {
        logger.trace(request.toString());
        Location actualLocation = null;
        try {
            // Find the workspace ...
            WorkspaceEntity workspace = getExistingWorkspace(request.inWorkspace(), request);
            if (workspace == null) return;
            Long workspaceId = workspace.getId();
            assert workspaceId != null;

            // Create nodes have to be defined via a path ...
            Location parentLocation = request.under();
            ActualLocation actual = getActualLocation(workspace.getId(), parentLocation);
            String parentUuidString = actual.uuid;
            assert parentUuidString != null;

            // We need to look for an existing UUID property in the request,
            // so since we have to iterate through the properties, go ahead an serialize them right away ...
            String uuidString = null;
            for (Property property : request.properties()) {
                if (property.getName().equals(DnaLexicon.UUID)) {
                    uuidString = stringFactory.create(property.getFirstValue());
                    break;
                }
            }
            if (uuidString == null) uuidString = UUID.randomUUID().toString();
            assert uuidString != null;
            createProperties(workspaceId, uuidString, request.properties());

            // Find or create the namespace for the child ...
            Name childName = request.named();
            String childNsUri = childName.getNamespaceUri();
            NamespaceEntity ns = namespaces.get(childNsUri, true);
            assert ns != null;
            final Path parentPath = actual.location.getPath();
            assert parentPath != null;

            // Figure out the next SNS index and index-in-parent for this new child ...
            actualLocation = addNewChild(workspaceId, actual, uuidString, childName, true);

            // Since we've just created this node, we know about all the children (actually, there are none).
            cache.setAllChildren(workspace.getId(), actualLocation.getPath(), new LinkedList<Location>());

            // Flush the entities ...
            // entities.flush();

        } catch (Throwable e) { // Includes PathNotFoundException
            request.setError(e);
            logger.trace(e, "Problem " + request);
            return;
        }
        request.setActualLocationOfNode(actualLocation);
    }

    /**
     * Create a new child with the supplied UUID and name under the supplied parent. If the parent is null, then the child will be
     * the root node.
     *
     * @param workspaceId the ID of the workspace in which the child is to be created
     * @param parent the actual location of the parent, or null if the child is to be the root of the workspace
     * @param childUuid the UUID of the child
     * @param childName the name of the child
     * @param allowSameNameChildrenInNewNode
     * @return the location of the new child
     */
    protected Location addNewChild( Long workspaceId,
                                    ActualLocation parent,
                                    String childUuid,
                                    Name childName,
                                    boolean allowSameNameChildrenInNewNode ) {
        int nextSnsIndex = 1; // SNS index is 1-based
        int nextIndexInParent = 0; // index-in-parent is 0-based
        String childNsUri = childName.getNamespaceUri();
        NamespaceEntity ns = namespaces.get(childNsUri, true);
        assert ns != null;

        // If the parent is null, the create a root node ...
        Path parentPath = null;
        String parentUuid = null;
        ChildEntity parentEntity = null;
        if (parent == null) {
            return Location.create(pathFactory.createRootPath(), UUID.fromString(childUuid));
        }
        parentPath = parent.location.getPath();
        parentUuid = parent.uuid;
        parentEntity = parent.childEntity; // may be null

        assert workspaceId != null;

        ChildId id = new ChildId(workspaceId, parentUuid, childUuid);
        ChildEntity entity = null;

        // Look in the cache for the children of the parent node.
        LinkedList<Location> childrenOfParent = cache.getAllChildren(workspaceId, parentPath);

        // Now create the entity ...
        if (parentEntity == null || parentEntity.getAllowsSameNameChildren()) {
            // The parent DOES allow same-name-siblings, so we need to find the SNS index ...

            if (childrenOfParent != null) {
                // The cache had the complete list of children for the parent node, which means
                // we know about all of the children and can walk the children to figure out the next indexes.
                nextIndexInParent = childrenOfParent.size();
                if (nextIndexInParent > 1) {
                    // Since we want the last indexes, process the list backwards ...
                    ListIterator<Location> iter = childrenOfParent.listIterator(childrenOfParent.size());
                    while (iter.hasPrevious()) {
                        Location existing = iter.previous();
                        Path.Segment segment = existing.getPath().getLastSegment();
                        if (!segment.getName().equals(childName)) continue;
                        // Otherwise the name matched, so get the indexes ...
                        nextSnsIndex = segment.getIndex() + 1;
                    }
                }
            } else {
                // The cache did not have the complete list of children for the parent node,
                // so we need to look the values up by querying the database ...

                // Find the largest SNS index in the existing ChildEntity objects with the same name ...
                String childLocalName = childName.getLocalName();
                Query query = entities.createNamedQuery("ChildEntity.findMaximumSnsIndex");
                query.setParameter("workspaceId", workspaceId);
                query.setParameter("parentUuid", parentUuid);
                query.setParameter("ns", ns.getId());
                query.setParameter("childName", childLocalName);
                try {
                    Integer result = (Integer)query.getSingleResult();
                    nextSnsIndex = result != null ? result + 1 : 1; // SNS index is 1-based
                } catch (NoResultException e) {
                }

                // Find the largest child index in the existing ChildEntity objects ...
                query = entities.createNamedQuery("ChildEntity.findMaximumChildIndex");
                query.setParameter("workspaceId", workspaceId);
                query.setParameter("parentUuid", parentUuid);
                try {
                    Integer result = (Integer)query.getSingleResult();
                    nextIndexInParent = result != null ? result + 1 : 0; // index-in-parent is 0-based
                } catch (NoResultException e) {
                }
            }

            // Create the new ChildEntity ...
            entity = new ChildEntity(id, nextIndexInParent, ns, childName.getLocalName(), nextSnsIndex);
        } else {
            // The parent does not allow same-name-siblings, so we only need to find the next index ...
            // Find the largest child index in the existing ChildEntity objects ...
            if (childrenOfParent != null) {
                // The cache had the complete list of children for the parent node, which means
                // we know about all of the children and can walk the children to figure out the next indexes.
                nextIndexInParent = childrenOfParent.size();
            } else {
                // We don't have the children cached, so we need to do a query ...
                Query query = entities.createNamedQuery("ChildEntity.findMaximumChildIndex");
                query.setParameter("workspaceId", workspaceId);
                query.setParameter("parentUuid", parentUuid);
                try {
                    Integer result = (Integer)query.getSingleResult();
                    nextIndexInParent = result != null ? result + 1 : 0; // index-in-parent is 0-based
                } catch (NoResultException e) {
                }
            }

            // Create the new child entity ...
            entity = new ChildEntity(id, nextIndexInParent, ns, childName.getLocalName(), 1);
        }

        // Persist the new child entity ...
        entity.setAllowsSameNameChildren(allowSameNameChildrenInNewNode);
        entities.persist(entity);

        // Set the actual path, regardless of the supplied path...
        Path path = pathFactory.create(parentPath, childName, nextSnsIndex);
        Location actualLocation = Location.create(path, UUID.fromString(childUuid));

        // Finally, update the cache with the information we know ...
        if (childrenOfParent != null) {
            // Add to the cached list of children ...
            childrenOfParent.add(actualLocation);
        } else {
            cache.setAllChildren(workspaceId, parentPath, null);
        }
        return actualLocation;
    }

    protected class NextChildIndexes {
        protected final int nextIndexInParent;
        protected final int nextSnsIndex;

        protected NextChildIndexes( int nextIndexInParent,
                                    int nextSnsIndex ) {
            this.nextIndexInParent = nextIndexInParent;
            this.nextSnsIndex = nextSnsIndex;
        }
    }

    /**
     * {@inheritDoc}
     *
     * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.ReadNodeRequest)
     */
    @Override
    public void process( ReadNodeRequest request ) {
        logger.trace(request.toString());
        Location actualLocation = null;
        try {
            // Find the workspace ...
            WorkspaceEntity workspace = getExistingWorkspace(request.inWorkspace(), request);
            if (workspace == null) return;
            Long workspaceId = workspace.getId();
            assert workspaceId != null;

            Location location = request.at();
            ActualLocation actual = getActualLocation(workspaceId, location);
            String parentUuidString = actual.uuid;
            actualLocation = actual.location;

            // Record the UUID as a property, since it's not stored in the serialized properties...
            request.addProperty(actualLocation.getIdProperty(DnaLexicon.UUID));

            // Find the properties entity for this node ...
            Query query = entities.createNamedQuery("PropertiesEntity.findByUuid");
            query.setParameter("workspaceId", workspaceId);
            query.setParameter("uuid", parentUuidString);
            try {
                PropertiesEntity entity = (PropertiesEntity)query.getSingleResult();

                // Deserialize the properties ...
                boolean compressed = entity.isCompressed();
                Collection<Property> properties = new LinkedList<Property>();
                byte[] data = entity.getData();
                if (data != null) {
                    LargeValueSerializer largeValues = new LargeValueSerializer(entity);
                    ByteArrayInputStream bais = new ByteArrayInputStream(data);
                    InputStream is = compressed ? new GZIPInputStream(bais) : bais;
                    ObjectInputStream ois = new ObjectInputStream(is);
                    try {
                        serializer.deserializeAllProperties(ois, properties, largeValues);
                        for (Property property : properties) {
                            request.addProperty(property);
                        }
                    } finally {
                        ois.close();
                    }
                }

            } catch (NoResultException e) {
                // No properties, but that's okay...
            }

            // Get the children for this node ...
            for (Location childLocation : getAllChildren(workspaceId, actual)) {
                request.addChild(childLocation);
            }
        } catch (NoResultException e) {
            // there are no properties (probably not expected, but still okay) ...
        } catch (Throwable e) { // Includes PathNotFoundException
            request.setError(e);
            return;
        }
        if (actualLocation != null) request.setActualLocationOfNode(actualLocation);
        setCacheableInfo(request);
    }

    /**
     * {@inheritDoc}
     *
     * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.ReadAllChildrenRequest)
     */
    @Override
    public void process( ReadAllChildrenRequest request ) {
        logger.trace(request.toString());
        Location actualLocation = null;
        try {
            // Find the workspace ...
            WorkspaceEntity workspace = getExistingWorkspace(request.inWorkspace(), request);
            if (workspace == null) return;
            Long workspaceId = workspace.getId();
            assert workspaceId != null;

            Location location = request.of();
            ActualLocation actual = getActualLocation(workspaceId, location);
            actualLocation = actual.location;

            // Get the children for this node ...
            for (Location childLocation : getAllChildren(workspaceId, actual)) {
                request.addChild(childLocation);
            }
        } catch (NoResultException e) {
            // there are no properties (probably not expected, but still okay) ...
        } catch (Throwable e) { // Includes PathNotFoundException
            request.setError(e);
            return;
        }
        if (actualLocation != null) request.setActualLocationOfNode(actualLocation);
        setCacheableInfo(request);
    }

    /**
     * Utility method to obtain all of the children for a node, either from the cache (if all children are known to this
     * processor) or by querying the database (and caching the list of children).
     *
     * @param workspaceId the ID of the workspace; may not be null
     * @param parent the actual location of the parent node; may not be null
     * @return the list of child locations
     */
    protected LinkedList<Location> getAllChildren( Long workspaceId,
                                                   ActualLocation parent ) {
        assert parent != null;
        Path parentPath = parent.location.getPath();
        assert parentPath != null;
        LinkedList<Location> cachedChildren = cache.getAllChildren(workspaceId, parentPath);
        if (cachedChildren != null) {
            // The cache has all of the children for the node ...
            return cachedChildren;
        }

        // Not found in the cache, so query the database ...
        Query query = entities.createNamedQuery("ChildEntity.findAllUnderParent");
        query.setParameter("workspaceId", workspaceId);
        query.setParameter("parentUuidString", parent.uuid);
        LinkedList<Location> childLocations = new LinkedList<Location>();
        @SuppressWarnings( "unchecked" )
        List<ChildEntity> children = query.getResultList();
        for (ChildEntity child : children) {
            String namespaceUri = child.getChildNamespace().getUri();
            String localName = child.getChildName();
            Name childName = nameFactory.create(namespaceUri, localName);
            int sns = child.getSameNameSiblingIndex();
            Path childPath = pathFactory.create(parentPath, childName, sns);
            String childUuidString = child.getId().getChildUuidString();
            Location childLocation = Location.create(childPath, UUID.fromString(childUuidString));
            childLocations.add(childLocation);
        }
        // Update the cache ...
        cache.setAllChildren(workspaceId, parentPath, childLocations);
        return childLocations;
    }

    /**
     * {@inheritDoc}
     *
     * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.ReadBlockOfChildrenRequest)
     */
    @Override
    public void process( ReadBlockOfChildrenRequest request ) {
        logger.trace(request.toString());
        Location actualLocation = null;
        final int startingIndex = request.startingAtIndex();
        try {
            // Find the workspace ...
            WorkspaceEntity workspace = getExistingWorkspace(request.inWorkspace(), request);
            if (workspace == null) return;
            Long workspaceId = workspace.getId();
            assert workspaceId != null;

            Location parentLocation = request.of();
            ActualLocation actualParent = getActualLocation(workspaceId, parentLocation);
            actualLocation = actualParent.location;

            Path parentPath = actualParent.location.getPath();
            assert parentPath != null;
            LinkedList<Location> cachedChildren = cache.getAllChildren(workspaceId, parentPath);
            if (cachedChildren != null) {
                // The cache has all of the children for the node ...
                if (startingIndex < cachedChildren.size()) {
                    ListIterator<Location> iter = cachedChildren.listIterator(startingIndex);
                    for (int i = 0; i != request.count() && iter.hasNext(); ++i) {
                        Location child = iter.next();
                        request.addChild(child);
                    }
                }
            } else {
                // Nothing was cached, so we need to search the database for the children ...
                Query query = entities.createNamedQuery("ChildEntity.findRangeUnderParent");
                query.setParameter("workspaceId", workspaceId);
                query.setParameter("parentUuidString", actualParent.uuid);
                query.setParameter("firstIndex", startingIndex);
                query.setParameter("afterIndex", startingIndex + request.count());
                @SuppressWarnings( "unchecked" )
                List<ChildEntity> children = query.getResultList();
                for (ChildEntity child : children) {
                    String namespaceUri = child.getChildNamespace().getUri();
                    String localName = child.getChildName();
                    Name childName = nameFactory.create(namespaceUri, localName);
                    int sns = child.getSameNameSiblingIndex();
                    Path childPath = pathFactory.create(parentPath, childName, sns);
                    String childUuidString = child.getId().getChildUuidString();
                    Location childLocation = Location.create(childPath, UUID.fromString(childUuidString));
                    request.addChild(childLocation);
                }
                // Do not update the cache, since we don't know all of the children.
            }

        } catch (NoResultException e) {
            // there are no properties (probably not expected, but still okay) ...
        } catch (Throwable e) { // Includes PathNotFoundException
            request.setError(e);
            return;
        }
        if (actualLocation != null) request.setActualLocationOfNode(actualLocation);
        setCacheableInfo(request);
    }

    /**
     * {@inheritDoc}
     *
     * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.ReadNextBlockOfChildrenRequest)
     */
    @Override
    public void process( ReadNextBlockOfChildrenRequest request ) {
        logger.trace(request.toString());
        Location actualLocation = null;
        final Location previousSibling = request.startingAfter();
        final int count = request.count();
        try {
            // Find the workspace ...
            WorkspaceEntity workspace = getExistingWorkspace(request.inWorkspace(), request);
            if (workspace == null) return;
            Long workspaceId = workspace.getId();
            assert workspaceId != null;

            ActualLocation actualSibling = getActualLocation(workspaceId, previousSibling);
            actualLocation = actualSibling.location;
            if (!actualLocation.getPath().isRoot()) {
                // First look in the cache for the children of the parent ...
                Path parentPath = actualSibling.location.getPath().getParent();
                assert parentPath != null;
                LinkedList<Location> cachedChildren = cache.getAllChildren(workspaceId, parentPath);
                if (cachedChildren != null) {
                    // The cache has all of the children for the node.
                    // First find the location of the previous sibling ...
                    boolean accumulate = false;
                    int counter = 0;
                    for (Location child : cachedChildren) {
                        if (accumulate) {
                            // We're accumulating children ...
                            request.addChild(child);
                            ++counter;
                            if (counter <= count) continue;
                            break;
                        }
                        // Haven't found the previous sibling yet ...
                        if (child.isSame(previousSibling)) {
                            accumulate = true;
                        }
                    }
                } else {
                    // The children were not found in the cache, so we have to search the database.
                    // We don't know the UUID of the parent, so find the previous sibling and
                    // then get the starting index and the parent UUID ...
                    ChildEntity previousChild = actualSibling.childEntity;
                    if (previousChild == null) {
                        Query query = entities.createNamedQuery("ChildEntity.findByChildUuid");
                        query.setParameter("workspaceId", workspaceId);
                        query.setParameter("childUuidString", actualSibling.uuid);
                        previousChild = (ChildEntity)query.getSingleResult();
                    }
                    int startingIndex = previousChild.getIndexInParent() + 1;
                    String parentUuid = previousChild.getId().getParentUuidString();

                    // Now search the database for the children ...
                    Query query = entities.createNamedQuery("ChildEntity.findRangeUnderParent");
                    query.setParameter("workspaceId", workspaceId);
                    query.setParameter("parentUuidString", parentUuid);
                    query.setParameter("firstIndex", startingIndex);
                    query.setParameter("afterIndex", startingIndex + request.count());
                    @SuppressWarnings( "unchecked" )
                    List<ChildEntity> children = query.getResultList();
                    LinkedList<Location> allChildren = null;
                    if (startingIndex == 1 && children.size() < request.count()) {
                        // The previous child was the first sibling, and we got fewer children than
                        // the max count. This means we know all of the children, so accumulate the locations
                        // so they can be cached ...
                        allChildren = new LinkedList<Location>();
                        allChildren.add(actualSibling.location);
                    }
                    for (ChildEntity child : children) {
                        String namespaceUri = child.getChildNamespace().getUri();
                        String localName = child.getChildName();
                        Name childName = nameFactory.create(namespaceUri, localName);
                        int sns = child.getSameNameSiblingIndex();
                        Path childPath = pathFactory.create(parentPath, childName, sns);
                        String childUuidString = child.getId().getChildUuidString();
                        Location childLocation = Location.create(childPath, UUID.fromString(childUuidString));
                        request.addChild(childLocation);
                        if (allChildren != null) {
                            // We're going to cache the results, so add this child ...
                            allChildren.add(childLocation);
                        }
                    }

                    if (allChildren != null) {
                        cache.setAllChildren(workspaceId, parentPath, allChildren);
                    }
                }
            }

        } catch (NoResultException e) {
            // there are no properties (probably not expected, but still okay) ...
        } catch (Throwable e) { // Includes PathNotFoundException
            request.setError(e);
            return;
        }
        if (actualLocation != null) request.setActualLocationOfStartingAfterNode(actualLocation);
        setCacheableInfo(request);
    }

    /**
     * {@inheritDoc}
     *
     * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.ReadAllPropertiesRequest)
     */
    @Override
    public void process( ReadAllPropertiesRequest request ) {
        logger.trace(request.toString());
        Location actualLocation = null;
        try {
            // Find the workspace ...
            WorkspaceEntity workspace = getExistingWorkspace(request.inWorkspace(), request);
            if (workspace == null) return;
            Long workspaceId = workspace.getId();
            assert workspaceId != null;

            Location location = request.at();
            ActualLocation actual = getActualLocation(workspaceId, location);
            String uuidString = actual.uuid;
            actualLocation = actual.location;

            // Record the UUID as a property, since it's not stored in the serialized properties...
            request.addProperty(actualLocation.getIdProperty(DnaLexicon.UUID));

            // Find the properties entity for this node ...
            Query query = entities.createNamedQuery("PropertiesEntity.findByUuid");
            query.setParameter("workspaceId", workspaceId);
            query.setParameter("uuid", uuidString);
            PropertiesEntity entity = (PropertiesEntity)query.getSingleResult();

            // Deserialize the properties ...
            boolean compressed = entity.isCompressed();
            int propertyCount = entity.getPropertyCount();
            Collection<Property> properties = new ArrayList<Property>(propertyCount);
            byte[] data = entity.getData();
            if (data != null) {
                LargeValueSerializer largeValues = new LargeValueSerializer(entity);
                ByteArrayInputStream bais = new ByteArrayInputStream(data);
                InputStream is = compressed ? new GZIPInputStream(bais) : bais;
                ObjectInputStream ois = new ObjectInputStream(is);
                try {
                    serializer.deserializeAllProperties(ois, properties, largeValues);
                    for (Property property : properties) {
                        request.addProperty(property);
                    }
                } finally {
                    ois.close();
                }
            }
        } catch (NoResultException e) {
            // there are no properties (probably not expected, but still okay) ...
        } catch (Throwable e) { // Includes PathNotFoundException
            request.setError(e);
            return;
        }
        if (actualLocation != null) request.setActualLocationOfNode(actualLocation);
        setCacheableInfo(request);
    }

    /**
     * {@inheritDoc}
     *
     * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.ReadPropertyRequest)
     */
    @Override
    public void process( ReadPropertyRequest request ) {
        logger.trace(request.toString());
        // Process the one property that's requested ...
        Location actualLocation = null;
        try {
            // Find the workspace ...
            WorkspaceEntity workspace = getExistingWorkspace(request.inWorkspace(), request);
            if (workspace == null) return;
            Long workspaceId = workspace.getId();
            assert workspaceId != null;

            // Small optimization ...
            final Name propertyName = request.named();
            if (DnaLexicon.UUID.equals(propertyName)) {
                try {
                    // Just get the UUID ...
                    Location location = request.on();
                    ActualLocation actual = getActualLocation(workspaceId, location); // verifies the UUID
                    UUID uuid = actual.location.getUuid();
                    Property uuidProperty = getExecutionContext().getPropertyFactory().create(propertyName, uuid);
                    request.setProperty(uuidProperty);
                    request.setActualLocationOfNode(actual.location);
                    setCacheableInfo(request);
                } catch (Throwable e) { // Includes PathNotFoundException
                    request.setError(e);
                }
                return;
            }

            Location location = request.on();
            ActualLocation actual = getActualLocation(workspaceId, location);
            String uuidString = actual.uuid;
            actualLocation = actual.location;

            // Find the properties entity for this node ...
            Query query = entities.createNamedQuery("PropertiesEntity.findByUuid");
            query.setParameter("workspaceId", workspaceId);
            query.setParameter("uuid", uuidString);
            PropertiesEntity entity = (PropertiesEntity)query.getSingleResult();

            // Deserialize the stream of properties, but only materialize the one property ...
            boolean compressed = entity.isCompressed();
            int propertyCount = entity.getPropertyCount();
            Collection<Property> properties = new ArrayList<Property>(propertyCount);
            byte[] data = entity.getData();
            if (data != null) {
                LargeValueSerializer largeValues = new LargeValueSerializer(entity);
                ByteArrayInputStream bais = new ByteArrayInputStream(data);
                InputStream is = compressed ? new GZIPInputStream(bais) : bais;
                ObjectInputStream ois = new ObjectInputStream(is);
                try {
                    Serializer.LargeValues skippedLargeValues = Serializer.NO_LARGE_VALUES;
                    serializer.deserializeSomeProperties(ois, properties, largeValues, skippedLargeValues, propertyName);
                    for (Property property : properties) {
                        request.setProperty(property); // should be only one property
                    }
                } finally {
                    ois.close();
                }
            }
        } catch (NoResultException e) {
            // there are no properties (probably not expected, but still okay) ...
        } catch (Throwable e) { // Includes PathNotFoundException
            request.setError(e);
            return;
        }
        if (actualLocation != null) request.setActualLocationOfNode(actualLocation);
        setCacheableInfo(request);
    }

    /**
     * {@inheritDoc}
     *
     * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.UpdatePropertiesRequest)
     */
    @Override
    public void process( UpdatePropertiesRequest request ) {
        logger.trace(request.toString());
        Location actualLocation = null;
        try {
            // Find the workspace ...
            WorkspaceEntity workspace = getExistingWorkspace(request.inWorkspace(), request);
            if (workspace == null) return;
            Long workspaceId = workspace.getId();
            assert workspaceId != null;

            Location location = request.on();
            ActualLocation actual = getActualLocation(workspaceId, location);
            actualLocation = actual.location;

            // Find the properties entity for this node ...
            Query query = entities.createNamedQuery("PropertiesEntity.findByUuid");
            query.setParameter("workspaceId", workspaceId);
            query.setParameter("uuid", actual.uuid);
            PropertiesEntity entity = null;
            try {
                entity = (PropertiesEntity)query.getSingleResult();

                // Prepare the streams so we can deserialize all existing properties and reserialize the old and updated
                // properties ...
                boolean compressed = entity.isCompressed();
                byte[] originalData = entity.getData();
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                OutputStream os = compressed ? new GZIPOutputStream(baos) : baos;
                ObjectOutputStream oos = new ObjectOutputStream(os);
                int numProps = 0;
                LargeValueSerializer largeValues = null;
                Map<Name, Property> props = request.properties();
                References refs = enforceReferentialIntegrity ? new References() : null;
                if (originalData == null) {
                    largeValues = new LargeValueSerializer(entity);
                    numProps = props.size();
                    serializer.serializeProperties(oos, numProps, props.values(), largeValues, refs);
                } else {
                    boolean hadLargeValues = !entity.getLargeValues().isEmpty();
                    Set<String> largeValueHashesWritten = hadLargeValues ? new HashSet<String>() : null;
                    largeValues = new LargeValueSerializer(entity, largeValueHashesWritten);
                    ByteArrayInputStream bais = new ByteArrayInputStream(originalData);
                    InputStream is = compressed ? new GZIPInputStream(bais) : bais;
                    ObjectInputStream ois = new ObjectInputStream(is);
                    SkippedLargeValues removedValues = new SkippedLargeValues(largeValues);
                    try {
                        Serializer.ReferenceValues refValues = refs != null ? refs : Serializer.NO_REFERENCES_VALUES;
                        numProps = serializer.reserializeProperties(ois, oos, props, largeValues, removedValues, refValues);
                    } finally {
                        try {
                            ois.close();
                        } finally {
                            oos.close();
                        }
                    }
                    // The new large values were recorded and associated with the properties entity during reserialization.
                    // However, any values no longer used now need to be removed ...
                    if (hadLargeValues) {
                        // Remove any large value from the 'skipped' list that was also written ...
                        removedValues.skippedKeys.removeAll(largeValueHashesWritten);
                        for (String oldHexKey : removedValues.skippedKeys) {
                            LargeValueId id = new LargeValueId(oldHexKey);
                            entity.getLargeValues().remove(id);
                        }
                    }

                    if (refs != null) {
                        // Remove any existing references ...
                        if (refs.hasRemoved()) {
                            for (Reference reference : refs.getRemoved()) {
                                String toUuid = resolveToUuid(workspaceId, reference);
                                if (toUuid != null) {
                                    ReferenceId id = new ReferenceId(workspaceId, actual.uuid, toUuid);
                                    ReferenceEntity refEntity = entities.find(ReferenceEntity.class, id);
                                    if (refEntity != null) {
                                        entities.remove(refEntity);
                                        workspaceIdsWithChangedReferences.add(workspaceId);
                                    }
                                }
                            }
                        }
                    }
                }
                entity.setPropertyCount(numProps);
                entity.setData(baos.toByteArray());
                entity.setCompressed(compressData);

                if (refs != null && refs.hasWritten()) {
                    // If there were references from the updated node ...
                    Set<Reference> newReferences = refs.getWritten();
                    // Remove any reference that was written (and not removed) ...
                    newReferences.removeAll(refs.getRead());
                    if (newReferences.size() != 0) {
                        // Now save the new references ...
                        for (Reference reference : newReferences) {
                            String toUuid = resolveToUuid(workspaceId, reference);
                            if (toUuid != null) {
                                ReferenceId id = new ReferenceId(workspaceId, actual.uuid, toUuid);
                                ReferenceEntity refEntity = new ReferenceEntity(id);
                                entities.persist(refEntity);
                                workspaceIdsWithChangedReferences.add(workspaceId);
                            }
                        }
                    }
                }
            } catch (NoResultException e) {
                // there are no properties yet ...
                createProperties(workspaceId, actual.uuid, request.properties().values());
            }

        } catch (Throwable e) { // Includes PathNotFoundException
            request.setError(e);
            return;
        }
        if (actualLocation != null) request.setActualLocationOfNode(actualLocation);
    }

    /**
     * {@inheritDoc}
     *
     * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.ReadBranchRequest)
     */
    @Override
    public void process( ReadBranchRequest request ) {
        logger.trace(request.toString());
        Location actualLocation = null;
        try {
            // Find the workspace ...
            WorkspaceEntity workspace = getExistingWorkspace(request.inWorkspace(), request);
            if (workspace == null) return;
            Long workspaceId = workspace.getId();
            assert workspaceId != null;

            Location location = request.at();
            ActualLocation actual = getActualLocation(workspaceId, location);
            actualLocation = actual.location;
            Path path = actualLocation.getPath();

            // Record the location of each node by its UUID; we'll use this when processing the properties ...
            Map<String, Location> locationsByUuid = new HashMap<String, Location>();
            locationsByUuid.put(actual.uuid, location);

            // Compute the subgraph, including the root ...
            int maxDepth = request.maximumDepth();
            SubgraphQuery query = SubgraphQuery.create(getExecutionContext(),
                                                       entities,
                                                       workspaceId,
                                                       actualLocation.getUuid(),
                                                       path,
                                                       maxDepth);

            try {
                // Record all of the children ...
                Path parent = path;
                String parentUuid = actual.uuid;
                Location parentLocation = actualLocation;
                List<Location> children = new LinkedList<Location>();
                Map<Location, List<Location>> childrenByParentLocation = new HashMap<Location, List<Location>>();
                childrenByParentLocation.put(parentLocation, children);
                boolean includeChildrenOfNodesAtMaxDepth = true;
                for (ChildEntity child : query.getNodes(false, includeChildrenOfNodesAtMaxDepth)) {
                    String namespaceUri = child.getChildNamespace().getUri();
                    String localName = child.getChildName();
                    Name childName = nameFactory.create(namespaceUri, localName);
                    int sns = child.getSameNameSiblingIndex();
                    // Figure out who the parent is ...
                    String childParentUuid = child.getId().getParentUuidString();
                    if (!parentUuid.equals(childParentUuid)) {
                        // Find the correct parent ...
                        parentLocation = locationsByUuid.get(childParentUuid);
                        parent = parentLocation.getPath();
                        parentUuid = childParentUuid;
                        // See if there is already a list of children for this parent ...
                        children = childrenByParentLocation.get(parentLocation);
                        if (children == null) {
                            children = new LinkedList<Location>();
                            childrenByParentLocation.put(parentLocation, children);
                        }
                    }
                    assert children != null;
                    Path childPath = pathFactory.create(parent, childName, sns);
                    String childUuidString = child.getId().getChildUuidString();
                    Location childLocation = Location.create(childPath, UUID.fromString(childUuidString));
                    locationsByUuid.put(childUuidString, childLocation);
                    children.add(childLocation);
                }
                // Now add the list of children to the results ...
                for (Map.Entry<Location, List<Location>> entry : childrenByParentLocation.entrySet()) {
                    // Don't add if there are no children ...
                    if (!entry.getValue().isEmpty()) {
                        request.setChildren(entry.getKey(), entry.getValue());
                    }
                }

                // Note that we've found children for nodes that are at the maximum depth. This is so that the nodes
                // in the subgraph all have the correct children. However, we don't want to store the properties for
                // any node whose depth is greater than the maximum depth. Therefore, only get the properties that
                // include nodes within the maximum depth...
                includeChildrenOfNodesAtMaxDepth = false;

                // Now record all of the properties ...
                for (PropertiesEntity props : query.getProperties(true, includeChildrenOfNodesAtMaxDepth)) {
                    boolean compressed = props.isCompressed();
                    int propertyCount = props.getPropertyCount();
                    Collection<Property> properties = new ArrayList<Property>(propertyCount);
                    Location nodeLocation = locationsByUuid.get(props.getId().getUuidString());
                    assert nodeLocation != null;
                    // Record the UUID as a property, since it's not stored in the serialized properties...
                    properties.add(actualLocation.getIdProperty(DnaLexicon.UUID));
                    // Deserialize all the properties (except the UUID)...
                    byte[] data = props.getData();
                    if (data != null) {
                        LargeValueSerializer largeValues = new LargeValueSerializer(props);
                        ByteArrayInputStream bais = new ByteArrayInputStream(data);
                        InputStream is = compressed ? new GZIPInputStream(bais) : bais;
                        ObjectInputStream ois = new ObjectInputStream(is);
                        try {
                            serializer.deserializeAllProperties(ois, properties, largeValues);
                            request.setProperties(nodeLocation, properties);
                        } finally {
                            ois.close();
                        }
                    }
                }
            } finally {
                // Close and release the temporary data used for this operation ...
                query.close();
            }

        } catch (Throwable e) { // Includes PathNotFoundException
            request.setError(e);
            return;
        }
        request.setActualLocationOfNode(actualLocation);
        setCacheableInfo(request);
    }

    /**
     * {@inheritDoc}
     *
     * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.CopyBranchRequest)
     */
    @Override
    public void process( CopyBranchRequest request ) {
        logger.trace(request.toString());
        Location actualFromLocation = null;
        Location actualToLocation = null;
        try {
            // Find the workspaces ...
            WorkspaceEntity fromWorkspace = getExistingWorkspace(request.fromWorkspace(), request);
            if (fromWorkspace == null) return;
            WorkspaceEntity intoWorkspace = getExistingWorkspace(request.intoWorkspace(), request);
            if (intoWorkspace == null) return;
            Long fromWorkspaceId = fromWorkspace.getId();
            Long intoWorkspaceId = intoWorkspace.getId();
            assert fromWorkspaceId != null;
            assert intoWorkspaceId != null;
            final boolean isSameWorkspace = fromWorkspaceId == intoWorkspaceId;

            Location fromLocation = request.from();
            ActualLocation actualFrom = getActualLocation(fromWorkspaceId, fromLocation);
            actualFromLocation = actualFrom.location;
            Path fromPath = actualFromLocation.getPath();

            Location newParentLocation = request.into();
            ActualLocation actualNewParent = getActualLocation(intoWorkspaceId, newParentLocation);
            assert actualNewParent != null;

            // Create a map that we'll use to record the new UUID for each of the original nodes ...
            Map<String, String> originalToNewUuid = new HashMap<String, String>();

            // Compute the subgraph, including the top node in the subgraph ...
            SubgraphQuery query = SubgraphQuery.create(getExecutionContext(),
                                                       entities,
                                                       fromWorkspaceId,
                                                       actualFromLocation.getUuid(),
                                                       fromPath,
                                                       0);
            try {
                // Walk through the original nodes, creating new ChildEntity object (i.e., copy) for each original ...
                List<ChildEntity> originalNodes = query.getNodes(true, true);
                Iterator<ChildEntity> originalIter = originalNodes.iterator();

                // Start with the original (top-level) node first, since we need to add it to the list of children ...
                if (originalIter.hasNext()) {
                    ChildEntity original = originalIter.next();

                    // Create a new UUID for the copy ...
                    String copyUuid = original.getId().getChildUuidString();
                    if (isSameWorkspace) {
                        copyUuid = UUID.randomUUID().toString();
                        originalToNewUuid.put(original.getId().getChildUuidString(), copyUuid);
                    }

                    // Now add the new copy of the original ...
                    Name childName = request.desiredName();
                    if (childName == null) childName = fromPath.getLastSegment().getName();
                    boolean allowSnS = original.getAllowsSameNameChildren();
                    actualToLocation = addNewChild(intoWorkspaceId, actualNewParent, copyUuid, childName, allowSnS);
                }

                // Now create copies of all children in the subgraph.
                // We assign new UUIDs to each new child only if the 'from' and 'into' workspaces are the same ...
                while (originalIter.hasNext()) {
                    ChildEntity original = originalIter.next();
                    String newParentUuidOfCopy = original.getId().getParentUuidString();
                    if (isSameWorkspace) newParentUuidOfCopy = originalToNewUuid.get(newParentUuidOfCopy);
                    assert newParentUuidOfCopy != null;

                    // Create a new UUID for the copy ...
                    String copyUuid = original.getId().getChildUuidString();
                    if (isSameWorkspace) {
                        copyUuid = UUID.randomUUID().toString();
                        originalToNewUuid.put(original.getId().getChildUuidString(), copyUuid);
                    }

                    // Create the copy ...
                    ChildEntity copy = new ChildEntity(new ChildId(intoWorkspaceId, newParentUuidOfCopy, copyUuid),
                                                       original.getIndexInParent(), original.getChildNamespace(),
                                                       original.getChildName(), original.getSameNameSiblingIndex());
                    entities.persist(copy);
                }
                entities.flush();

                Set<String> newNodesWithReferenceProperties = new HashSet<String>();
                if (isSameWorkspace) {
                    // Now create copies of all the intra-subgraph references, replacing the UUIDs on both ends ...
                    for (ReferenceEntity reference : query.getInternalReferences()) {
                        String newFromUuid = originalToNewUuid.get(reference.getId().getFromUuidString());
                        assert newFromUuid != null;
                        String newToUuid = originalToNewUuid.get(reference.getId().getToUuidString());
                        assert newToUuid != null;
                        ReferenceEntity copy = new ReferenceEntity(new ReferenceId(intoWorkspaceId, newFromUuid, newToUuid));
                        entities.persist(copy);
                        newNodesWithReferenceProperties.add(newFromUuid);
                    }

                    // Now create copies of all the references owned by the subgraph but pointing to non-subgraph nodes,
                    // so we only replaced the 'from' UUID ...
                    for (ReferenceEntity reference : query.getOutwardReferences()) {
                        String oldToUuid = reference.getId().getToUuidString();
                        String newFromUuid = originalToNewUuid.get(reference.getId().getFromUuidString());
                        assert newFromUuid != null;
                        ReferenceEntity copy = new ReferenceEntity(new ReferenceId(intoWorkspaceId, newFromUuid, oldToUuid));
                        entities.persist(copy);
                        newNodesWithReferenceProperties.add(newFromUuid);
                    }
                }

                // Now process the properties, creating a copy (note references are not changed) ...
                for (PropertiesEntity original : query.getProperties(true, true)) {
                    // Find the UUID of the copy ...
                    String copyUuid = original.getId().getUuidString();
                    if (isSameWorkspace) copyUuid = originalToNewUuid.get(copyUuid);
                    assert copyUuid != null;

                    // Create the copy ...
                    boolean compressed = original.isCompressed();
                    byte[] originalData = original.getData();
                    PropertiesEntity copy = new PropertiesEntity(new NodeId(intoWorkspaceId, copyUuid));
                    copy.setCompressed(compressed);
                    if (newNodesWithReferenceProperties.contains(copyUuid)) {

                        // This node has internal or outward references that must be adjusted ...
                        ByteArrayOutputStream baos = new ByteArrayOutputStream();
                        OutputStream os = compressed ? new GZIPOutputStream(baos) : baos;
                        ObjectOutputStream oos = new ObjectOutputStream(os);
                        ByteArrayInputStream bais = new ByteArrayInputStream(originalData);
                        InputStream is = compressed ? new GZIPInputStream(bais) : bais;
                        ObjectInputStream ois = new ObjectInputStream(is);
                        try {
                            serializer.adjustReferenceProperties(ois, oos, originalToNewUuid);
                        } finally {
                            try {
                                ois.close();
                            } finally {
                                oos.close();
                            }
                        }
                        copy.setData(baos.toByteArray());
                    } else {
                        // No references to adjust, so just copy the original data ...
                        copy.setData(originalData);
                    }
                    copy.setPropertyCount(original.getPropertyCount());
                    copy.setReferentialIntegrityEnforced(original.isReferentialIntegrityEnforced());
                    entities.persist(copy);
                }
                entities.flush();

            } finally {
                // Close and release the temporary data used for this operation ...
                query.close();
            }

        } catch (Throwable e) { // Includes PathNotFoundException
            request.setError(e);
            return;
        }
        request.setActualLocations(actualFromLocation, actualToLocation);
    }

    /**
     * {@inheritDoc}
     *
     * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.DeleteBranchRequest)
     */
    @Override
    public void process( DeleteBranchRequest request ) {
        logger.trace(request.toString());
        Location actualLocation = null;
        try {
            // Find the workspace ...
            WorkspaceEntity workspace = getExistingWorkspace(request.inWorkspace(), request);
            if (workspace == null) return;
            Long workspaceId = workspace.getId();
            assert workspaceId != null;

            Location location = request.at();
            ActualLocation actual = getActualLocation(workspaceId, location);
            actualLocation = actual.location;
            Path path = actualLocation.getPath();

            // Compute the subgraph, including the top node in the subgraph ...
            SubgraphQuery query = SubgraphQuery.create(getExecutionContext(),
                                                       entities,
                                                       workspaceId,
                                                       actualLocation.getUuid(),
                                                       path,
                                                       0);
            try {
                ChildEntity deleted = query.getNode();
                String parentUuidString = deleted.getId().getParentUuidString();
                String childName = deleted.getChildName();
                long nsId = deleted.getChildNamespace().getId();
                int indexInParent = deleted.getIndexInParent();

                // Get the locations of all deleted nodes, which will be required by events ...
                List<Location> deletedLocations = query.getNodeLocations(true, true);

                // Now delete the subgraph ...
                query.deleteSubgraph(true);

                // Verify referential integrity: that none of the deleted nodes are referenced by nodes not being deleted.
                List<ReferenceEntity> invalidReferences = query.getInwardReferences();
                if (invalidReferences.size() > 0) {
                    // Some of the references that remain will be invalid, since they point to nodes that
                    // have just been deleted. Build up the information necessary to produce a useful exception ...
                    ValueFactory<Reference> refFactory = getExecutionContext().getValueFactories().getReferenceFactory();
                    Map<Location, List<Reference>> invalidRefs = new HashMap<Location, List<Reference>>();
                    for (ReferenceEntity entity : invalidReferences) {
                        UUID fromUuid = UUID.fromString(entity.getId().getFromUuidString());
                        ActualLocation actualFromLocation = getActualLocation(workspaceId, Location.create(fromUuid));
                        Location fromLocation = actualFromLocation.location;
                        List<Reference> refs = invalidRefs.get(fromLocation);
                        if (refs == null) {
                            refs = new ArrayList<Reference>();
                            invalidRefs.put(fromLocation, refs);
                        }
                        UUID toUuid = UUID.fromString(entity.getId().getToUuidString());
                        refs.add(refFactory.create(toUuid));
                    }
                    String msg = JpaConnectorI18n.unableToDeleteBecauseOfReferences.text();
                    throw new ReferentialIntegrityException(invalidRefs, msg);
                }

                // And adjust the SNS index and indexes ...
                ChildEntity.adjustSnsIndexesAndIndexesAfterRemoving(entities,
                                                                    workspaceId,
                                                                    parentUuidString,
                                                                    childName,
                                                                    nsId,
                                                                    indexInParent);
                entities.flush();

                // Remove from the cache of children locations all entries for deleted nodes ...
                cache.removeBranch(workspaceId, deletedLocations);
            } finally {
                // Close and release the temporary data used for this operation ...
                query.close();
            }

        } catch (Throwable e) { // Includes PathNotFoundException
            request.setError(e);
            return;
        }
        request.setActualLocationOfNode(actualLocation);
    }

    /**
     * {@inheritDoc}
     *
     * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.MoveBranchRequest)
     */
    @Override
    public void process( MoveBranchRequest request ) {
        logger.trace(request.toString());
        Location actualOldLocation = null;
        Location actualNewLocation = null;
        try {
            // Find the workspaces ...
            WorkspaceEntity workspace = getExistingWorkspace(request.inWorkspace(), request);
            if (workspace == null) return;
            Long workspaceId = workspace.getId();
            assert workspaceId != null;

            Location fromLocation = request.from();
            ActualLocation actualLocation = getActualLocation(workspaceId, fromLocation);
            String fromUuidString = actualLocation.uuid;
            actualOldLocation = actualLocation.location;
            Path oldPath = actualOldLocation.getPath();

            // It's not possible to move the root node
            if (oldPath.isRoot()) {
                String msg = JpaConnectorI18n.unableToMoveRootNode.text(getSourceName());
                throw new InvalidRequestException(msg);
            }

            // Find the ChildEntity of the existing 'from' node ...
            ChildEntity fromEntity = actualLocation.childEntity;
            final String oldParentUuid = fromEntity.getId().getParentUuidString();

            // Find the actual new location ...
            Location toLocation = request.into();
            String toUuidString = null;
            if (request.hasNoEffect()) {
                actualNewLocation = actualOldLocation;
            } else {
                // We have to proceed as normal ...
                ActualLocation actualIntoLocation = getActualLocation(workspaceId, toLocation);
                toUuidString = actualIntoLocation.uuid;
                if (!toUuidString.equals(oldParentUuid)) {
                    // Now we know that the new parent is not the existing parent ...
                    final int oldIndex = fromEntity.getIndexInParent();

                    // Make sure the child name is set correctly ...
                    String childOldLocalName = fromEntity.getChildName();
                    String childLocalName = null;
                    NamespaceEntity ns = null;
                    Name childName = request.desiredName();
                    if (childName != null) {
                        childLocalName = request.desiredName().getLocalName();
                        String childNsUri = childName.getNamespaceUri();
                        ns = namespaces.get(childNsUri, true);
                    } else {
                        childName = oldPath.getLastSegment().getName();
                        childLocalName = fromEntity.getChildName();
                        ns = fromEntity.getChildNamespace();
                    }

                    // Find the largest SNS index in the existing ChildEntity objects with the same name ...
                    Query query = entities.createNamedQuery("ChildEntity.findMaximumSnsIndex");
                    query.setParameter("workspaceId", workspaceId);
                    query.setParameter("parentUuid", toUuidString);
                    query.setParameter("ns", ns.getId());
                    query.setParameter("childName", childLocalName);
                    int nextSnsIndex = 1;
                    try {
                        Integer index = (Integer)query.getSingleResult();
                        if (index != null) nextSnsIndex = index.intValue() + 1;
                    } catch (NoResultException e) {
                    }

                    // Find the largest child index in the existing ChildEntity objects ...
                    query = entities.createNamedQuery("ChildEntity.findMaximumChildIndex");
                    query.setParameter("workspaceId", workspaceId);
                    query.setParameter("parentUuid", toUuidString);
                    int nextIndexInParent = 1;
                    try {
                        Integer index = (Integer)query.getSingleResult();
                        if (index != null) nextIndexInParent = index + 1;
                    } catch (NoResultException e) {
                    }

                    ChildId movedId = new ChildId(workspaceId, toUuidString, fromUuidString);
                    if (fromEntity.getId().equals(movedId)) {
                        // The node is being renamed, but not moved ...
                        fromEntity.setChildName(childLocalName);
                        fromEntity.setChildNamespace(ns);
                        fromEntity.setIndexInParent(nextIndexInParent);
                        fromEntity.setSameNameSiblingIndex(nextSnsIndex);
                    } else {
                        // We won't be able to move the entity to a different parent, because that would involve
                        // changing the PK for the entity, which is not possible. Instead, we have to create a
                        // new entity with the same identity information, then delete 'fromEntity'
                        ChildEntity movedEntity = new ChildEntity(movedId, nextIndexInParent, ns, childLocalName, nextSnsIndex);
                        movedEntity.setAllowsSameNameChildren(fromEntity.getAllowsSameNameChildren());
                        entities.persist(movedEntity);
                        entities.remove(fromEntity);
                    }

                    // Flush the entities to the database ...
                    entities.flush();

                    // Determine the new location ...
                    Path newParentPath = actualIntoLocation.location.getPath();
                    Path newPath = pathFactory.create(newParentPath, childName, nextSnsIndex);
                    actualNewLocation = actualOldLocation.with(newPath);

                    // And adjust the SNS index and indexes ...
                    ChildEntity.adjustSnsIndexesAndIndexesAfterRemoving(entities,
                                                                        workspaceId,
                                                                        oldParentUuid,
                                                                        childOldLocalName,
                                                                        ns.getId(),
                                                                        oldIndex);

                    // Update the cache ...
                    cache.moveNode(workspaceId, actualOldLocation, oldIndex, actualNewLocation);
                }

            }

        } catch (Throwable e) { // Includes PathNotFoundException
            request.setError(e);
            return;
        }
        request.setActualLocations(actualOldLocation, actualNewLocation);
    }

    /**
     * {@inheritDoc}
     *
     * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.VerifyWorkspaceRequest)
     */
    @Override
    public void process( VerifyWorkspaceRequest request ) {
        // Find the workspace ...
        String workspaceName = request.workspaceName();
        if (workspaceName == null) workspaceName = nameOfDefaultWorkspace;
        WorkspaceEntity workspace = getExistingWorkspace(workspaceName, request);
        if (workspace != null) {
            Long workspaceId = workspace.getId();
            assert workspaceId != null;
            ActualLocation actual = getActualLocation(workspaceId, Location.create(pathFactory.createRootPath()));
            request.setActualRootLocation(actual.location);
            request.setActualWorkspaceName(workspace.getName());
        }
    }

    /**
     * {@inheritDoc}
     *
     * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.GetWorkspacesRequest)
     */
    @Override
    public void process( GetWorkspacesRequest request ) {
        // Return the set of available workspace names, even if new workspaces can be created ...
        Set<String> names = workspaces.getWorkspaceNames();
        // Add in the names of the predefined workspaces (in case they weren't yet accessed) ...
        for (String name : this.predefinedWorkspaceNames) {
            names.add(name);
        }
        request.setAvailableWorkspaceNames(Collections.unmodifiableSet(names));
        setCacheableInfo(request);
    }

    /**
     * {@inheritDoc}
     *
     * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.CreateWorkspaceRequest)
     */
    @Override
    public void process( CreateWorkspaceRequest request ) {
        String name = request.desiredNameOfNewWorkspace();
        if (!creatingWorkspacesAllowed) {
            String msg = JpaConnectorI18n.unableToCreateWorkspaces.text(getSourceName());
            request.setError(new InvalidRequestException(msg));
        }
        Set<String> existingNames = workspaces.getWorkspaceNames();
        int counter = 0;
        while (existingNames.contains(name)) {
            switch (request.conflictBehavior()) {
                case CREATE_WITH_ADJUSTED_NAME:
                    name = request.desiredNameOfNewWorkspace() + ++counter;
                    break;
                case DO_NOT_CREATE:
                default:
                    String msg = JpaConnectorI18n.workspaceAlreadyExists.text(getSourceName(), name);
                    request.setError(new InvalidWorkspaceException(msg));
                    return;
            }
        }
        // Create the workspace ...
        WorkspaceEntity entity = workspaces.create(name);
        request.setActualWorkspaceName(entity.getName());
        // Create the root node ...
        Location root = Location.create(pathFactory.createRootPath());
        request.setActualRootLocation(getActualLocation(entity.getId(), root).location);
    }

    /**
     * {@inheritDoc}
     *
     * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.CloneWorkspaceRequest)
     */
    @SuppressWarnings( "unchecked" )
    @Override
    public void process( CloneWorkspaceRequest request ) {
        if (!creatingWorkspacesAllowed) {
            String msg = JpaConnectorI18n.unableToCreateWorkspaces.text(getSourceName());
            request.setError(new InvalidRequestException(msg));
        }
        Set<String> existingNames = workspaces.getWorkspaceNames();
        String name = request.desiredNameOfTargetWorkspace();
        int counter = 0;
        while (existingNames.contains(name)) {
            switch (request.targetConflictBehavior()) {
                case CREATE_WITH_ADJUSTED_NAME:
                    name = request.desiredNameOfTargetWorkspace() + ++counter;
                    break;
                case DO_NOT_CREATE:
                default:
                    String msg = JpaConnectorI18n.workspaceAlreadyExists.text(getSourceName(), name);
                    request.setError(new InvalidWorkspaceException(msg));
                    return;
            }
        }
        String fromWorkspaceName = request.nameOfWorkspaceToBeCloned();
        WorkspaceEntity fromWorkspace = workspaces.get(fromWorkspaceName, false);
        if (fromWorkspace == null) {
            switch (request.cloneConflictBehavior()) {
                case SKIP_CLONE:
                    break;
                case DO_NOT_CLONE:
                default:
                    String msg = JpaConnectorI18n.workspaceDoesNotExist.text(getSourceName(), fromWorkspaceName);
                    request.setError(new InvalidRequestException(msg));
                    return;
            }
        }

        // Create the workspace ...
        WorkspaceEntity intoWorkspace = workspaces.create(name);
        String newWorkspaceName = intoWorkspace.getName();
        request.setActualWorkspaceName(newWorkspaceName);

        if (fromWorkspace != null) {
            // Copy the workspace into the new workspace, via bulk insert statements ..
            Long fromWorkspaceId = fromWorkspace.getId();
            Long intoWorkspaceId = intoWorkspace.getId();
            Query query = entities.createNamedQuery("ChildEntity.findInWorkspace");
            query.setParameter("workspaceId", fromWorkspaceId);
            List<ChildEntity> childEntities = query.getResultList();
            for (ChildEntity child : childEntities) {
                ChildId origId = child.getId();
                ChildId copyId = new ChildId(intoWorkspaceId, origId.getParentUuidString(), origId.getChildUuidString());
                ChildEntity copy = new ChildEntity(copyId, child.getIndexInParent(), child.getChildNamespace(),
                                                   child.getChildName());
                copy.setAllowsSameNameChildren(child.getAllowsSameNameChildren());
                copy.setSameNameSiblingIndex(child.getSameNameSiblingIndex());
                entities.persist(copy);
            }
            entities.flush();

            query = entities.createNamedQuery("PropertiesEntity.findInWorkspace");
            query.setParameter("workspaceId", fromWorkspaceId);
            List<PropertiesEntity> properties = query.getResultList();
            for (PropertiesEntity property : properties) {
                NodeId copyId = new NodeId(intoWorkspaceId, property.getId().getUuidString());
                PropertiesEntity copy = new PropertiesEntity(copyId);
                copy.setCompressed(property.isCompressed());
                copy.setData(property.getData());
                copy.setPropertyCount(property.getPropertyCount());
                copy.setReferentialIntegrityEnforced(property.isReferentialIntegrityEnforced());
                Collection<LargeValueId> ids = property.getLargeValues();
                if (ids.size() != 0) {
                    copy.getLargeValues().addAll(ids);
                }
                entities.persist(copy);
            }
            entities.flush();

            query = entities.createNamedQuery("ReferenceEntity.findInWorkspace");
            query.setParameter("workspaceId", fromWorkspaceId);
            List<ReferenceEntity> references = query.getResultList();
            for (ReferenceEntity reference : references) {
                ReferenceId from = reference.getId();
                ReferenceId copy = new ReferenceId(fromWorkspaceId, from.getFromUuidString(), from.getToUuidString());
                entities.persist(new ReferenceEntity(copy));
            }
            entities.flush();
        }

        // Finish up the request ...
        Location root = Location.create(pathFactory.createRootPath(), rootNodeUuid);
        request.setActualRootLocation(getActualLocation(intoWorkspace.getId(), root).location);
    }

    /**
     * {@inheritDoc}
     *
     * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.DestroyWorkspaceRequest)
     */
    @Override
    public void process( DestroyWorkspaceRequest request ) {
        // Verify the workspace exists ...
        WorkspaceEntity workspace = getExistingWorkspace(request.workspaceName(), request);
        if (workspace == null) return;
        Long workspaceId = workspace.getId();
        assert workspaceId != null;

        // Delete the workspace ...
        workspaces.destroy(workspace.getName());

        // Delete all the entities from this workspace ...
        Query delete = entities.createQuery("delete PropertiesEntity entity where entity.id.workspaceId = :workspaceId");
        delete.setParameter("workspaceId", workspaceId);
        delete.executeUpdate();

        delete = entities.createQuery("delete ChildEntity entity where entity.id.workspaceId = :workspaceId");
        delete.setParameter("workspaceId", workspaceId);
        delete.executeUpdate();

        delete = entities.createQuery("delete ReferenceEntity entity where entity.id.workspaceId = :workspaceId");
        delete.setParameter("workspaceId", workspaceId);
        delete.executeUpdate();

        // Delete unused large values ...
        LargeValueEntity.deleteUnused(entities);
    }

    /**
     * {@inheritDoc}
     *
     * @see org.jboss.dna.graph.request.processor.RequestProcessor#close()
     */
    @Override
    public void close() {
        // Verify that the references are valid so far ...
        verifyReferences();

        // Now commit the transaction ...
        EntityTransaction txn = entities.getTransaction();
        if (txn != null) txn.commit();
        super.close();
    }

    protected WorkspaceEntity getExistingWorkspace( String workspaceName,
                                                    Request request ) {
        WorkspaceEntity workspace = workspaces.get(workspaceName, false);
        if (workspace == null) {
            // Is this a predefined workspace?
            for (String name : predefinedWorkspaceNames) {
                if (workspaceName.equals(name)) {
                    // Create it anyway ...
                    return workspaces.create(workspaceName);
                }
            }
            String msg = JpaConnectorI18n.workspaceDoesNotExist.text(getSourceName(), workspaceName);
            request.setError(new InvalidWorkspaceException(msg));
        }
        return workspace;
    }

    /**
     * {@link ReferenceEntity Reference entities} are added and removed in the appropriate <code>process(...)</code> methods.
     * However, this method is typically called in {@link BasicRequestProcessor#close()} and performs the following steps:
     * <ol>
     * <li>Remove all references that have a "from" node that is under the versions branch.</li>
     * <li>Verify that all remaining references have a valid and existing "to" node</li>
     * </ol>
     */
    protected void verifyReferences() {
        if (!enforceReferentialIntegrity) return;
        if (!workspaceIdsWithChangedReferences.isEmpty()) {

            Map<Location, List<Reference>> invalidRefs = new HashMap<Location, List<Reference>>();
            for (Long workspaceId : workspaceIdsWithChangedReferences) {

                // Remove all references that have a "from" node that doesn't support referential integrity ...
                ReferenceEntity.deleteUnenforcedReferences(workspaceId, entities);

                // Verify that all references are resolved to existing nodes ...
                int numUnresolved = ReferenceEntity.countAllReferencesResolved(workspaceId, entities);
                if (numUnresolved != 0) {
                    List<ReferenceEntity> references = ReferenceEntity.verifyAllReferencesResolved(workspaceId, entities);
                    ValueFactory<Reference> refFactory = getExecutionContext().getValueFactories().getReferenceFactory();
                    for (ReferenceEntity entity : references) {
                        ReferenceId id = entity.getId();
                        UUID fromUuid = UUID.fromString(id.getFromUuidString());
                        Location location = Location.create(fromUuid);
                        location = getActualLocation(id.getWorkspaceId(), location).location;
                        List<Reference> refs = invalidRefs.get(location);
                        if (refs == null) {
                            refs = new ArrayList<Reference>();
                            invalidRefs.put(location, refs);
                        }
                        UUID toUuid = UUID.fromString(id.getToUuidString());
                        refs.add(refFactory.create(toUuid));
                    }
                }
            }

            workspaceIdsWithChangedReferences.clear();

            if (!invalidRefs.isEmpty()) {
                String msg = JpaConnectorI18n.invalidReferences.text(getSourceName());
                throw new ReferentialIntegrityException(invalidRefs, msg);
            }
        }
    }

    protected String createProperties( Long workspaceId,
                                       String uuidString,
                                       Collection<Property> properties ) throws IOException {
        assert uuidString != null;

        // Create the PropertiesEntity ...
        NodeId nodeId = new NodeId(workspaceId, uuidString);
        PropertiesEntity props = new PropertiesEntity(nodeId);

        // If there are properties ...
        boolean processProperties = true;
        if (properties.isEmpty()) processProperties = false;
        else if (properties.size() == 1 && properties.iterator().next().getName().equals(JcrLexicon.NAME)) processProperties = false;

        if (processProperties) {
            References refs = enforceReferentialIntegrity ? new References() : null;
            LargeValueSerializer largeValues = new LargeValueSerializer(props);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            OutputStream os = compressData ? new GZIPOutputStream(baos) : baos;
            ObjectOutputStream oos = new ObjectOutputStream(os);
            int numProperties = properties.size();
            try {
                Serializer.ReferenceValues refValues = refs != null ? refs : Serializer.NO_REFERENCES_VALUES;
                serializer.serializeProperties(oos, numProperties, properties, largeValues, refValues);
            } finally {
                oos.close();
            }

            props.setData(baos.toByteArray());
            props.setPropertyCount(numProperties);

            // Record the changes to the references ...
            if (refs != null && refs.hasWritten()) {
                for (Reference reference : refs.getWritten()) {
                    String toUuid = resolveToUuid(workspaceId, reference);
                    if (toUuid != null) {
                        ReferenceId id = new ReferenceId(workspaceId, uuidString, toUuid);
                        ReferenceEntity refEntity = new ReferenceEntity(id);
                        entities.persist(refEntity);
                        workspaceIdsWithChangedReferences.add(workspaceId);
                    }
                }
            }
        } else {
            props.setData(null);
            props.setPropertyCount(0);
        }
        props.setCompressed(compressData);
        props.setReferentialIntegrityEnforced(true);

        entities.persist(props);

        // References will be persisted in the commit ...
        return uuidString;
    }

    /**
     * Attempt to resolve the reference.
     *
     * @param workspaceId the ID of the workspace in which the reference occurs; may not be null
     * @param reference the reference
     * @return the UUID of the node to which the reference points, or null if the reference could not be resolved
     */
    protected String resolveToUuid( Long workspaceId,
                                    Reference reference ) {
        // See if the reference is by UUID ...
        try {
            UUID uuid = uuidFactory.create(reference);
            ActualLocation actualLocation = getActualLocation(workspaceId, Location.create(uuid));
            return actualLocation.uuid;
        } catch (ValueFormatException e) {
            // Unknown kind of reference, which we don't track
        } catch (PathNotFoundException e) {
            // Unable to resolve reference ...
        }
        // Unable to resolve reference ...
        return null;
    }

    /**
     * Utility method to look up the actual information given a supplied location. This method verifies that the location actually
     * represents an existing node, or it throws a {@link PathNotFoundException}. In all cases, the resulting information contains
     * the correct path and the correct UUID.
     * <p>
     * Note that this method sometimes performs "unnecessary" work when the location contains both a path to a node and the node's
     * corresponding UUID. Strictly speaking, this method would need to do very little. However, in such cases, this method does
     * verify that the information is still correct (ensuring that calls to use the {@link ChildEntity} will be correct). So,
     * while this work <i>may</i> be unnecessary, it does ensure that the location is consistent and correct (something that is
     * not unnecessary).
     * </p>
     * <p>
     * There are cases when a request containing a Path and a UUID are no longer correct. The node may have been just moved by
     * another request (perhaps from a different client), or there may be an error in the component making the request. In these
     * cases, this method assumes that the path is incorrect (since paths may change) and finds the <i>correct path</i> given the
     * UUID.
     * </p>
     * <p>
     * This method will also find the path when the location contains just the UUID.
     * </p>
     *
     * @param workspaceId the ID of the workspace; may not be null
     * @param original the original location; may not be null
     * @return the actual location, which includes the verified location and additional information needed by this method that may
     *         be usable after this method is called
     * @throws PathNotFoundException if the location does not represent a location that could be found
     */
    protected ActualLocation getActualLocation( Long workspaceId,
                                                Location original ) throws PathNotFoundException {
        assert original != null;

        // Look for the UUID in the original ...
        Property uuidProperty = original.getIdProperty(DnaLexicon.UUID);
        String uuidString = uuidProperty != null && !uuidProperty.isEmpty() ? stringFactory.create(uuidProperty.getFirstValue()) : null;

        Path path = original.getPath();
        if (path != null) {
            // See if the location is already in the cache ...
            Location cached = cache.getLocationFor(workspaceId, path);
            if (cached != null) {
                return new ActualLocation(cached, cached.getUuid().toString(), null);
            }
        }

        // If the original location has a UUID, then use that to find the child entity that represents the location ...
        if (uuidString != null) {
            // The original has a UUID, so use that to find the child entity.
            // Then walk up the ancestors and build the path.
            String nodeUuidString = uuidString;
            LinkedList<Path.Segment> segments = new LinkedList<Path.Segment>();
            ChildEntity entity = null;
            ChildEntity originalEntity = null;
            while (uuidString != null && !uuidString.equals(this.rootNodeUuidString)) {
                Query query = entities.createNamedQuery("ChildEntity.findByChildUuid");
                query.setParameter("workspaceId", workspaceId);
                query.setParameter("childUuidString", uuidString);
                try {
                    // Find the parent of the UUID ...
                    entity = (ChildEntity)query.getSingleResult();
                    if (originalEntity == null) originalEntity = entity;
                    String localName = entity.getChildName();
                    String uri = entity.getChildNamespace().getUri();
                    int sns = entity.getSameNameSiblingIndex();
                    Name name = nameFactory.create(uri, localName);
                    segments.addFirst(pathFactory.createSegment(name, sns));
                    uuidString = entity.getId().getParentUuidString();
                } catch (NoResultException e) {
                    uuidString = null;
                }
            }
            Path fullPath = pathFactory.createAbsolutePath(segments);
            Location newLocation = Location.create(fullPath, uuidProperty);
            cache.addNewNode(workspaceId, newLocation);
            return new ActualLocation(newLocation, nodeUuidString, originalEntity);
        }

        // There is no UUID, so look for a path ...
        if (path == null) {
            String propName = DnaLexicon.UUID.getString(getExecutionContext().getNamespaceRegistry());
            String msg = JpaConnectorI18n.locationShouldHavePathAndOrProperty.text(getSourceName(), propName);
            throw new PathNotFoundException(original, pathFactory.createRootPath(), msg);
        }

        // Walk the child entities, starting at the root, down the to the path ...
        if (path.isRoot()) {
            Location newLocation = original.with(rootNodeUuid);
            cache.addNewNode(workspaceId, newLocation);
            return new ActualLocation(newLocation, rootNodeUuidString, null);
        }
        // See if the parent location is known in the cache ...
        Location cachedParent = cache.getLocationFor(workspaceId, path.getParent());
        if (cachedParent != null) {
            // We know the UUID of the parent, so we can find the child a little faster ...
            ChildEntity child = findByPathSegment(workspaceId, cachedParent.getUuid().toString(), path.getLastSegment());
            uuidString = child.getId().getChildUuidString();
            Location newLocation = original.with(UUID.fromString(uuidString));
            cache.addNewNode(workspaceId, newLocation);
            return new ActualLocation(newLocation, uuidString, child);
        }

        // We couldn't find the parent, so we need to search by path ...
        String parentUuid = this.rootNodeUuidString;
        ChildEntity child = null;
        for (Path.Segment segment : path) {
            child = findByPathSegment(workspaceId, parentUuid, segment);
            if (child == null) {
                // Unable to complete the path, so prepare the exception by determining the lowest path that exists ...
                Path lowest = path;
                while (lowest.getLastSegment() != segment) {
                    lowest = lowest.getParent();
                }
                lowest = lowest.getParent();
                throw new PathNotFoundException(original, lowest);
            }
            parentUuid = child.getId().getChildUuidString();
        }
        assert child != null;
        uuidString = child.getId().getChildUuidString();
        Location newLocation = original.with(UUID.fromString(uuidString));
        cache.addNewNode(workspaceId, newLocation);
        return new ActualLocation(newLocation, uuidString, child);
    }

    /**
     * Find the node with the supplied path segment that is a child of the supplied parent.
     *
     * @param workspaceId the ID of the workspace
     * @param parentUuid the UUID of the parent node, in string form
     * @param pathSegment the path segment of the child
     * @return the existing namespace, or null if one does not exist
     * @throws IllegalArgumentException if the manager or URI are null
     */
    protected ChildEntity findByPathSegment( Long workspaceId,
                                             String parentUuid,
                                             Path.Segment pathSegment ) {
        assert namespaces != null;
        assert parentUuid != null;
        assert pathSegment != null;
        assert workspaceId != null;
        Name name = pathSegment.getName();
        String localName = name.getLocalName();
        String nsUri = name.getNamespaceUri();
        NamespaceEntity ns = namespaces.get(nsUri, false);
        if (ns == null) {
            // The namespace can't be found, then certainly the node won't be found ...
            return null;
        }
        int snsIndex = pathSegment.hasIndex() ? pathSegment.getIndex() : 1;
        Query query = entities.createNamedQuery("ChildEntity.findByPathSegment");
        query.setParameter("workspaceId", workspaceId);
        query.setParameter("parentUuidString", parentUuid);
        query.setParameter("ns", ns.getId());
        query.setParameter("childName", localName);
        query.setParameter("sns", snsIndex);
        try {
            return (ChildEntity)query.getSingleResult();
        } catch (NoResultException e) {
            return null;
        }
    }

    protected String createHexValuesString( Collection<String> hexValues ) {
        if (hexValues == null || hexValues.isEmpty()) return null;
        StringBuilder sb = new StringBuilder();
        boolean first = true;
        for (String hexValue : hexValues) {
            if (first) {
                first = false;
            } else {
                sb.append(',');
            }
            sb.append(hexValue);
        }
        return sb.toString();
    }

    protected Collection<String> createHexValues( String hexValuesString ) {
        return Arrays.asList(hexValuesString.split(","));
    }

    protected class LargeValueSerializer implements LargeValues {
        private final PropertiesEntity properties;
        private Set<String> written;

        public LargeValueSerializer( PropertiesEntity entity ) {
            this.properties = entity;
            this.written = null;
        }

        public LargeValueSerializer( PropertiesEntity entity,
                                     Set<String> written ) {
            this.properties = entity;
            this.written = written;
        }

        /**
         * {@inheritDoc}
         *
         * @see org.jboss.dna.connector.store.jpa.util.Serializer.LargeValues#getMinimumSize()
         */
        public long getMinimumSize() {
            return largeValueMinimumSizeInBytes;
        }

        /**
         * {@inheritDoc}
         *
         * @see org.jboss.dna.connector.store.jpa.util.Serializer.LargeValues#read(org.jboss.dna.graph.property.ValueFactories,
         *      byte[], long)
         */
        public Object read( ValueFactories valueFactories,
                            byte[] hash,
                            long length ) throws IOException {
            String hashStr = StringUtil.getHexString(hash);
            // Find the large value ...
            LargeValueId largeValueId = new LargeValueId(hashStr);
            LargeValueEntity entity = entities.find(LargeValueEntity.class, largeValueId);
            if (entity != null) {
                // Find the large value from the existing property entity ...
                byte[] data = entity.getData();
                if (entity.isCompressed()) {
                    InputStream stream = new GZIPInputStream(new ByteArrayInputStream(data));
                    try {
                        data = IoUtil.readBytes(stream);
                    } finally {
                        stream.close();
                    }
                }
                return valueFactories.getValueFactory(entity.getType()).create(data);
            }
            throw new IOException(JpaConnectorI18n.unableToReadLargeValue.text(getSourceName(), hashStr));
        }

        /**
         * {@inheritDoc}
         *
         * @see org.jboss.dna.connector.store.jpa.util.Serializer.LargeValues#write(byte[], long,
         *      org.jboss.dna.graph.property.PropertyType, java.lang.Object)
         */
        public void write( byte[] hash,
                           long length,
                           PropertyType type,
                           Object value ) throws IOException {
            if (value == null) return;
            String hashStr = StringUtil.getHexString(hash);
            if (written != null) written.add(hashStr);

            // Look for an existing value in the collection ...
            final LargeValueId id = new LargeValueId(hashStr);
            for (LargeValueId existing : properties.getLargeValues()) {
                if (existing.equals(id)) {
                    // Already associated with this properties entity
                    return;
                }
            }
            LargeValueEntity entity = entities.find(LargeValueEntity.class, id);
            if (entity == null) {
                // We have to create the large value entity ...
                entity = new LargeValueEntity();
                entity.setCompressed(true);
                entity.setId(id);
                entity.setLength(length);
                entity.setType(type);
                ValueFactories factories = getExecutionContext().getValueFactories();
                byte[] bytes = null;
                switch (type) {
                    case BINARY:
                        Binary binary = factories.getBinaryFactory().create(value);
                        InputStream stream = null;
                        try {
                            binary.acquire();
                            stream = binary.getStream();
                            if (compressData) stream = new GZIPInputStream(stream);
                            bytes = IoUtil.readBytes(stream);
                        } finally {
                            try {
                                if (stream != null) stream.close();
                            } finally {
                                binary.release();
                            }
                        }
                        break;
                    case URI:
                        // This will be treated as a string ...
                    default:
                        String str = factories.getStringFactory().create(value);
                        if (compressData) {
                            ByteArrayOutputStream bs = new ByteArrayOutputStream();
                            OutputStream strStream = new GZIPOutputStream(bs);
                            try {
                                IoUtil.write(str, strStream);
                            } finally {
                                strStream.close();
                            }
                            bytes = bs.toByteArray();
                        } else {
                            bytes = str.getBytes();
                        }
                        break;
                }
                entity.setData(bytes);
                entities.persist(entity);
            }
            // Now associate the large value with the properties entity ...
            assert id.getHash() != null;
            properties.getLargeValues().add(id);
        }

    }

    protected class RecordingLargeValues implements LargeValues {
        protected final Collection<String> readKeys = new HashSet<String>();
        protected final Collection<String> writtenKeys = new HashSet<String>();
        protected final LargeValues delegate;

        RecordingLargeValues( LargeValues delegate ) {
            assert delegate != null;
            this.delegate = delegate;
        }

        /**
         * {@inheritDoc}
         *
         * @see org.jboss.dna.connector.store.jpa.util.Serializer.LargeValues#getMinimumSize()
         */
        public long getMinimumSize() {
            return delegate.getMinimumSize();
        }

        /**
         * {@inheritDoc}
         *
         * @see org.jboss.dna.connector.store.jpa.util.Serializer.LargeValues#read(org.jboss.dna.graph.property.ValueFactories,
         *      byte[], long)
         */
        public Object read( ValueFactories valueFactories,
                            byte[] hash,
                            long length ) throws IOException {
            String key = StringUtil.getHexString(hash);
            readKeys.add(key);
            return delegate.read(valueFactories, hash, length);
        }

        public void write( byte[] hash,
                           long length,
                           PropertyType type,
                           Object value ) throws IOException {
            String key = StringUtil.getHexString(hash);
            writtenKeys.add(key);
            delegate.write(hash, length, type, value);
        }
    }

    protected class SkippedLargeValues implements LargeValues {
        protected Collection<String> skippedKeys = new HashSet<String>();
        protected final LargeValues delegate;

        SkippedLargeValues( LargeValues delegate ) {
            assert delegate != null;
            this.delegate = delegate;
        }

        /**
         * {@inheritDoc}
         *
         * @see org.jboss.dna.connector.store.jpa.util.Serializer.LargeValues#getMinimumSize()
         */
        public long getMinimumSize() {
            return delegate.getMinimumSize();
        }

        /**
         * {@inheritDoc}
         *
         * @see org.jboss.dna.connector.store.jpa.util.Serializer.LargeValues#read(org.jboss.dna.graph.property.ValueFactories,
         *      byte[], long)
         */
        public Object read( ValueFactories valueFactories,
                            byte[] hash,
                            long length ) throws IOException {
            String key = StringUtil.getHexString(hash);
            skippedKeys.add(key);
            return null;
        }

        public void write( byte[] hash,
                           long length,
                           PropertyType type,
                           Object value ) {
            throw new UnsupportedOperationException();
        }
    }

    @Immutable
    protected static class ActualLocation {
        /** The actual location */
        protected final Location location;
        /** The string-form of the UUID, supplied as a convenience. */
        protected final String uuid;
        /** The ChildEntity that represents the location, which may be null if the location represents the root node */
        protected final ChildEntity childEntity;

        protected ActualLocation( Location location,
                                  String uuid,
                                  ChildEntity childEntity ) {
            assert location != null;
            assert uuid != null;
            this.location = location;
            this.uuid = uuid;
            this.childEntity = childEntity;
        }

        /**
         * {@inheritDoc}
         *
         * @see java.lang.Object#toString()
         */
        @Override
        public String toString() {
            return this.location.toString() + " (uuid=" + uuid + ") " + childEntity;
        }
    }

    protected class References implements Serializer.ReferenceValues {
        private Set<Reference> read;
        private Set<Reference> removed;
        private Set<Reference> written;

        protected References() {
        }

        /**
         * {@inheritDoc}
         *
         * @see org.jboss.dna.connector.store.jpa.util.Serializer.ReferenceValues#read(org.jboss.dna.graph.property.Reference)
         */
        public void read( Reference reference ) {
            if (read == null) read = new HashSet<Reference>();
            read.add(reference);
        }

        /**
         * {@inheritDoc}
         *
         * @see org.jboss.dna.connector.store.jpa.util.Serializer.ReferenceValues#remove(org.jboss.dna.graph.property.Reference)
         */
        public void remove( Reference reference ) {
            if (removed == null) removed = new HashSet<Reference>();
            removed.add(reference);
        }

        /**
         * {@inheritDoc}
         *
         * @see org.jboss.dna.connector.store.jpa.util.Serializer.ReferenceValues#write(org.jboss.dna.graph.property.Reference)
         */
        public void write( Reference reference ) {
            if (written == null) written = new HashSet<Reference>();
            written.add(reference);
        }

        public boolean hasRead() {
            return read != null;
        }

        public boolean hasRemoved() {
            return removed != null;
        }

        public boolean hasWritten() {
            return written != null;
        }

        /**
         * @return read
         */
        public Set<Reference> getRead() {
            if (read != null) return read;
            return Collections.emptySet();
        }

        /**
         * @return removed
         */
        public Set<Reference> getRemoved() {
            if (removed != null) return removed;
            return Collections.emptySet();
        }

        /**
         * @return written
         */
        public Set<Reference> getWritten() {
            if (written != null) return written;
            return Collections.emptySet();
        }
    }
}
TOP

Related Classes of org.jboss.dna.connector.store.jpa.model.basic.BasicRequestProcessor

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.