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; 023 024 import java.util.ArrayList; 025 import java.util.HashMap; 026 import java.util.HashSet; 027 import java.util.List; 028 import java.util.Map; 029 import java.util.Set; 030 import java.util.concurrent.ExecutorService; 031 import java.util.concurrent.ScheduledThreadPoolExecutor; 032 import java.util.concurrent.TimeUnit; 033 import net.jcip.annotations.Immutable; 034 import org.jboss.dna.common.collection.Problem; 035 import org.jboss.dna.common.collection.Problems; 036 import org.jboss.dna.common.collection.SimpleProblems; 037 import org.jboss.dna.common.util.CheckArg; 038 import org.jboss.dna.common.util.Logger; 039 import org.jboss.dna.graph.ExecutionContext; 040 import org.jboss.dna.graph.Graph; 041 import org.jboss.dna.graph.JcrLexicon; 042 import org.jboss.dna.graph.JcrMixLexicon; 043 import org.jboss.dna.graph.JcrNtLexicon; 044 import org.jboss.dna.graph.Location; 045 import org.jboss.dna.graph.Node; 046 import org.jboss.dna.graph.Subgraph; 047 import org.jboss.dna.graph.connector.RepositoryConnection; 048 import org.jboss.dna.graph.connector.RepositoryConnectionFactory; 049 import org.jboss.dna.graph.connector.RepositorySource; 050 import org.jboss.dna.graph.connector.RepositorySourceException; 051 import org.jboss.dna.graph.mimetype.ExtensionBasedMimeTypeDetector; 052 import org.jboss.dna.graph.mimetype.MimeTypeDetector; 053 import org.jboss.dna.graph.mimetype.MimeTypeDetectorConfig; 054 import org.jboss.dna.graph.mimetype.MimeTypeDetectors; 055 import org.jboss.dna.graph.property.Name; 056 import org.jboss.dna.graph.property.Path; 057 import org.jboss.dna.graph.property.PathExpression; 058 import org.jboss.dna.graph.property.PathNotFoundException; 059 import org.jboss.dna.graph.property.Property; 060 import org.jboss.dna.repository.sequencer.SequencerConfig; 061 import org.jboss.dna.repository.sequencer.SequencingService; 062 063 /** 064 * A single instance of the DNA services, which is obtained after setting up the {@link DnaConfiguration#build() configuration}. 065 * 066 * @see DnaConfiguration 067 */ 068 @Immutable 069 public class DnaEngine { 070 071 public static final String CONFIGURATION_REPOSITORY_NAME = "dna:configuration"; 072 073 protected final DnaConfiguration.ConfigurationDefinition configuration; 074 private final ConfigurationScanner scanner; 075 private final Problems problems; 076 protected final ExecutionContext context; 077 078 private final RepositoryService repositoryService; 079 private final SequencingService sequencingService; 080 private final ExecutorService executorService; 081 private final MimeTypeDetectors detectors; 082 083 private final RepositoryConnectionFactory connectionFactory; 084 085 protected DnaEngine( ExecutionContext context, 086 DnaConfiguration.ConfigurationDefinition configuration ) { 087 this.problems = new SimpleProblems(); 088 089 // Use the configuration's context ... 090 this.detectors = new MimeTypeDetectors(); 091 this.context = context.with(detectors); 092 093 // And set up the scanner ... 094 this.configuration = configuration; 095 this.scanner = new ConfigurationScanner(this.problems, this.context, this.configuration); 096 097 // Add the mime type detectors in the configuration ... 098 for (MimeTypeDetectorConfig config : scanner.getMimeTypeDetectors()) { 099 detectors.addDetector(config); 100 } 101 // Add an extension-based detector by default ... 102 detectors.addDetector(new MimeTypeDetectorConfig("ExtensionDetector", "Extension-based MIME type detector", 103 ExtensionBasedMimeTypeDetector.class)); 104 105 // Create the RepositoryService, pointing it to the configuration repository ... 106 Path pathToConfigurationRoot = this.configuration.getPath(); 107 String configWorkspaceName = this.configuration.getWorkspace(); 108 final RepositorySource configSource = this.configuration.getRepositorySource(); 109 repositoryService = new RepositoryService(configSource, configWorkspaceName, pathToConfigurationRoot, context); 110 111 // Create the sequencing service ... 112 executorService = new ScheduledThreadPoolExecutor(10); // Use a magic number for now 113 sequencingService = new SequencingService(); 114 sequencingService.setExecutionContext(context); 115 sequencingService.setExecutorService(executorService); 116 sequencingService.setRepositoryLibrary(repositoryService.getRepositoryLibrary()); 117 for (SequencerConfig sequencerConfig : scanner.getSequencingConfigurations()) { 118 sequencingService.addSequencer(sequencerConfig); 119 } 120 121 // Set up the connection factory for this engine ... 122 connectionFactory = new RepositoryConnectionFactory() { 123 public RepositoryConnection createConnection( String sourceName ) throws RepositorySourceException { 124 RepositorySource source = DnaEngine.this.getRepositorySource(sourceName); 125 if (source == null) { 126 throw new RepositorySourceException(sourceName); 127 } 128 129 return source.getConnection(); 130 } 131 }; 132 } 133 134 /** 135 * Get the problems that were encountered when setting up this engine from the configuration. 136 * 137 * @return the problems, which may be empty but will never be null 138 */ 139 public Problems getProblems() { 140 return problems; 141 } 142 143 /** 144 * Get the context in which this engine is executing. 145 * 146 * @return the execution context; never null 147 */ 148 public final ExecutionContext getExecutionContext() { 149 return context; 150 } 151 152 /** 153 * Get the {@link RepositorySource} instance used by this engine. 154 * 155 * @param repositoryName the name of the repository source 156 * @return the source, or null if no source with the given name exists 157 * @throws IllegalStateException if this engine was not {@link #start() started} 158 */ 159 public final RepositorySource getRepositorySource( String repositoryName ) { 160 checkRunning(); 161 return repositoryService.getRepositoryLibrary().getSource(repositoryName); 162 } 163 164 /** 165 * Get a factory of connections, backed by the RepositorySor 166 * 167 * @return the connection factory; never null 168 * @throws IllegalStateException if this engine was not {@link #start() started} 169 */ 170 public final RepositoryConnectionFactory getRepositoryConnectionFactory() { 171 checkRunning(); 172 return connectionFactory; 173 } 174 175 /** 176 * Get the repository service. 177 * 178 * @return the repository service owned by this engine; never null 179 * @throws IllegalStateException if this engine was not {@link #start() started} 180 */ 181 public final RepositoryService getRepositoryService() { 182 checkRunning(); 183 return repositoryService; 184 } 185 186 /** 187 * Get a graph to the underlying source. 188 * 189 * @param sourceName the name of the source 190 * @return the graph 191 * @throws IllegalArgumentException if the source name is null 192 * @throws RepositorySourceException if a source with the supplied name does not exist 193 * @throws IllegalStateException if this engine was not {@link #start() started} 194 */ 195 public final Graph getGraph( String sourceName ) { 196 CheckArg.isNotNull(sourceName, "sourceName"); 197 return getGraph(getExecutionContext(), sourceName); 198 } 199 200 /** 201 * Get a graph to the underlying source, using the supplied context. Note that the supplied context should be a derivative of 202 * the engine's {@link #getExecutionContext() context}. 203 * 204 * @param context the context of execution for this graph; may not be null 205 * @param sourceName the name of the source 206 * @return the graph 207 * @throws IllegalArgumentException if the context or source name are null 208 * @throws RepositorySourceException if a source with the supplied name does not exist 209 * @throws IllegalStateException if this engine was not {@link #start() started} 210 */ 211 public final Graph getGraph( ExecutionContext context, 212 String sourceName ) { 213 CheckArg.isNotNull(context, "context"); 214 CheckArg.isNotNull(sourceName, "sourceName"); 215 checkRunning(); 216 Graph graph = Graph.create(sourceName, getRepositoryService().getRepositoryLibrary(), context); 217 if (configuration.getRepositorySource().getName().equals(sourceName) && configuration.getWorkspace() != null) { 218 // set the workspace ... 219 graph.useWorkspace(configuration.getWorkspace()); 220 } 221 return graph; 222 } 223 224 /** 225 * Get the sequencing service. 226 * 227 * @return the sequencing service owned by this engine; never null 228 * @throws IllegalStateException if this engine was not {@link #start() started} 229 */ 230 public final SequencingService getSequencingService() { 231 checkRunning(); 232 return sequencingService; 233 } 234 235 /** 236 * Return the component that is able to detect MIME types given the name of a stream and a stream. 237 * 238 * @return the MIME type detector used by this engine; never null 239 * @throws IllegalStateException if this engine was not {@link #start() started} 240 */ 241 protected final MimeTypeDetector getMimeTypeDetector() { 242 checkRunning(); 243 return detectors; 244 } 245 246 protected final boolean checkRunning() { 247 if (repositoryService.getAdministrator().isStarted() && sequencingService.getAdministrator().isStarted()) { 248 return true; 249 } 250 throw new IllegalStateException(RepositoryI18n.engineIsNotRunning.text()); 251 } 252 253 /* 254 * Lifecycle methods 255 */ 256 /** 257 * Start this engine to make it available for use. 258 * 259 * @throws IllegalStateException if this method is called when already shut down. 260 * @see #shutdown() 261 */ 262 public void start() { 263 if (getProblems().hasErrors()) { 264 // First log the messages ... 265 Logger log = Logger.getLogger(getClass()); 266 log.error(RepositoryI18n.errorsPreventStarting); 267 for (Problem problem : getProblems()) { 268 log.error(problem.getMessage(), problem.getParameters()); 269 } 270 // Then throw an exception ... 271 throw new IllegalStateException(RepositoryI18n.errorsPreventStarting.text()); 272 } 273 repositoryService.getAdministrator().start(); 274 sequencingService.getAdministrator().start(); 275 } 276 277 /** 278 * Shutdown this engine to close all connections, terminate any ongoing background operations (such as sequencing), and 279 * reclaim any resources that were acquired by this engine. This method may be called multiple times, but only the first time 280 * has an effect. 281 * 282 * @see #start() 283 */ 284 public void shutdown() { 285 // First, shutdown the sequencing service, which will prevent any additional jobs from going through ... 286 sequencingService.getAdministrator().shutdown(); 287 288 // Then terminate the executor service, which may be running background jobs that are not yet completed ... 289 try { 290 executorService.awaitTermination(10 * 60, TimeUnit.SECONDS); // No TimeUnit.MINUTES in JDK 5 291 } catch (InterruptedException ie) { 292 // Reset the thread's status and continue this method ... 293 Thread.interrupted(); 294 } 295 executorService.shutdown(); 296 297 // Finally shut down the repository source, which closes all connections ... 298 repositoryService.getAdministrator().shutdown(); 299 } 300 301 /** 302 * Blocks until the shutdown has completed, or the timeout occurs, or the current thread is interrupted, whichever happens 303 * first. 304 * 305 * @param timeout the maximum time to wait for each component in this engine 306 * @param unit the time unit of the timeout argument 307 * @return <tt>true</tt> if this service complete shut down and <tt>false</tt> if the timeout elapsed before it was shut down 308 * completely 309 * @throws InterruptedException if interrupted while waiting 310 */ 311 public boolean awaitTermination( long timeout, 312 TimeUnit unit ) throws InterruptedException { 313 if (!sequencingService.getAdministrator().awaitTermination(timeout, unit)) return false; 314 if (!executorService.awaitTermination(timeout, unit)) return false; 315 if (!repositoryService.getAdministrator().awaitTermination(timeout, unit)) return false; 316 return true; 317 } 318 319 /** 320 * Get a graph to the configuration content. 321 * 322 * @return a graph to the configuration content 323 */ 324 protected Graph getConfigurationGraph() { 325 Graph result = Graph.create(configuration.getRepositorySource(), context); 326 if (configuration.getWorkspace() != null) { 327 result.useWorkspace(configuration.getWorkspace()); 328 } 329 return result; 330 } 331 332 /** 333 * The component responsible for reading the configuration repository and (eventually) for propagating changes in the 334 * configuration repository into the services. 335 */ 336 protected class ConfigurationScanner { 337 private final Problems problems; 338 private final ExecutionContext context; 339 private final DnaConfiguration.ConfigurationDefinition configurationRepository; 340 341 protected ConfigurationScanner( Problems problems, 342 ExecutionContext context, 343 DnaConfiguration.ConfigurationDefinition configurationRepository ) { 344 this.problems = problems; 345 this.context = context; 346 this.configurationRepository = configurationRepository; 347 } 348 349 public List<MimeTypeDetectorConfig> getMimeTypeDetectors() { 350 List<MimeTypeDetectorConfig> detectors = new ArrayList<MimeTypeDetectorConfig>(); 351 Graph graph = Graph.create(configurationRepository.getRepositorySource(), context); 352 Path pathToSequencersNode = context.getValueFactories().getPathFactory().create(configurationRepository.getPath(), 353 DnaLexicon.MIME_TYPE_DETECTORS); 354 try { 355 Subgraph subgraph = graph.getSubgraphOfDepth(2).at(pathToSequencersNode); 356 357 Set<Name> skipProperties = new HashSet<Name>(); 358 skipProperties.add(DnaLexicon.READABLE_NAME); 359 skipProperties.add(DnaLexicon.DESCRIPTION); 360 skipProperties.add(DnaLexicon.CLASSNAME); 361 skipProperties.add(DnaLexicon.CLASSPATH); 362 skipProperties.add(DnaLexicon.PATH_EXPRESSION); 363 Set<String> skipNamespaces = new HashSet<String>(); 364 skipNamespaces.add(JcrLexicon.Namespace.URI); 365 skipNamespaces.add(JcrNtLexicon.Namespace.URI); 366 skipNamespaces.add(JcrMixLexicon.Namespace.URI); 367 368 for (Location detectorLocation : subgraph.getRoot().getChildren()) { 369 Node node = subgraph.getNode(detectorLocation); 370 String name = stringValueOf(node, DnaLexicon.READABLE_NAME); 371 if (name == null) name = stringValueOf(node); 372 String desc = stringValueOf(node, DnaLexicon.DESCRIPTION); 373 String classname = stringValueOf(node, DnaLexicon.CLASSNAME); 374 String[] classpath = stringValuesOf(node, DnaLexicon.CLASSPATH); 375 Map<String, Object> properties = new HashMap<String, Object>(); 376 for (Property property : node.getProperties()) { 377 Name propertyName = property.getName(); 378 if (skipNamespaces.contains(propertyName.getNamespaceUri())) continue; 379 if (skipProperties.contains(propertyName)) continue; 380 if (property.isSingle()) { 381 properties.put(propertyName.getLocalName(), property.getFirstValue()); 382 } else { 383 properties.put(propertyName.getLocalName(), property.getValuesAsArray()); 384 } 385 } 386 MimeTypeDetectorConfig config = new MimeTypeDetectorConfig(name, desc, properties, classname, classpath); 387 detectors.add(config); 388 } 389 } catch (PathNotFoundException e) { 390 // no detectors registered ... 391 } 392 return detectors; 393 } 394 395 public List<SequencerConfig> getSequencingConfigurations() { 396 List<SequencerConfig> configs = new ArrayList<SequencerConfig>(); 397 Graph graph = Graph.create(configurationRepository.getRepositorySource(), context); 398 Path pathToSequencersNode = context.getValueFactories().getPathFactory().create(configurationRepository.getPath(), 399 DnaLexicon.SEQUENCERS); 400 try { 401 Subgraph subgraph = graph.getSubgraphOfDepth(2).at(pathToSequencersNode); 402 403 Set<Name> skipProperties = new HashSet<Name>(); 404 skipProperties.add(DnaLexicon.READABLE_NAME); 405 skipProperties.add(DnaLexicon.DESCRIPTION); 406 skipProperties.add(DnaLexicon.CLASSNAME); 407 skipProperties.add(DnaLexicon.CLASSPATH); 408 skipProperties.add(DnaLexicon.PATH_EXPRESSION); 409 Set<String> skipNamespaces = new HashSet<String>(); 410 skipNamespaces.add(JcrLexicon.Namespace.URI); 411 skipNamespaces.add(JcrNtLexicon.Namespace.URI); 412 skipNamespaces.add(JcrMixLexicon.Namespace.URI); 413 414 for (Location sequencerLocation : subgraph.getRoot().getChildren()) { 415 Node sequencerNode = subgraph.getNode(sequencerLocation); 416 String name = stringValueOf(sequencerNode, DnaLexicon.READABLE_NAME); 417 if (name == null) name = stringValueOf(sequencerNode); 418 String desc = stringValueOf(sequencerNode, DnaLexicon.DESCRIPTION); 419 String classname = stringValueOf(sequencerNode, DnaLexicon.CLASSNAME); 420 String[] classpath = stringValuesOf(sequencerNode, DnaLexicon.CLASSPATH); 421 String[] expressionStrings = stringValuesOf(sequencerNode, DnaLexicon.PATH_EXPRESSION); 422 List<PathExpression> pathExpressions = new ArrayList<PathExpression>(); 423 if (expressionStrings != null) { 424 for (String expressionString : expressionStrings) { 425 try { 426 pathExpressions.add(PathExpression.compile(expressionString)); 427 } catch (Throwable t) { 428 problems.addError(t, 429 RepositoryI18n.pathExpressionIsInvalidOnSequencer, 430 expressionString, 431 name, 432 t.getLocalizedMessage()); 433 } 434 } 435 } 436 String[] goodExpressionStrings = new String[pathExpressions.size()]; 437 for (int i = 0; i != pathExpressions.size(); ++i) { 438 PathExpression expression = pathExpressions.get(i); 439 goodExpressionStrings[i] = expression.getExpression(); 440 } 441 Map<String, Object> properties = new HashMap<String, Object>(); 442 for (Property property : sequencerNode.getProperties()) { 443 Name propertyName = property.getName(); 444 if (skipNamespaces.contains(propertyName.getNamespaceUri())) continue; 445 if (skipProperties.contains(propertyName)) continue; 446 if (property.isSingle()) { 447 properties.put(propertyName.getLocalName(), property.getFirstValue()); 448 } else { 449 properties.put(propertyName.getLocalName(), property.getValuesAsArray()); 450 } 451 } 452 SequencerConfig config = new SequencerConfig(name, desc, properties, classname, classpath, 453 goodExpressionStrings); 454 configs.add(config); 455 } 456 } catch (PathNotFoundException e) { 457 // no detectors registered ... 458 } 459 return configs; 460 } 461 462 private String stringValueOf( Node node ) { 463 return node.getLocation().getPath().getLastSegment().getString(context.getNamespaceRegistry()); 464 } 465 466 private String stringValueOf( Node node, 467 Name propertyName ) { 468 Property property = node.getProperty(propertyName); 469 if (property == null) return null; 470 if (property.isEmpty()) return null; 471 return context.getValueFactories().getStringFactory().create(property.getFirstValue()); 472 } 473 474 private String[] stringValuesOf( Node node, 475 Name propertyName ) { 476 Property property = node.getProperty(propertyName); 477 if (property == null) return null; 478 return context.getValueFactories().getStringFactory().create(property.getValuesAsArray()); 479 } 480 481 } 482 }