001    /*
002     * JBoss, Home of Professional Open Source.
003     * Copyright 2008, Red Hat Middleware LLC, and individual contributors
004     * as indicated by the @author tags. See the copyright.txt file in the
005     * distribution for a full listing of individual contributors. 
006     *
007     * This is free software; you can redistribute it and/or modify it
008     * under the terms of the GNU Lesser General Public License as
009     * published by the Free Software Foundation; either version 2.1 of
010     * the License, or (at your option) any later version.
011     *
012     * This software is distributed in the hope that it will be useful,
013     * but WITHOUT ANY WARRANTY; without even the implied warranty of
014     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015     * Lesser General Public License for more details.
016     *
017     * You should have received a copy of the GNU Lesser General Public
018     * License along with this software; if not, write to the Free
019     * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020     * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
021     */
022    package org.jboss.dna.repository.sequencers;
023    
024    import java.security.AccessControlContext;
025    import java.util.ArrayList;
026    import java.util.HashMap;
027    import java.util.HashSet;
028    import java.util.List;
029    import java.util.Map;
030    import java.util.Set;
031    import java.util.concurrent.ExecutorService;
032    import java.util.concurrent.Executors;
033    import java.util.concurrent.RejectedExecutionException;
034    import java.util.concurrent.TimeUnit;
035    import java.util.concurrent.atomic.AtomicBoolean;
036    import java.util.concurrent.atomic.AtomicLong;
037    import javax.jcr.Node;
038    import javax.jcr.Repository;
039    import javax.jcr.RepositoryException;
040    import javax.jcr.Session;
041    import javax.jcr.observation.Event;
042    import javax.security.auth.Subject;
043    import javax.security.auth.login.LoginContext;
044    import net.jcip.annotations.Immutable;
045    import net.jcip.annotations.ThreadSafe;
046    import org.jboss.dna.common.component.ClassLoaderFactory;
047    import org.jboss.dna.common.component.ComponentLibrary;
048    import org.jboss.dna.common.component.StandardClassLoaderFactory;
049    import org.jboss.dna.common.monitor.LoggingProgressMonitor;
050    import org.jboss.dna.common.monitor.ProgressMonitor;
051    import org.jboss.dna.common.monitor.SimpleProgressMonitor;
052    import org.jboss.dna.common.util.CheckArg;
053    import org.jboss.dna.common.util.HashCode;
054    import org.jboss.dna.common.util.Logger;
055    import org.jboss.dna.graph.properties.NamespaceRegistry;
056    import org.jboss.dna.graph.properties.PropertyFactory;
057    import org.jboss.dna.graph.properties.ValueFactories;
058    import org.jboss.dna.repository.RepositoryI18n;
059    import org.jboss.dna.repository.observation.NodeChange;
060    import org.jboss.dna.repository.observation.NodeChangeListener;
061    import org.jboss.dna.repository.observation.NodeChanges;
062    import org.jboss.dna.repository.services.AbstractServiceAdministrator;
063    import org.jboss.dna.repository.services.AdministeredService;
064    import org.jboss.dna.repository.services.ServiceAdministrator;
065    import org.jboss.dna.repository.util.JcrExecutionContext;
066    import org.jboss.dna.repository.util.JcrTools;
067    import org.jboss.dna.repository.util.RepositoryNodePath;
068    import org.jboss.dna.repository.util.SessionFactory;
069    
070    /**
071     * A sequencing system is used to monitor changes in the content of {@link Repository JCR repositories} and to sequence the
072     * content to extract or to generate structured information.
073     * 
074     * @author Randall Hauch
075     */
076    public class SequencingService implements AdministeredService, NodeChangeListener {
077    
078        /**
079         * Interface used to select the set of {@link Sequencer} instances that should be run.
080         * 
081         * @author Randall Hauch
082         */
083        public static interface Selector {
084    
085            /**
086             * Select the sequencers that should be used to sequence the supplied node.
087             * 
088             * @param sequencers the list of all sequencers available at the moment; never null
089             * @param node the node to be sequenced; never null
090             * @param nodeChange the set of node changes; never null
091             * @return the list of sequencers that should be used; may not be null
092             */
093            List<Sequencer> selectSequencers( List<Sequencer> sequencers,
094                                              Node node,
095                                              NodeChange nodeChange );
096        }
097    
098        /**
099         * The default {@link Selector} implementation that selects every sequencer every time it's called, regardless of the node (or
100         * logger) supplied.
101         * 
102         * @author Randall Hauch
103         */
104        protected static class DefaultSelector implements Selector {
105    
106            public List<Sequencer> selectSequencers( List<Sequencer> sequencers,
107                                                     Node node,
108                                                     NodeChange nodeChange ) {
109                return sequencers;
110            }
111        }
112    
113        /**
114         * Interface used to determine whether a {@link NodeChange} should be processed.
115         * 
116         * @author Randall Hauch
117         */
118        public static interface NodeFilter {
119    
120            /**
121             * Determine whether the node represented by the supplied change should be submitted for sequencing.
122             * 
123             * @param nodeChange the node change event
124             * @return true if the node should be submitted for sequencing, or false if the change should be ignored
125             */
126            boolean accept( NodeChange nodeChange );
127        }
128    
129        /**
130         * The default filter implementation, which accepts only new nodes or nodes that have new or changed properties.
131         * 
132         * @author Randall Hauch
133         */
134        protected static class DefaultNodeFilter implements NodeFilter {
135    
136            public boolean accept( NodeChange nodeChange ) {
137                // Only care about new nodes or nodes that have new/changed properies ...
138                return nodeChange.includesEventTypes(Event.NODE_ADDED, Event.PROPERTY_ADDED, Event.PROPERTY_CHANGED);
139            }
140        }
141    
142        /**
143         * The default {@link Selector} that considers every {@link Sequencer} to be used for every node.
144         * 
145         * @see SequencingService#setSequencerSelector(org.jboss.dna.repository.sequencers.SequencingService.Selector)
146         */
147        public static final Selector DEFAULT_SEQUENCER_SELECTOR = new DefaultSelector();
148        /**
149         * The default {@link NodeFilter} that accepts new nodes or nodes that have new/changed properties.
150         * 
151         * @see SequencingService#setSequencerSelector(org.jboss.dna.repository.sequencers.SequencingService.Selector)
152         */
153        public static final NodeFilter DEFAULT_NODE_FILTER = new DefaultNodeFilter();
154    
155        /**
156         * Class loader factory instance that always returns the {@link Thread#getContextClassLoader() current thread's context class
157         * loader} (if not null) or component library's class loader.
158         */
159        protected static final ClassLoaderFactory DEFAULT_CLASSLOADER_FACTORY = new StandardClassLoaderFactory(
160                                                                                                               SequencingService.class.getClassLoader());
161    
162        /**
163         * The administrative component for this service.
164         * 
165         * @author Randall Hauch
166         */
167        protected class Administrator extends AbstractServiceAdministrator {
168    
169            protected Administrator() {
170                super(RepositoryI18n.sequencingServiceName, State.PAUSED);
171            }
172    
173            /**
174             * {@inheritDoc}
175             */
176            @Override
177            protected void doStart( State fromState ) {
178                super.doStart(fromState);
179                startService();
180            }
181    
182            /**
183             * {@inheritDoc}
184             */
185            @Override
186            protected void doShutdown( State fromState ) {
187                super.doShutdown(fromState);
188                shutdownService();
189            }
190    
191            /**
192             * {@inheritDoc}
193             */
194            @Override
195            protected boolean doCheckIsTerminated() {
196                return isServiceTerminated();
197            }
198    
199            /**
200             * {@inheritDoc}
201             */
202            public boolean awaitTermination( long timeout,
203                                             TimeUnit unit ) throws InterruptedException {
204                return doAwaitTermination(timeout, unit);
205            }
206    
207        }
208    
209        private JcrExecutionContext executionContext;
210        private SequencerLibrary sequencerLibrary = new SequencerLibrary();
211        private Selector sequencerSelector = DEFAULT_SEQUENCER_SELECTOR;
212        private NodeFilter nodeFilter = DEFAULT_NODE_FILTER;
213        private ExecutorService executorService;
214        private final Statistics statistics = new Statistics();
215        private final Administrator administrator = new Administrator();
216    
217        /**
218         * Create a new sequencing system, configured with no sequencers and not monitoring any workspaces. Upon construction, the
219         * system is {@link ServiceAdministrator#isPaused() paused} and must be configured and then
220         * {@link ServiceAdministrator#start() started}.
221         */
222        public SequencingService() {
223            this.sequencerLibrary.setClassLoaderFactory(DEFAULT_CLASSLOADER_FACTORY);
224        }
225    
226        /**
227         * Return the administrative component for this service.
228         * 
229         * @return the administrative component; never null
230         */
231        public ServiceAdministrator getAdministrator() {
232            return this.administrator;
233        }
234    
235        /**
236         * Get the statistics for this system.
237         * 
238         * @return statistics
239         */
240        public Statistics getStatistics() {
241            return this.statistics;
242        }
243    
244        /**
245         * @return sequencerLibrary
246         */
247        protected ComponentLibrary<Sequencer, SequencerConfig> getSequencerLibrary() {
248            return this.sequencerLibrary;
249        }
250    
251        /**
252         * Add the configuration for a sequencer, or update any existing one that represents the
253         * {@link SequencerConfig#equals(Object) same configuration}
254         * 
255         * @param config the new configuration
256         * @return true if the sequencer was added, or false if there already was an existing and
257         *         {@link SequencerConfig#hasChanged(SequencerConfig) unchanged} sequencer configuration
258         * @throws IllegalArgumentException if <code>config</code> is null
259         * @see #updateSequencer(SequencerConfig)
260         * @see #removeSequencer(SequencerConfig)
261         */
262        public boolean addSequencer( SequencerConfig config ) {
263            return this.sequencerLibrary.add(config);
264        }
265    
266        /**
267         * Update the configuration for a sequencer, or add it if there is no {@link SequencerConfig#equals(Object) matching
268         * configuration}.
269         * 
270         * @param config the updated (or new) configuration
271         * @return true if the sequencer was updated, or false if there already was an existing and
272         *         {@link SequencerConfig#hasChanged(SequencerConfig) unchanged} sequencer configuration
273         * @throws IllegalArgumentException if <code>config</code> is null
274         * @see #addSequencer(SequencerConfig)
275         * @see #removeSequencer(SequencerConfig)
276         */
277        public boolean updateSequencer( SequencerConfig config ) {
278            return this.sequencerLibrary.update(config);
279        }
280    
281        /**
282         * Remove the configuration for a sequencer.
283         * 
284         * @param config the configuration to be removed
285         * @return true if the sequencer was removed, or false if there was no existing sequencer
286         * @throws IllegalArgumentException if <code>config</code> is null
287         * @see #addSequencer(SequencerConfig)
288         * @see #updateSequencer(SequencerConfig)
289         */
290        public boolean removeSequencer( SequencerConfig config ) {
291            return this.sequencerLibrary.remove(config);
292        }
293    
294        /**
295         * @return executionContext
296         */
297        public JcrExecutionContext getExecutionContext() {
298            return this.executionContext;
299        }
300    
301        /**
302         * @param executionContext Sets executionContext to the specified value.
303         */
304        public void setExecutionContext( JcrExecutionContext executionContext ) {
305            CheckArg.isNotNull(executionContext, "execution context");
306            if (this.getAdministrator().isStarted()) {
307                throw new IllegalStateException(RepositoryI18n.unableToChangeExecutionContextWhileRunning.text());
308            }
309            this.executionContext = executionContext;
310            this.sequencerLibrary.setClassLoaderFactory(executionContext);
311        }
312    
313        /**
314         * Get the executor service used to run the sequencers.
315         * 
316         * @return the executor service
317         * @see #setExecutorService(ExecutorService)
318         */
319        public ExecutorService getExecutorService() {
320            return this.executorService;
321        }
322    
323        /**
324         * Set the executor service that should be used by this system. By default, the system is set up with a
325         * {@link Executors#newSingleThreadExecutor() executor that uses a single thread}.
326         * 
327         * @param executorService the executor service
328         * @see #getExecutorService()
329         * @see Executors#newCachedThreadPool()
330         * @see Executors#newCachedThreadPool(java.util.concurrent.ThreadFactory)
331         * @see Executors#newFixedThreadPool(int)
332         * @see Executors#newFixedThreadPool(int, java.util.concurrent.ThreadFactory)
333         * @see Executors#newScheduledThreadPool(int)
334         * @see Executors#newScheduledThreadPool(int, java.util.concurrent.ThreadFactory)
335         * @see Executors#newSingleThreadExecutor()
336         * @see Executors#newSingleThreadExecutor(java.util.concurrent.ThreadFactory)
337         * @see Executors#newSingleThreadScheduledExecutor()
338         * @see Executors#newSingleThreadScheduledExecutor(java.util.concurrent.ThreadFactory)
339         */
340        public void setExecutorService( ExecutorService executorService ) {
341            CheckArg.isNotNull(executorService, "executor service");
342            if (this.getAdministrator().isStarted()) {
343                throw new IllegalStateException(RepositoryI18n.unableToChangeExecutionContextWhileRunning.text());
344            }
345            this.executorService = executorService;
346        }
347    
348        /**
349         * Override this method to creates a different kind of default executor service. This method is called when the system is
350         * {@link #startService() started} without an executor service being {@link #setExecutorService(ExecutorService) set}.
351         * <p>
352         * This method creates a {@link Executors#newSingleThreadExecutor() single-threaded executor}.
353         * </p>
354         * 
355         * @return the executor service
356         */
357        protected ExecutorService createDefaultExecutorService() {
358            return Executors.newSingleThreadExecutor();
359        }
360    
361        protected void startService() {
362            if (this.getExecutionContext() == null) {
363                throw new IllegalStateException(RepositoryI18n.unableToStartSequencingServiceWithoutExecutionContext.text());
364            }
365            if (this.executorService == null) {
366                this.executorService = createDefaultExecutorService();
367            }
368            assert this.executorService != null;
369            assert this.sequencerSelector != null;
370            assert this.nodeFilter != null;
371            assert this.sequencerLibrary != null;
372        }
373    
374        protected void shutdownService() {
375            if (this.executorService != null) {
376                this.executorService.shutdown();
377            }
378        }
379    
380        protected boolean isServiceTerminated() {
381            if (this.executorService != null) {
382                return this.executorService.isTerminated();
383            }
384            return true;
385        }
386    
387        protected boolean doAwaitTermination( long timeout,
388                                              TimeUnit unit ) throws InterruptedException {
389            if (this.executorService == null || this.executorService.isTerminated()) return true;
390            return this.executorService.awaitTermination(timeout, unit);
391        }
392    
393        /**
394         * Get the sequencing selector used by this system.
395         * 
396         * @return the sequencing selector
397         */
398        public Selector getSequencerSelector() {
399            return this.sequencerSelector;
400        }
401    
402        /**
403         * Set the sequencer selector, or null if the {@link #DEFAULT_SEQUENCER_SELECTOR default sequencer selector} should be used.
404         * 
405         * @param sequencerSelector the selector
406         */
407        public void setSequencerSelector( Selector sequencerSelector ) {
408            this.sequencerSelector = sequencerSelector != null ? sequencerSelector : DEFAULT_SEQUENCER_SELECTOR;
409        }
410    
411        /**
412         * Get the node filter used by this system.
413         * 
414         * @return the node filter
415         */
416        public NodeFilter getNodeFilter() {
417            return this.nodeFilter;
418        }
419    
420        /**
421         * Set the filter that checks which nodes are to be sequenced, or null if the {@link #DEFAULT_NODE_FILTER default node filter}
422         * should be used.
423         * 
424         * @param nodeFilter the new node filter
425         */
426        public void setNodeFilter( NodeFilter nodeFilter ) {
427            this.nodeFilter = nodeFilter != null ? nodeFilter : DEFAULT_NODE_FILTER;
428        }
429    
430        /**
431         * {@inheritDoc}
432         */
433        public void onNodeChanges( NodeChanges changes ) {
434            NodeFilter filter = this.getNodeFilter();
435            for (final NodeChange changedNode : changes) {
436                // Only care about new nodes or nodes that have new/changed properies ...
437                if (filter.accept(changedNode)) {
438                    try {
439                        this.executorService.execute(new Runnable() {
440    
441                            public void run() {
442                                processChangedNode(changedNode);
443                            }
444                        });
445                    } catch (RejectedExecutionException e) {
446                        // The executor service has been shut down, so do nothing with this set of changes
447                    }
448                }
449            }
450        }
451    
452        /**
453         * Do the work of processing by sequencing the node. This method is called by the {@link #executorService executor service}
454         * when it performs it's work on the enqueued {@link NodeChange NodeChange runnable objects}.
455         * 
456         * @param changedNode the node to be processed.
457         */
458        protected void processChangedNode( NodeChange changedNode ) {
459            final JcrExecutionContext context = this.getExecutionContext();
460            final Logger logger = context.getLogger(getClass());
461            assert logger != null;
462            try {
463                final String repositoryWorkspaceName = changedNode.getRepositoryWorkspaceName();
464                Session session = null;
465                try {
466                    // Figure out which sequencers accept this path,
467                    // and track which output nodes should be passed to each sequencer...
468                    final String nodePath = changedNode.getAbsolutePath();
469                    Map<SequencerCall, Set<RepositoryNodePath>> sequencerCalls = new HashMap<SequencerCall, Set<RepositoryNodePath>>();
470                    List<Sequencer> allSequencers = this.sequencerLibrary.getInstances();
471                    List<Sequencer> sequencers = new ArrayList<Sequencer>(allSequencers.size());
472                    for (Sequencer sequencer : allSequencers) {
473                        final SequencerConfig config = sequencer.getConfiguration();
474                        for (SequencerPathExpression pathExpression : config.getPathExpressions()) {
475                            for (String propertyName : changedNode.getModifiedProperties()) {
476                                String path = nodePath + "/@" + propertyName;
477                                SequencerPathExpression.Matcher matcher = pathExpression.matcher(path);
478                                if (matcher.matches()) {
479                                    // String selectedPath = matcher.getSelectedPath();
480                                    RepositoryNodePath outputPath = RepositoryNodePath.parse(matcher.getOutputPath(),
481                                                                                             repositoryWorkspaceName);
482                                    SequencerCall call = new SequencerCall(sequencer, propertyName);
483                                    // Record the output path ...
484                                    Set<RepositoryNodePath> outputPaths = sequencerCalls.get(call);
485                                    if (outputPaths == null) {
486                                        outputPaths = new HashSet<RepositoryNodePath>();
487                                        sequencerCalls.put(call, outputPaths);
488                                    }
489                                    outputPaths.add(outputPath);
490                                    sequencers.add(sequencer);
491                                    break;
492                                }
493                            }
494                        }
495                    }
496    
497                    Node node = null;
498                    if (!sequencers.isEmpty()) {
499                        // Create a session that we'll use for all sequencing ...
500                        session = context.getSessionFactory().createSession(repositoryWorkspaceName);
501    
502                        // Find the changed node ...
503                        String relPath = changedNode.getAbsolutePath().replaceAll("^/+", "");
504                        node = session.getRootNode().getNode(relPath);
505    
506                        // Figure out which sequencers should run ...
507                        sequencers = this.sequencerSelector.selectSequencers(sequencers, node, changedNode);
508                    }
509                    if (sequencers.isEmpty()) {
510                        this.statistics.recordNodeSkipped();
511                        if (logger.isDebugEnabled()) {
512                            logger.trace("Skipping '{0}': no sequencers matched this condition", changedNode);
513                        }
514                    } else {
515                        // Run each of those sequencers ...
516                        ProgressMonitor progressMonitor = new SimpleProgressMonitor(RepositoryI18n.sequencerTask.text(changedNode));
517                        if (logger.isTraceEnabled()) {
518                            progressMonitor = new LoggingProgressMonitor(progressMonitor, logger, Logger.Level.TRACE);
519                        }
520                        try {
521                            progressMonitor.beginTask(sequencerCalls.size(), RepositoryI18n.sequencerTask, changedNode);
522                            for (Map.Entry<SequencerCall, Set<RepositoryNodePath>> entry : sequencerCalls.entrySet()) {
523                                final SequencerCall sequencerCall = entry.getKey();
524                                final Set<RepositoryNodePath> outputPaths = entry.getValue();
525                                final Sequencer sequencer = sequencerCall.getSequencer();
526                                final String sequencerName = sequencer.getConfiguration().getName();
527                                final String propertyName = sequencerCall.getSequencedPropertyName();
528    
529                                // Get the paths to the nodes where the sequencer should write it's output ...
530                                assert outputPaths != null && outputPaths.size() != 0;
531    
532                                // Create a new execution context for each sequencer
533                                final Context executionContext = new Context(context);
534                                final ProgressMonitor sequenceMonitor = progressMonitor.createSubtask(1);
535                                try {
536                                    sequenceMonitor.beginTask(100, RepositoryI18n.sequencerSubtask, sequencerName);
537                                    sequencer.execute(node,
538                                                      propertyName,
539                                                      changedNode,
540                                                      outputPaths,
541                                                      executionContext,
542                                                      sequenceMonitor.createSubtask(80)); // 80%
543                                } catch (RepositoryException e) {
544                                    logger.error(e, RepositoryI18n.errorInRepositoryWhileSequencingNode, sequencerName, changedNode);
545                                } catch (SequencerException e) {
546                                    logger.error(e, RepositoryI18n.errorWhileSequencingNode, sequencerName, changedNode);
547                                } finally {
548                                    try {
549                                        // Save the changes made by each sequencer ...
550                                        if (session != null) session.save();
551                                        sequenceMonitor.worked(10); // 90% of sequenceMonitor
552                                    } finally {
553                                        try {
554                                            // And always close the context.
555                                            // This closes all sessions that may have been created by the sequencer.
556                                            executionContext.close();
557                                        } finally {
558                                            sequenceMonitor.done(); // 100% of sequenceMonitor
559                                        }
560                                    }
561                                }
562                            }
563                            this.statistics.recordNodeSequenced();
564                        } finally {
565                            progressMonitor.done();
566                        }
567                    }
568                } finally {
569                    if (session != null) session.logout();
570                }
571            } catch (RepositoryException e) {
572                logger.error(e, RepositoryI18n.errorInRepositoryWhileFindingSequencersToRunAgainstNode, changedNode);
573            } catch (Throwable e) {
574                logger.error(e, RepositoryI18n.errorFindingSequencersToRunAgainstNode, changedNode);
575            }
576        }
577    
578        protected class Context implements JcrExecutionContext {
579    
580            protected final JcrExecutionContext delegate;
581            protected final SessionFactory factory;
582            private final Set<Session> sessions = new HashSet<Session>();
583            protected final AtomicBoolean closed = new AtomicBoolean(false);
584    
585            protected Context( JcrExecutionContext context ) {
586                this.delegate = context;
587                final SessionFactory delegateSessionFactory = this.delegate.getSessionFactory();
588                this.factory = new SessionFactory() {
589    
590                    public Session createSession( String name ) throws RepositoryException {
591                        if (closed.get()) throw new IllegalStateException(RepositoryI18n.executionContextHasBeenClosed.text());
592                        Session session = delegateSessionFactory.createSession(name);
593                        recordSession(session);
594                        return session;
595                    }
596                };
597            }
598    
599            public synchronized void close() {
600                if (this.closed.get()) return;
601                this.closed.set(true);
602                for (Session session : sessions) {
603                    if (session != null) session.logout();
604                }
605            }
606    
607            /**
608             * {@inheritDoc}
609             * 
610             * @see org.jboss.dna.common.component.ClassLoaderFactory#getClassLoader(java.lang.String[])
611             */
612            public ClassLoader getClassLoader( String... classpath ) {
613                return delegate.getClassLoader(classpath);
614            }
615    
616            /**
617             * {@inheritDoc}
618             * 
619             * @see org.jboss.dna.graph.ExecutionContext#getAccessControlContext()
620             */
621            public AccessControlContext getAccessControlContext() {
622                return delegate.getAccessControlContext();
623            }
624    
625            /**
626             * {@inheritDoc}
627             * 
628             * @see org.jboss.dna.graph.ExecutionContext#getLoginContext()
629             */
630            public LoginContext getLoginContext() {
631                return delegate.getLoginContext();
632            }
633    
634            /**
635             * {@inheritDoc}
636             */
637            public NamespaceRegistry getNamespaceRegistry() {
638                return this.delegate.getNamespaceRegistry();
639            }
640    
641            /**
642             * {@inheritDoc}
643             */
644            public PropertyFactory getPropertyFactory() {
645                return this.delegate.getPropertyFactory();
646            }
647    
648            /**
649             * {@inheritDoc}
650             */
651            public SessionFactory getSessionFactory() {
652                return this.factory;
653            }
654    
655            /**
656             * {@inheritDoc}
657             * 
658             * @see org.jboss.dna.graph.ExecutionContext#getSubject()
659             */
660            public Subject getSubject() {
661                return this.delegate.getSubject();
662            }
663    
664            /**
665             * {@inheritDoc}
666             */
667            public JcrTools getTools() {
668                return SequencingService.this.getExecutionContext().getTools();
669            }
670    
671            /**
672             * {@inheritDoc}
673             */
674            public ValueFactories getValueFactories() {
675                return this.delegate.getValueFactories();
676            }
677    
678            /**
679             * {@inheritDoc}
680             * 
681             * @see org.jboss.dna.graph.ExecutionContext#getLogger(java.lang.Class)
682             */
683            public Logger getLogger( Class<?> clazz ) {
684                return this.delegate.getLogger(clazz);
685            }
686    
687            /**
688             * {@inheritDoc}
689             * 
690             * @see org.jboss.dna.graph.ExecutionContext#getLogger(java.lang.String)
691             */
692            public Logger getLogger( String name ) {
693                return this.delegate.getLogger(name);
694            }
695    
696            protected synchronized void recordSession( Session session ) {
697                if (session != null) sessions.add(session);
698            }
699        }
700    
701        /**
702         * The statistics for the system. Each sequencing system has an instance of this class that is updated.
703         * 
704         * @author Randall Hauch
705         */
706        @ThreadSafe
707        public class Statistics {
708    
709            private final AtomicLong numberOfNodesSequenced = new AtomicLong(0);
710            private final AtomicLong numberOfNodesSkipped = new AtomicLong(0);
711            private final AtomicLong startTime;
712    
713            protected Statistics() {
714                startTime = new AtomicLong(System.currentTimeMillis());
715            }
716    
717            public Statistics reset() {
718                this.startTime.set(System.currentTimeMillis());
719                this.numberOfNodesSequenced.set(0);
720                this.numberOfNodesSkipped.set(0);
721                return this;
722            }
723    
724            /**
725             * @return the system time when the statistics were started
726             */
727            public long getStartTime() {
728                return this.startTime.get();
729            }
730    
731            /**
732             * @return the number of nodes that were sequenced
733             */
734            public long getNumberOfNodesSequenced() {
735                return this.numberOfNodesSequenced.get();
736            }
737    
738            /**
739             * @return the number of nodes that were skipped because no sequencers applied
740             */
741            public long getNumberOfNodesSkipped() {
742                return this.numberOfNodesSkipped.get();
743            }
744    
745            protected void recordNodeSequenced() {
746                this.numberOfNodesSequenced.incrementAndGet();
747            }
748    
749            protected void recordNodeSkipped() {
750                this.numberOfNodesSkipped.incrementAndGet();
751            }
752        }
753    
754        @Immutable
755        protected class SequencerCall {
756    
757            private final Sequencer sequencer;
758            private final String sequencerName;
759            private final String sequencedPropertyName;
760            private final int hc;
761    
762            protected SequencerCall( Sequencer sequencer,
763                                     String sequencedPropertyName ) {
764                this.sequencer = sequencer;
765                this.sequencerName = sequencer.getConfiguration().getName();
766                this.sequencedPropertyName = sequencedPropertyName;
767                this.hc = HashCode.compute(this.sequencerName, this.sequencedPropertyName);
768            }
769    
770            /**
771             * @return sequencer
772             */
773            public Sequencer getSequencer() {
774                return this.sequencer;
775            }
776    
777            /**
778             * @return sequencedPropertyName
779             */
780            public String getSequencedPropertyName() {
781                return this.sequencedPropertyName;
782            }
783    
784            /**
785             * {@inheritDoc}
786             */
787            @Override
788            public int hashCode() {
789                return this.hc;
790            }
791    
792            /**
793             * {@inheritDoc}
794             */
795            @Override
796            public boolean equals( Object obj ) {
797                if (obj == this) return true;
798                if (obj instanceof SequencerCall) {
799                    SequencerCall that = (SequencerCall)obj;
800                    if (!this.sequencerName.equals(that.sequencerName)) return false;
801                    if (!this.sequencedPropertyName.equals(that.sequencedPropertyName)) return false;
802                    return true;
803                }
804                return false;
805            }
806        }
807    }