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 }