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 }