Package org.jboss.dna.repository.sequencer

Source Code of org.jboss.dna.repository.sequencer.SequencingService

/*
* 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.repository.sequencer;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import net.jcip.annotations.Immutable;
import net.jcip.annotations.ThreadSafe;
import org.jboss.dna.common.collection.SimpleProblems;
import org.jboss.dna.common.component.ClassLoaderFactory;
import org.jboss.dna.common.component.ComponentLibrary;
import org.jboss.dna.common.component.StandardClassLoaderFactory;
import org.jboss.dna.common.util.CheckArg;
import org.jboss.dna.common.util.HashCode;
import org.jboss.dna.common.util.Logger;
import org.jboss.dna.graph.ExecutionContext;
import org.jboss.dna.graph.Graph;
import org.jboss.dna.graph.Node;
import org.jboss.dna.graph.connector.RepositorySource;
import org.jboss.dna.graph.observe.ChangeObserver;
import org.jboss.dna.graph.observe.NetChangeObserver;
import org.jboss.dna.graph.observe.NetChangeObserver.NetChange;
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.repository.RepositoryI18n;
import org.jboss.dna.repository.RepositoryLibrary;
import org.jboss.dna.repository.service.AbstractServiceAdministrator;
import org.jboss.dna.repository.service.AdministeredService;
import org.jboss.dna.repository.service.ServiceAdministrator;
import org.jboss.dna.repository.util.RepositoryNodePath;

/**
* A sequencing system is used to monitor changes in the content of DNA repositories and to sequence the content to extract or to
* generate structured information.
*/
public class SequencingService implements AdministeredService {

    /**
     * Interface used to select the set of {@link Sequencer} instances that should be run.
     *
     * @author Randall Hauch
     */
    public static interface Selector {

        /**
         * Select the sequencers that should be used to sequence the supplied node.
         *
         * @param sequencers the list of all sequencers available at the moment; never null
         * @param node the node to be sequenced; never null
         * @param nodeChange the set of node changes; never null
         * @return the list of sequencers that should be used; may not be null
         */
        List<Sequencer> selectSequencers( List<Sequencer> sequencers,
                                          Node node,
                                          NetChange nodeChange );
    }

    /**
     * The default {@link Selector} implementation that selects every sequencer every time it's called, regardless of the node (or
     * logger) supplied.
     *
     * @author Randall Hauch
     */
    protected static class DefaultSelector implements Selector {

        public List<Sequencer> selectSequencers( List<Sequencer> sequencers,
                                                 Node node,
                                                 NetChange nodeChange ) {
            return sequencers;
        }
    }

    /**
     * The default {@link Selector} that considers every {@link Sequencer} to be used for every node.
     *
     * @see SequencingService#setSequencerSelector(org.jboss.dna.repository.sequencer.SequencingService.Selector)
     */
    public static final Selector DEFAULT_SEQUENCER_SELECTOR = new DefaultSelector();

    /**
     * Class loader factory instance that always returns the {@link Thread#getContextClassLoader() current thread's context class
     * loader} (if not null) or component library's class loader.
     */
    protected static final ClassLoaderFactory DEFAULT_CLASSLOADER_FACTORY = new StandardClassLoaderFactory(
                                                                                                           SequencingService.class.getClassLoader());

    /**
     * The administrative component for this service.
     *
     * @author Randall Hauch
     */
    protected class Administrator extends AbstractServiceAdministrator {

        protected Administrator() {
            super(RepositoryI18n.sequencingServiceName, State.PAUSED);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        protected void doStart( State fromState ) {
            super.doStart(fromState);
            startService();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        protected void doShutdown( State fromState ) {
            super.doShutdown(fromState);
            shutdownService();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        protected boolean doCheckIsTerminated() {
            return isServiceTerminated();
        }

        /**
         * {@inheritDoc}
         */
        public boolean awaitTermination( long timeout,
                                         TimeUnit unit ) throws InterruptedException {
            return doAwaitTermination(timeout, unit);
        }

    }

    private ExecutionContext executionContext;
    private SequencerLibrary sequencerLibrary = new SequencerLibrary();
    private Selector sequencerSelector = DEFAULT_SEQUENCER_SELECTOR;
    private ExecutorService executorService;
    private RepositoryLibrary repositoryLibrary;
    private ChangeObserver repositoryObserver;
    private final Statistics statistics = new Statistics();
    private final Administrator administrator = new Administrator();

    /**
     * Create a new sequencing system, configured with no sequencers and not monitoring any workspaces. Upon construction, the
     * system is {@link ServiceAdministrator#isPaused() paused} and must be configured and then
     * {@link ServiceAdministrator#start() started}.
     */
    public SequencingService() {
        this.sequencerLibrary.setClassLoaderFactory(DEFAULT_CLASSLOADER_FACTORY);
    }

    /**
     * Return the administrative component for this service.
     *
     * @return the administrative component; never null
     */
    public ServiceAdministrator getAdministrator() {
        return this.administrator;
    }

    /**
     * Get the statistics for this system.
     *
     * @return statistics
     */
    public Statistics getStatistics() {
        return this.statistics;
    }

    /**
     * @return sequencerLibrary
     */
    protected ComponentLibrary<Sequencer, SequencerConfig> getSequencerLibrary() {
        return this.sequencerLibrary;
    }

    /**
     * Add the configuration for a sequencer, or update any existing one that represents the
     * {@link SequencerConfig#equals(Object) same configuration}
     *
     * @param config the new configuration
     * @return true if the sequencer was added, or false if there already was an existing and
     *         {@link SequencerConfig#hasChanged(SequencerConfig) unchanged} sequencer configuration
     * @throws IllegalArgumentException if <code>config</code> is null
     * @see #updateSequencer(SequencerConfig)
     * @see #removeSequencer(SequencerConfig)
     */
    public boolean addSequencer( SequencerConfig config ) {
        return this.sequencerLibrary.add(config);
    }

    /**
     * Update the configuration for a sequencer, or add it if there is no {@link SequencerConfig#equals(Object) matching
     * configuration}.
     *
     * @param config the updated (or new) configuration
     * @return true if the sequencer was updated, or false if there already was an existing and
     *         {@link SequencerConfig#hasChanged(SequencerConfig) unchanged} sequencer configuration
     * @throws IllegalArgumentException if <code>config</code> is null
     * @see #addSequencer(SequencerConfig)
     * @see #removeSequencer(SequencerConfig)
     */
    public boolean updateSequencer( SequencerConfig config ) {
        return this.sequencerLibrary.update(config);
    }

    /**
     * Remove the configuration for a sequencer.
     *
     * @param config the configuration to be removed
     * @return true if the sequencer was removed, or false if there was no existing sequencer
     * @throws IllegalArgumentException if <code>config</code> is null
     * @see #addSequencer(SequencerConfig)
     * @see #updateSequencer(SequencerConfig)
     */
    public boolean removeSequencer( SequencerConfig config ) {
        return this.sequencerLibrary.remove(config);
    }

    /**
     * @return executionContext
     */
    public ExecutionContext getExecutionContext() {
        return this.executionContext;
    }

    /**
     * @param executionContext Sets executionContext to the specified value.
     */
    public void setExecutionContext( ExecutionContext executionContext ) {
        CheckArg.isNotNull(executionContext, "execution context");
        if (this.getAdministrator().isStarted()) {
            throw new IllegalStateException(RepositoryI18n.unableToChangeExecutionContextWhileRunning.text());
        }
        this.executionContext = executionContext;
        this.sequencerLibrary.setClassLoaderFactory(executionContext);
    }

    /**
     * Get the repository library to be used for repository lookup
     *
     * @return the repository library
     */
    public RepositoryLibrary getRepositoryLibrary() {
        return this.repositoryLibrary;
    }

    public void setRepositoryLibrary( RepositoryLibrary repositoryLibrary ) {
        this.repositoryLibrary = repositoryLibrary;
    }

    /**
     * Get the executor service used to run the sequencers.
     *
     * @return the executor service
     * @see #setExecutorService(ExecutorService)
     */
    public ExecutorService getExecutorService() {
        return this.executorService;
    }

    /**
     * Set the executor service that should be used by this system. By default, the system is set up with a
     * {@link Executors#newSingleThreadExecutor() executor that uses a single thread}.
     *
     * @param executorService the executor service
     * @see #getExecutorService()
     * @see Executors#newCachedThreadPool()
     * @see Executors#newCachedThreadPool(java.util.concurrent.ThreadFactory)
     * @see Executors#newFixedThreadPool(int)
     * @see Executors#newFixedThreadPool(int, java.util.concurrent.ThreadFactory)
     * @see Executors#newScheduledThreadPool(int)
     * @see Executors#newScheduledThreadPool(int, java.util.concurrent.ThreadFactory)
     * @see Executors#newSingleThreadExecutor()
     * @see Executors#newSingleThreadExecutor(java.util.concurrent.ThreadFactory)
     * @see Executors#newSingleThreadScheduledExecutor()
     * @see Executors#newSingleThreadScheduledExecutor(java.util.concurrent.ThreadFactory)
     */
    public void setExecutorService( ExecutorService executorService ) {
        CheckArg.isNotNull(executorService, "executor service");
        if (this.getAdministrator().isStarted()) {
            throw new IllegalStateException(RepositoryI18n.unableToChangeExecutionContextWhileRunning.text());
        }
        this.executorService = executorService;
    }

    /**
     * Override this method to creates a different kind of default executor service. This method is called when the system is
     * {@link #startService() started} without an executor service being {@link #setExecutorService(ExecutorService) set}.
     * <p>
     * This method creates a {@link Executors#newSingleThreadExecutor() single-threaded executor}.
     * </p>
     *
     * @return the executor service
     */
    protected ExecutorService createDefaultExecutorService() {
        return Executors.newSingleThreadExecutor();
    }

    protected void startService() {
        if (this.getExecutionContext() == null) {
            throw new IllegalStateException(RepositoryI18n.unableToStartSequencingServiceWithoutExecutionContext.text());
        }
        if (this.executorService == null) {
            this.executorService = createDefaultExecutorService();
        }
        assert this.executorService != null;
        assert this.sequencerSelector != null;
        assert this.sequencerLibrary != null;
        assert this.repositoryLibrary != null;
        this.repositoryObserver = new RepositoryObserver();
        // Register the observer ...
        this.repositoryLibrary.register(this.repositoryObserver);
    }

    protected void shutdownService() {
        // Unregister our observer ...
        if (this.repositoryObserver != null) this.repositoryObserver.unregister();
        // And shut down the executor service ..
        if (this.executorService != null) {
            this.executorService.shutdown();
        }
    }

    protected boolean isServiceTerminated() {
        if (this.executorService != null) {
            return this.executorService.isTerminated();
        }
        return true;
    }

    protected boolean doAwaitTermination( long timeout,
                                          TimeUnit unit ) throws InterruptedException {
        if (this.executorService == null || this.executorService.isTerminated()) return true;
        return this.executorService.awaitTermination(timeout, unit);
    }

    /**
     * Get the sequencing selector used by this system.
     *
     * @return the sequencing selector
     */
    public Selector getSequencerSelector() {
        return this.sequencerSelector;
    }

    /**
     * Set the sequencer selector, or null if the {@link #DEFAULT_SEQUENCER_SELECTOR default sequencer selector} should be used.
     *
     * @param sequencerSelector the selector
     */
    public void setSequencerSelector( Selector sequencerSelector ) {
        this.sequencerSelector = sequencerSelector != null ? sequencerSelector : DEFAULT_SEQUENCER_SELECTOR;
    }

    /**
     * Do the work of processing by sequencing the node. This method is called by the {@link #executorService executor service}
     * when it performs it's work on the enqueued {@link NetChange NetChange runnable objects}.
     *
     * @param change the change describing the node to be processed.
     */
    protected void processChange( NetChange change ) {
        final ExecutionContext context = this.getExecutionContext();
        final Logger logger = context.getLogger(getClass());
        assert logger != null;

        try {
            final String repositorySourceName = change.getRepositorySourceName();
            final String repositoryWorkspaceName = change.getRepositoryWorkspaceName();

            // Figure out which sequencers accept this path,
            // and track which output nodes should be passed to each sequencer...
            final Path nodePath = change.getPath();
            final String nodePathStr = context.getValueFactories().getStringFactory().create(nodePath);
            Map<SequencerCall, Set<RepositoryNodePath>> sequencerCalls = new HashMap<SequencerCall, Set<RepositoryNodePath>>();
            List<Sequencer> allSequencers = this.sequencerLibrary.getInstances();
            List<Sequencer> sequencers = new ArrayList<Sequencer>(allSequencers.size());
            for (Sequencer sequencer : allSequencers) {
                final SequencerConfig config = sequencer.getConfiguration();
                for (SequencerPathExpression pathExpression : config.getPathExpressions()) {
                    for (Property property : change.getModifiedProperties()) {
                        Name propertyName = property.getName();
                        String propertyNameStr = context.getValueFactories().getStringFactory().create(propertyName);
                        String path = nodePathStr + "/@" + propertyNameStr;
                        SequencerPathExpression.Matcher matcher = pathExpression.matcher(path);
                        if (matcher.matches()) {
                            // String selectedPath = matcher.getSelectedPath();
                            RepositoryNodePath outputPath = RepositoryNodePath.parse(matcher.getOutputPath(),
                                                                                     repositorySourceName,
                                                                                     repositoryWorkspaceName);
                            SequencerCall call = new SequencerCall(sequencer, propertyNameStr);
                            // Record the output path ...
                            Set<RepositoryNodePath> outputPaths = sequencerCalls.get(call);
                            if (outputPaths == null) {
                                outputPaths = new HashSet<RepositoryNodePath>();
                                sequencerCalls.put(call, outputPaths);
                            }
                            outputPaths.add(outputPath);
                            sequencers.add(sequencer);
                            break;
                        }
                    }
                }
            }

            RepositorySource source = repositoryLibrary.getSource(repositorySourceName);
            Graph graph = Graph.create(source, context);
            Node node = null;
            if (!sequencers.isEmpty()) {

                // Find the changed node ...
                node = graph.getNodeAt(nodePath);

                // Figure out which sequencers should run ...
                sequencers = this.sequencerSelector.selectSequencers(sequencers, node, change);
            }
            if (sequencers.isEmpty()) {
                this.statistics.recordNodeSkipped();
                if (logger.isDebugEnabled()) {
                    logger.trace("Skipping '{0}': no sequencers matched this condition", change);
                }
            } else {
                // Run each of those sequencers ...
                for (Map.Entry<SequencerCall, Set<RepositoryNodePath>> entry : sequencerCalls.entrySet()) {

                    final SequencerCall sequencerCall = entry.getKey();
                    final Set<RepositoryNodePath> outputPaths = entry.getValue();
                    final Sequencer sequencer = sequencerCall.getSequencer();
                    final String sequencerName = sequencer.getConfiguration().getName();
                    final String propertyName = sequencerCall.getSequencedPropertyName();

                    // Get the paths to the nodes where the sequencer should write it's output ...
                    assert outputPaths != null && outputPaths.size() != 0;

                    // Create a new execution context for each sequencer
                    final SimpleProblems problems = new SimpleProblems();
                    SequencerContext sequencerContext = new SequencerContext(context, graph);
                    try {
                        sequencer.execute(node, propertyName, change, outputPaths, sequencerContext, problems);
                        sequencerContext.getDestination().submit();
                    } catch (SequencerException e) {
                        logger.error(e, RepositoryI18n.errorWhileSequencingNode, sequencerName, change);
                    }
                }
                this.statistics.recordNodeSequenced();
            }
        } catch (Throwable e) {
            logger.error(e, RepositoryI18n.errorFindingSequencersToRunAgainstNode, change);
        }
    }

    /**
     * The statistics for the system. Each sequencing system has an instance of this class that is updated.
     *
     * @author Randall Hauch
     */
    @ThreadSafe
    public class Statistics {

        private final AtomicLong numberOfNodesSequenced = new AtomicLong(0);
        private final AtomicLong numberOfNodesSkipped = new AtomicLong(0);
        private final AtomicLong startTime;

        protected Statistics() {
            startTime = new AtomicLong(System.currentTimeMillis());
        }

        public Statistics reset() {
            this.startTime.set(System.currentTimeMillis());
            this.numberOfNodesSequenced.set(0);
            this.numberOfNodesSkipped.set(0);
            return this;
        }

        /**
         * @return the system time when the statistics were started
         */
        public long getStartTime() {
            return this.startTime.get();
        }

        /**
         * @return the number of nodes that were sequenced
         */
        public long getNumberOfNodesSequenced() {
            return this.numberOfNodesSequenced.get();
        }

        /**
         * @return the number of nodes that were skipped because no sequencers applied
         */
        public long getNumberOfNodesSkipped() {
            return this.numberOfNodesSkipped.get();
        }

        protected void recordNodeSequenced() {
            this.numberOfNodesSequenced.incrementAndGet();
        }

        protected void recordNodeSkipped() {
            this.numberOfNodesSkipped.incrementAndGet();
        }
    }

    @Immutable
    protected class SequencerCall {

        private final Sequencer sequencer;
        private final String sequencerName;
        private final String sequencedPropertyName;
        private final int hc;

        protected SequencerCall( Sequencer sequencer,
                                 String sequencedPropertyName ) {
            this.sequencer = sequencer;
            this.sequencerName = sequencer.getConfiguration().getName();
            this.sequencedPropertyName = sequencedPropertyName;
            this.hc = HashCode.compute(this.sequencerName, this.sequencedPropertyName);
        }

        /**
         * @return sequencer
         */
        public Sequencer getSequencer() {
            return this.sequencer;
        }

        /**
         * @return sequencedPropertyName
         */
        public String getSequencedPropertyName() {
            return this.sequencedPropertyName;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public int hashCode() {
            return this.hc;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean equals( Object obj ) {
            if (obj == this) return true;
            if (obj instanceof SequencerCall) {
                SequencerCall that = (SequencerCall)obj;
                if (!this.sequencerName.equals(that.sequencerName)) return false;
                if (!this.sequencedPropertyName.equals(that.sequencedPropertyName)) return false;
                return true;
            }
            return false;
        }
    }

    protected class RepositoryObserver extends NetChangeObserver {
        protected RepositoryObserver() {
        }

        /**
         * {@inheritDoc}
         *
         * @see org.jboss.dna.graph.observe.NetChangeObserver#notify(org.jboss.dna.graph.observe.NetChangeObserver.NetChange)
         */
        @Override
        protected void notify( final NetChange change ) {
            // Only care about new nodes or nodes that have new/changed properies ...
            if (change.includes(ChangeType.NODE_ADDED, ChangeType.PROPERTY_ADDED, ChangeType.PROPERTY_CHANGED)) {
                try {
                    getExecutorService().execute(new Runnable() {

                        public void run() {
                            processChange(change);
                        }
                    });
                } catch (RejectedExecutionException e) {
                    // The executor service has been shut down, so do nothing with this set of changes
                }
            }
        }
    }
}
TOP

Related Classes of org.jboss.dna.repository.sequencer.SequencingService

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.