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 }