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