Package org.jboss.dna.graph.request.processor

Source Code of org.jboss.dna.graph.request.processor.RequestProcessor$LocationWithDepth

/*
* 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.graph.request.processor;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import net.jcip.annotations.Immutable;
import net.jcip.annotations.NotThreadSafe;
import org.jboss.dna.common.util.CheckArg;
import org.jboss.dna.graph.ExecutionContext;
import org.jboss.dna.graph.GraphI18n;
import org.jboss.dna.graph.Location;
import org.jboss.dna.graph.cache.CachePolicy;
import org.jboss.dna.graph.connector.LockFailedException;
import org.jboss.dna.graph.observe.Changes;
import org.jboss.dna.graph.observe.Observer;
import org.jboss.dna.graph.property.DateTime;
import org.jboss.dna.graph.property.Name;
import org.jboss.dna.graph.property.Path;
import org.jboss.dna.graph.property.Property;
import org.jboss.dna.graph.property.ReferentialIntegrityException;
import org.jboss.dna.graph.request.AccessQueryRequest;
import org.jboss.dna.graph.request.CacheableRequest;
import org.jboss.dna.graph.request.ChangeRequest;
import org.jboss.dna.graph.request.CloneBranchRequest;
import org.jboss.dna.graph.request.CloneWorkspaceRequest;
import org.jboss.dna.graph.request.CompositeRequest;
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.DeleteChildrenRequest;
import org.jboss.dna.graph.request.DestroyWorkspaceRequest;
import org.jboss.dna.graph.request.FullTextSearchRequest;
import org.jboss.dna.graph.request.GetWorkspacesRequest;
import org.jboss.dna.graph.request.InvalidRequestException;
import org.jboss.dna.graph.request.LockBranchRequest;
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.RemovePropertyRequest;
import org.jboss.dna.graph.request.RenameNodeRequest;
import org.jboss.dna.graph.request.Request;
import org.jboss.dna.graph.request.SetPropertyRequest;
import org.jboss.dna.graph.request.UnlockBranchRequest;
import org.jboss.dna.graph.request.UnsupportedRequestException;
import org.jboss.dna.graph.request.UpdatePropertiesRequest;
import org.jboss.dna.graph.request.UpdateValuesRequest;
import org.jboss.dna.graph.request.VerifyNodeExistsRequest;
import org.jboss.dna.graph.request.VerifyWorkspaceRequest;

/**
* A component that is used to process and execute {@link Request}s. This class is intended to be subclassed and methods
* overwritten to define the behavior for executing the different kinds of requests. Abstract methods must be overridden, but
* non-abstract methods all have meaningful default implementations.
*/
@NotThreadSafe
public abstract class RequestProcessor {

    private final ExecutionContext context;
    private final String sourceName;
    private final DateTime nowInUtc;
    private final CachePolicy defaultCachePolicy;
    private List<ChangeRequest> changes;
    private final Observer observer;

    protected RequestProcessor( String sourceName,
                                ExecutionContext context,
                                Observer observer ) {
        this(sourceName, context, observer, null, null);
    }

    protected RequestProcessor( String sourceName,
                                ExecutionContext context,
                                Observer observer,
                                DateTime now ) {
        this(sourceName, context, observer, now, null);
    }

    protected RequestProcessor( String sourceName,
                                ExecutionContext context,
                                Observer observer,
                                DateTime now,
                                CachePolicy defaultCachePolicy ) {
        CheckArg.isNotEmpty(sourceName, "sourceName");
        CheckArg.isNotNull(context, "context");
        this.context = context;
        this.sourceName = sourceName;
        this.nowInUtc = now != null ? now : context.getValueFactories().getDateFactory().createUtc();
        this.defaultCachePolicy = defaultCachePolicy;
        this.changes = observer != null ? new LinkedList<ChangeRequest>() : null;
        this.observer = observer;
    }

    /**
     * Record the supplied change request for publishing through the event mechanism.
     *
     * @param request the completed change request; may not be null, and may not be cancelled or have an error
     */
    protected void recordChange( ChangeRequest request ) {
        assert request != null;
        assert !request.isCancelled();
        assert !request.hasError();
        if (changes != null) changes.add(request);
    }

    /**
     * Get the name of the source against which this processor is executing.
     *
     * @return the repository source name; never null or empty
     */
    public final String getSourceName() {
        return sourceName;
    }

    /**
     * The execution context that this process is operating within.
     *
     * @return the execution context; never null
     */
    public final ExecutionContext getExecutionContext() {
        return this.context;
    }

    /**
     * Get the 'current time' for this processor, which is usually a constant during its lifetime.
     *
     * @return the current time in UTC; never null
     */
    public final DateTime getNowInUtc() {
        return this.nowInUtc;
    }

    /**
     * @return defaultCachePolicy
     */
    protected final CachePolicy getDefaultCachePolicy() {
        return defaultCachePolicy;
    }

    /**
     * Set the supplied request to have the default cache policy and the {@link #getNowInUtc() current time in UTC}.
     *
     * @param request the cacheable request
     */
    protected void setCacheableInfo( CacheableRequest request ) {
        // Set it only if the request has no cache policy already ...
        if (request.getCachePolicy() == null && defaultCachePolicy != null) {
            request.setCachePolicy(defaultCachePolicy);
        }
        request.setTimeLoaded(nowInUtc);
    }

    /**
     * Set the supplied request to have the supplied cache policy and the {@link #getNowInUtc() current time in UTC}.
     *
     * @param request the cacheable request
     * @param cachePolicy the cache policy for the request; may be null if there is to be no cache policy
     */
    protected void setCacheableInfo( CacheableRequest request,
                                     CachePolicy cachePolicy ) {
        if (cachePolicy == null) cachePolicy = defaultCachePolicy;
        if (cachePolicy != null) {
            if (request.getCachePolicy() != null) {
                // Set the supplied only if less than the current ...
                if (request.getCachePolicy().getTimeToLive() > cachePolicy.getTimeToLive()) {
                    request.setCachePolicy(cachePolicy);
                }
            } else {
                // There is no current policy, so set the supplied policy ...
                request.setCachePolicy(cachePolicy);
            }
        }
        request.setTimeLoaded(nowInUtc);
    }

    /**
     * Process a request by determining the type of request and delegating to the appropriate <code>process</code> method for that
     * type.
     * <p>
     * This method does nothing if the request is null.
     * </p>
     *
     * @param request the general request
     */
    public void process( Request request ) {
        if (request == null) return;
        try {
            if (request.isCancelled()) return;
            if (request instanceof CompositeRequest) {
                process((CompositeRequest)request);
            } else if (request instanceof CopyBranchRequest) {
                process((CopyBranchRequest)request);
            } else if (request instanceof CreateNodeRequest) {
                process((CreateNodeRequest)request);
            } else if (request instanceof DeleteBranchRequest) {
                process((DeleteBranchRequest)request);
            } else if (request instanceof DeleteChildrenRequest) {
                process((DeleteChildrenRequest)request);
            } else if (request instanceof MoveBranchRequest) {
                process((MoveBranchRequest)request);
            } else if (request instanceof ReadAllChildrenRequest) {
                process((ReadAllChildrenRequest)request);
            } else if (request instanceof ReadNextBlockOfChildrenRequest) {
                process((ReadNextBlockOfChildrenRequest)request);
            } else if (request instanceof ReadBlockOfChildrenRequest) {
                process((ReadBlockOfChildrenRequest)request);
            } else if (request instanceof ReadBranchRequest) {
                process((ReadBranchRequest)request);
            } else if (request instanceof ReadNodeRequest) {
                process((ReadNodeRequest)request);
            } else if (request instanceof ReadAllPropertiesRequest) {
                process((ReadAllPropertiesRequest)request);
            } else if (request instanceof ReadPropertyRequest) {
                process((ReadPropertyRequest)request);
            } else if (request instanceof RemovePropertyRequest) {
                process((RemovePropertyRequest)request);
            } else if (request instanceof SetPropertyRequest) {
                process((SetPropertyRequest)request);
            } else if (request instanceof RenameNodeRequest) {
                process((RenameNodeRequest)request);
            } else if (request instanceof UpdatePropertiesRequest) {
                process((UpdatePropertiesRequest)request);
            } else if (request instanceof VerifyNodeExistsRequest) {
                process((VerifyNodeExistsRequest)request);
            } else if (request instanceof LockBranchRequest) {
                process((LockBranchRequest)request);
            } else if (request instanceof UnlockBranchRequest) {
                process((UnlockBranchRequest)request);
            } else if (request instanceof VerifyWorkspaceRequest) {
                process((VerifyWorkspaceRequest)request);
            } else if (request instanceof GetWorkspacesRequest) {
                process((GetWorkspacesRequest)request);
            } else if (request instanceof CreateWorkspaceRequest) {
                process((CreateWorkspaceRequest)request);
            } else if (request instanceof CloneBranchRequest) {
                process((CloneBranchRequest)request);
            } else if (request instanceof CloneWorkspaceRequest) {
                process((CloneWorkspaceRequest)request);
            } else if (request instanceof DestroyWorkspaceRequest) {
                process((DestroyWorkspaceRequest)request);
            } else if (request instanceof UpdateValuesRequest) {
                process((UpdateValuesRequest)request);
            } else if (request instanceof AccessQueryRequest) {
                process((AccessQueryRequest)request);
            } else if (request instanceof FullTextSearchRequest) {
                process((FullTextSearchRequest)request);
            } else {
                processUnknownRequest(request);
            }
        } catch (RuntimeException e) {
            request.setError(e);
        } finally {
            completeRequest(request);
        }
    }

    protected void completeRequest( Request request ) {
        request.freeze();
    }

    /**
     * Process a request that is composed of multiple other (non-composite) requests. If any of the embedded requests
     * {@link Request#hasError() has an error} after it is processed, each of the embedded requests will be marked with the error
     * <i>and</i> the submitted composite request will be marked with an error. If one of the embedded requests attempts to
     * {@link Request#isReadOnly() make a change} and results in an error, then the processing of all remaining embedded requests
     * is aborted and the composite request will be marked with only this error from the change request.
     * <p>
     * This method does nothing if the request is null.
     * </p>
     *
     * @param request the composite request
     */
    public void process( CompositeRequest request ) {
        if (request == null) return;
        boolean hasErrors = false;
        boolean readonly = request.isReadOnly();
        // Iterate over the requests in this composite, but only iterate once so that
        for (Request embedded : request) {
            assert embedded != null;
            if (embedded.isCancelled()) return;
            process(embedded);
            if (!hasErrors && embedded.hasError()) {
                hasErrors = true;
                if (!readonly && !embedded.isReadOnly()) {
                    // The request is trying to make changes, and this embedded was a change that resulted in an error.
                    // Therefore, the whole execution needs to stop and we should set this one error message
                    // on the composite request ...
                    assert embedded.getError() != null;
                    request.setError(embedded.getError());
                    return;
                }
            }
        }
        if (hasErrors) {
            request.checkForErrors();
        }
    }

    /**
     * Method that is called by {@link #process(Request)} when the request was found to be of a request type that is not known by
     * this processor. By default this method sets an {@link UnsupportedRequestException unsupported request error} on the
     * request.
     *
     * @param request the unknown request
     */
    protected void processUnknownRequest( Request request ) {
        request.setError(new InvalidRequestException(GraphI18n.unsupportedRequestType.text(request.getClass().getName(), request)));
    }

    /**
     * Process a request to verify a named workspace.
     * <p>
     * This method does nothing if the request is null.
     * </p>
     *
     * @param request the request
     */
    public abstract void process( VerifyWorkspaceRequest request );

    /**
     * Process a request to get the information about the available workspaces.
     * <p>
     * This method does nothing if the request is null.
     * </p>
     *
     * @param request the request
     */
    public abstract void process( GetWorkspacesRequest request );

    /**
     * Process a request to create a new workspace.
     * <p>
     * This method does nothing if the request is null.
     * </p>
     *
     * @param request the request
     */
    public abstract void process( CreateWorkspaceRequest request );

    /**
     * Process a request to clone a branch into a new workspace.
     * <p>
     * This method does nothing if the request is null.
     * </p>
     *
     * @param request the request
     */
    public abstract void process( CloneBranchRequest request );

    /**
     * Process a request to clone an existing workspace as a new workspace.
     * <p>
     * This method does nothing if the request is null.
     * </p>
     *
     * @param request the request
     */
    public abstract void process( CloneWorkspaceRequest request );

    /**
     * Process a request to permanently destroy a workspace.
     * <p>
     * This method does nothing if the request is null.
     * </p>
     *
     * @param request the request
     */
    public abstract void process( DestroyWorkspaceRequest request );

    /**
     * Process a request to copy a branch into another location.
     * <p>
     * This method does nothing if the request is null.
     * </p>
     *
     * @param request the copy request
     */
    public abstract void process( CopyBranchRequest request );

    /**
     * Process a request to create a node at a specified location.
     * <p>
     * This method does nothing if the request is null.
     * </p>
     *
     * @param request the create request
     */
    public abstract void process( CreateNodeRequest request );

    /**
     * Process a request to delete a branch at a specified location.
     * <p>
     * This method does nothing if the request is null.
     * </p>
     *
     * @param request the delete request
     * @throws ReferentialIntegrityException if the delete could not be performed because some references to deleted nodes would
     *         have remained after the delete operation completed
     */
    public abstract void process( DeleteBranchRequest request );

    /**
     * Process a request to delete all of the child nodes under the supplied existing node.
     * <p>
     * This method does nothing if the request is null.
     * </p>
     *
     * @param request the delete request
     * @throws ReferentialIntegrityException if the delete could not be performed because some references to deleted nodes would
     *         have remained after the delete operation completed
     */
    public void process( DeleteChildrenRequest request ) {
        if (request == null) return;
        if (request.isCancelled()) return;
        // First get all of the children under the node ...
        ReadAllChildrenRequest readChildren = new ReadAllChildrenRequest(request.at(), request.inWorkspace());
        process(readChildren);
        if (readChildren.hasError()) {
            request.setError(readChildren.getError());
            return;
        }
        if (readChildren.isCancelled()) return;

        // Issue a DeleteBranchRequest for each child ...
        for (Location child : readChildren) {
            if (request.isCancelled()) return;
            DeleteBranchRequest deleteChild = new DeleteBranchRequest(child, request.inWorkspace());
            process(deleteChild);
            request.addDeletedChild(child);
        }

        // Set the actual location of the parent node ...
        request.setActualLocationOfNode(readChildren.getActualLocationOfNode());
    }

    /**
     * Process a request to move a branch at a specified location into a different location.
     * <p>
     * This method does nothing if the request is null.
     * </p>
     *
     * @param request the move request
     */
    public abstract void process( MoveBranchRequest request );

    /**
     * Process a request to read all of the children of a node.
     * <p>
     * This method does nothing if the request is null.
     * </p>
     *
     * @param request the read request
     */
    public abstract void process( ReadAllChildrenRequest request );

    /**
     * Process a request to read a block of the children of a node. The block is defined by a
     * {@link ReadBlockOfChildrenRequest#startingAtIndex() starting index} and a {@link ReadBlockOfChildrenRequest#count() maximum
     * number of children to include in the block}.
     * <p>
     * This method does nothing if the request is null. The default implementation converts the command to a
     * {@link ReadAllChildrenRequest}, and then finds the children within the block. Obviously for large numbers of children, this
     * implementation may not be efficient and may need to be overridden.
     * </p>
     *
     * @param request the read request
     */
    public void process( ReadBlockOfChildrenRequest request ) {
        if (request == null) return;
        // Convert the request to a ReadAllChildrenRequest and execute it ...
        ReadAllChildrenRequest readAll = new ReadAllChildrenRequest(request.of(), request.inWorkspace());
        process(readAll);
        if (readAll.hasError()) {
            request.setError(readAll.getError());
            return;
        }
        List<Location> allChildren = readAll.getChildren();

        // If there aren't enough children for the block's range ...
        if (allChildren.size() < request.startingAtIndex()) return;

        // Now, find the children in the block ...
        int endIndex = Math.min(request.endingBefore(), allChildren.size());
        for (int i = request.startingAtIndex(); i != endIndex; ++i) {
            request.addChild(allChildren.get(i));
        }
        // Set the actual location ...
        request.setActualLocationOfNode(readAll.getActualLocationOfNode());
        setCacheableInfo(request);
    }

    /**
     * Process a request to read the next block of the children of a node, starting after a previously-retrieved child.
     * <p>
     * This method does nothing if the request is null. The default implementation converts the command to a
     * {@link ReadAllChildrenRequest}, and then finds the children within the block. Obviously for large numbers of children, this
     * implementation may not be efficient and may need to be overridden.
     * </p>
     *
     * @param request the read request
     */
    public void process( ReadNextBlockOfChildrenRequest request ) {
        if (request == null) return;

        // Get the parent path ...
        Location actualSiblingLocation = request.startingAfter();
        Path path = actualSiblingLocation.getPath();
        Path parentPath = null;
        if (path != null) parentPath = path.getParent();
        if (parentPath == null) {
            // Need to find the parent path, so get the actual location of the sibling ...
            VerifyNodeExistsRequest verifySibling = new VerifyNodeExistsRequest(request.startingAfter(), request.inWorkspace());
            process(verifySibling);
            actualSiblingLocation = verifySibling.getActualLocationOfNode();
            parentPath = actualSiblingLocation.getPath().getParent();
        }
        assert parentPath != null;

        // Convert the request to a ReadAllChildrenRequest and execute it ...
        ReadAllChildrenRequest readAll = new ReadAllChildrenRequest(Location.create(parentPath), request.inWorkspace());
        process(readAll);
        if (readAll.hasError()) {
            request.setError(readAll.getError());
            return;
        }
        List<Location> allChildren = readAll.getChildren();

        // Iterate through the children, looking for the 'startingAfter' child ...
        boolean found = false;
        int count = 0;
        for (Location child : allChildren) {
            if (count > request.count()) break;
            if (!found) {
                // Set to true if we find the child we're looking for ...
                found = child.isSame(request.startingAfter());
            } else {
                // Add the child to the block ...
                ++count;
                request.addChild(child);
            }
        }

        // Set the actual location ...
        request.setActualLocationOfStartingAfterNode(actualSiblingLocation);
        setCacheableInfo(request);
    }

    /**
     * Process a request to read a branch or subgraph that's below a node at a specified location.
     * <p>
     * This method does nothing if the request is null. The default implementation processes the branch by submitting the
     * equivalent requests to {@link ReadNodeRequest read the nodes} and the {@link ReadAllChildrenRequest children}. It starts by
     * doing this for the top-level node, then proceeds for each of the children of that node, and so forth.
     * </p>
     *
     * @param request the request to read the branch
     */
    public void process( ReadBranchRequest request ) {
        if (request == null) return;
        // Create a queue for locations that need to be read ...
        Queue<LocationWithDepth> locationsToRead = new LinkedList<LocationWithDepth>();
        locationsToRead.add(new LocationWithDepth(request.at(), 1));

        // Now read the locations ...
        boolean first = true;
        while (locationsToRead.peek() != null) {
            if (request.isCancelled()) return;
            LocationWithDepth read = locationsToRead.poll();

            // Check the depth ...
            if (read.depth > request.maximumDepth()) break;

            // Read the properties ...
            ReadNodeRequest readNode = new ReadNodeRequest(read.location, request.inWorkspace());
            process(readNode);
            if (readNode.hasError()) {
                request.setError(readNode.getError());
                return;
            }
            Location actualLocation = readNode.getActualLocationOfNode();
            if (first) {
                // Set the actual location on the original request
                request.setActualLocationOfNode(actualLocation);
                first = false;
            }

            // Record in the request the children and properties that were read on this node ...
            request.setChildren(actualLocation, readNode.getChildren());
            request.setProperties(actualLocation, readNode.getProperties());

            // Add each of the children to the list of locations that we need to read ...
            for (Location child : readNode.getChildren()) {
                locationsToRead.add(new LocationWithDepth(child, read.depth + 1));
            }
        }
        setCacheableInfo(request);
    }

    /**
     * Process a request to read the properties of a node at the supplied location.
     * <p>
     * This method does nothing if the request is null.
     * </p>
     *
     * @param request the read request
     */
    public abstract void process( ReadAllPropertiesRequest request );

    /**
     * Process a request to read the properties and children of a node at the supplied location.
     * <p>
     * This method does nothing if the request is null. Unless overridden, this method converts the single request into a
     * {@link ReadAllChildrenRequest} and a {@link ReadAllPropertiesRequest}.
     * </p>
     *
     * @param request the read request
     */
    public void process( ReadNodeRequest request ) {
        if (request == null) return;
        // Read the properties ...
        ReadAllPropertiesRequest readProperties = new ReadAllPropertiesRequest(request.at(), request.inWorkspace());
        process(readProperties);
        if (readProperties.hasError()) {
            request.setError(readProperties.getError());
            return;
        }
        // Set the actual location ...
        request.setActualLocationOfNode(readProperties.getActualLocationOfNode());

        // Read the children ...
        ReadAllChildrenRequest readChildren = new ReadAllChildrenRequest(request.at(), request.inWorkspace());
        process(readChildren);
        if (readChildren.hasError()) {
            request.setError(readChildren.getError());
            return;
        }
        if (request.isCancelled()) return;
        // Now, copy all of the results into the submitted request ...
        for (Property property : readProperties) {
            request.addProperty(property);
        }
        for (Location child : readChildren) {
            request.addChild(child);
        }
        setCacheableInfo(request);
    }

    /**
     * Process a request to read a single property of a node at the supplied location.
     * <p>
     * This method does nothing if the request is null. Unless overridden, this method converts the request that
     * {@link ReadAllPropertiesRequest reads the node} and simply returns the one property.
     * </p>
     *
     * @param request the read request
     */
    public void process( ReadPropertyRequest request ) {
        if (request == null) return;
        ReadAllPropertiesRequest readNode = new ReadAllPropertiesRequest(request.on(), request.inWorkspace());
        process(readNode);
        if (readNode.hasError()) {
            request.setError(readNode.getError());
            return;
        }
        Property property = readNode.getPropertiesByName().get(request.named());
        request.setProperty(property);
        // Set the actual location ...
        request.setActualLocationOfNode(readNode.getActualLocationOfNode());
        setCacheableInfo(request);
    }

    /**
     * Process a request to verify that a node exists at the supplied location.
     * <p>
     * This method does nothing if the request is null. Unless overridden, this method converts the request that
     * {@link ReadAllPropertiesRequest reads the node} and uses the result to determine if the node exists.
     * </p>
     *
     * @param request the read request
     */
    public void process( VerifyNodeExistsRequest request ) {
        if (request == null) return;
        ReadAllPropertiesRequest readNode = new ReadAllPropertiesRequest(request.at(), request.inWorkspace());
        process(readNode);
        if (readNode.hasError()) {
            request.setError(readNode.getError());
            return;
        }
        // Set the actual location ...
        request.setActualLocationOfNode(readNode.getActualLocationOfNode());
        setCacheableInfo(request);
    }

    /**
     * Process a request to remove the specified property from a node.
     * <p>
     * This method does nothing if the request is null. Unless overridden, this method converts this request into a
     * {@link UpdatePropertiesRequest}.
     * </p>
     *
     * @param request the request to remove the property
     */
    public void process( RemovePropertyRequest request ) {
        if (request == null) return;
        Map<Name, Property> properties = Collections.singletonMap(request.propertyName(), null);
        UpdatePropertiesRequest update = new UpdatePropertiesRequest(request.from(), request.inWorkspace(), properties);
        process(update);
        if (update.hasError()) {
            request.setError(update.getError());
        }
        // Set the actual location ...
        request.setActualLocationOfNode(update.getActualLocationOfNode());
    }

    /**
     * Process a request to set the specified property on a node.
     * <p>
     * This method does nothing if the request is null. Unless overridden, this method converts this request into a
     * {@link UpdatePropertiesRequest}.
     * </p>
     *
     * @param request the request to set the property
     */
    public void process( SetPropertyRequest request ) {
        if (request == null) return;
        Property property = request.property();
        Map<Name, Property> properties = Collections.singletonMap(property.getName(), property);
        UpdatePropertiesRequest update = new UpdatePropertiesRequest(request.on(), request.inWorkspace(), properties);
        process(update);
        if (update.hasError()) {
            request.setError(update.getError());
        } else {
            // Set the actual location and created flags ...
            request.setActualLocationOfNode(update.getActualLocationOfNode());
            request.setNewProperty(update.isNewProperty(property.getName()));
        }
    }

    /**
     * Process a request to remove the specified properties from a node.
     * <p>
     * This method does nothing if the request is null.
     * </p>
     *
     * @param request the remove request
     */
    public abstract void process( UpdatePropertiesRequest request );

    /**
     * Process a request to add and/or remove the specified values from a property on the given node.
     * <p>
     * This method does nothing if the request is null.
     * </p>
     *
     * @param request the remove request
     */
    public void process( UpdateValuesRequest request ) {
        String workspaceName = request.inWorkspace();
        Location on = request.on();
        Name propertyName = request.property();

        // Read in the current values
        ReadPropertyRequest readProperty = new ReadPropertyRequest(on, workspaceName, propertyName);
        process(readProperty);

        if (readProperty.hasError()) {
            request.setError(readProperty.getError());
            return;
        }

        Property property = readProperty.getProperty();
        List<Object> actualRemovedValues = new ArrayList<Object>(request.removedValues().size());
        List<Object> newValues = property == null ? new LinkedList<Object>() : new LinkedList<Object>(
                                                                                                      Arrays.asList(property.getValuesAsArray()));
        // Calculate what the new values should be
        for (Object removedValue : request.removedValues()) {
            for (Iterator<Object> iter = newValues.iterator(); iter.hasNext();) {
                if (iter.next().equals(removedValue)) {
                    iter.remove();
                    actualRemovedValues.add(removedValue);
                    break;
                }
            }
        }

        newValues.addAll(request.addedValues());
        Property newProperty = getExecutionContext().getPropertyFactory().create(propertyName, newValues);

        // Update the current values
        SetPropertyRequest setProperty = new SetPropertyRequest(on, workspaceName, newProperty);
        process(setProperty);

        if (setProperty.hasError()) {
            request.setError(setProperty.getError());
        } else {
            // Set the actual location and property
            request.setActualLocation(setProperty.getActualLocationOfNode(), request.addedValues(), actualRemovedValues);
            request.setActualProperty(newProperty, setProperty.isNewProperty());
        }

    }

    /**
     * Process a request to rename a node specified location into a different location.
     * <p>
     * This method does nothing if the request is null. Unless overridden, this method converts the rename into a
     * {@link MoveBranchRequest move}. However, this only works if the <code>request</code> has a {@link Location#hasPath() path}
     * for its {@link RenameNodeRequest#at() location}. (If not, this method throws an {@link UnsupportedOperationException} and
     * must be overridden.)
     * </p>
     *
     * @param request the rename request
     */
    public void process( RenameNodeRequest request ) {
        if (request == null) return;
        Location from = request.at();
        if (!from.hasPath()) {
            throw new UnsupportedOperationException();
        }
        Path newPath = getExecutionContext().getValueFactories().getPathFactory().create(from.getPath(), request.toName());
        Location to = Location.create(newPath);
        MoveBranchRequest move = new MoveBranchRequest(from, to, request.inWorkspace());
        process(move);
        // Set the actual locations ...
        request.setActualLocations(move.getActualLocationBefore(), move.getActualLocationAfter());
    }

    /**
     * Process a request to lock a node or branch within a workspace
     * <p>
     * The default implementation of this method does nothing, as most connectors will not support locking. Any implementation of
     * this method should do nothing if the request is null.
     * </p>
     * <p>
     * Implementations that do support locking should throw a {@link LockFailedException} if the request could not be fulfilled.
     * </p>
     *
     * @param request the request
     */
    public void process( LockBranchRequest request ) {
        Location actualLocation = request.at();
        if (!actualLocation.hasPath()) {
            VerifyNodeExistsRequest nodeExists = new VerifyNodeExistsRequest(request.at(), request.inWorkspace());

            process(nodeExists);

            if (nodeExists.hasError()) {
                request.setError(nodeExists.getError());
                return;
            }

            actualLocation = nodeExists.getActualLocationOfNode();
        }

        request.setActualLocation(actualLocation);
    }

    /**
     * Process a request to unlock a node or branch within a workspace
     * <p>
     * The default implementation of this method does nothing, as most connectors will not support locking. Any implementation of
     * this method should do nothing if the request is null.
     * </p>
     *
     * @param request the request
     */
    public void process( UnlockBranchRequest request ) {
        Location actualLocation = request.at();
        if (!actualLocation.hasPath()) {
            VerifyNodeExistsRequest nodeExists = new VerifyNodeExistsRequest(request.at(), request.inWorkspace());

            process(nodeExists);

            if (nodeExists.hasError()) {
                request.setError(nodeExists.getError());
                return;
            }

            actualLocation = nodeExists.getActualLocationOfNode();
        }

        request.setActualLocation(actualLocation);
    }

    /**
     * Process a request to query a workspace with an access query, which is is a low-level atomic query that is part of a larger,
     * planned query.
     * <p>
     * The default implementation of this method behaves as though the implementation does not support queries by setting an error
     * on the request
     * </p>
     *
     * @param request the request
     */
    public void process( AccessQueryRequest request ) {
        processUnknownRequest(request);
    }

    /**
     * Process a request to search a workspace.
     * <p>
     * The default implementation of this method behaves as though the implementation does not support full-text searches by
     * setting an error on the request
     * </p>
     *
     * @param request the request
     */
    public void process( FullTextSearchRequest request ) {
        processUnknownRequest(request);
    }

    /**
     * Close this processor, allowing it to clean up any open resources.
     */
    public void close() {
        // do nothing by default
    }

    /**
     * Obtain the list of {@link ChangeRequest}s that were successfully processed by this processor.
     * <p>
     * Note that this list is modified during processing and thus should only be accessed by the caller when this processor has
     * been {@link #close() closed}.
     * </p>
     * <p>
     * Also, if this processor encounters errors while processing {@link Request#isReadOnly() change requests}, the processor does
     * not throw out any of the changes. Thus it is up to the caller to decide whether any of the changes are to be kept.
     * </p>
     *
     * @return the list of successful changes; never null but possibly empty.
     */
    public List<ChangeRequest> getChanges() {
        return changes;
    }

    /**
     * Take any of the changes that have been accumulated by this processor and notify the observer. This should only be called
     * after {@link #close()} has been called.
     */
    public void notifyObserverOfChanges() {
        if (observer == null) {
            if (changes != null) changes.clear();
            return;
        }
        if (this.changes.isEmpty()) return;
        // then publish any changes ...
        String userName = context.getSecurityContext() != null ? context.getSecurityContext().getUserName() : null;
        if (userName == null) userName = "";
        String contextId = context.getId();
        String processId = null;
        Changes changes = new Changes(processId, contextId, userName, getSourceName(), getNowInUtc(), this.changes);
        observer.notify(changes);
        // Null the list, since this should have been closed
        this.changes = null;
    }

    /**
     * A class that represents a location at a known depth
     *
     * @author Randall Hauch
     */
    @Immutable
    protected class LocationWithDepth {
        protected final Location location;
        protected final int depth;

        public LocationWithDepth( Location location,
                                  int depth ) {
            this.location = location;
            this.depth = depth;
        }

        public Location getLocation() {
            return location;
        }

        public int getDepth() {
            return depth;
        }

        @Override
        public int hashCode() {
            return location.hashCode();
        }

        @Override
        public String toString() {
            return location.toString() + " at depth " + depth;
        }
    }

}
TOP

Related Classes of org.jboss.dna.graph.request.processor.RequestProcessor$LocationWithDepth

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.