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.Arrays; 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.ScheduledThreadPoolExecutor; 033 import java.util.concurrent.TimeUnit; 034 import net.jcip.annotations.Immutable; 035 import org.jboss.dna.common.collection.Problems; 036 import org.jboss.dna.common.collection.SimpleProblems; 037 import org.jboss.dna.graph.ExecutionContext; 038 import org.jboss.dna.graph.Graph; 039 import org.jboss.dna.graph.JcrLexicon; 040 import org.jboss.dna.graph.JcrMixLexicon; 041 import org.jboss.dna.graph.JcrNtLexicon; 042 import org.jboss.dna.graph.Location; 043 import org.jboss.dna.graph.Node; 044 import org.jboss.dna.graph.Subgraph; 045 import org.jboss.dna.graph.connector.RepositoryConnection; 046 import org.jboss.dna.graph.connector.RepositoryConnectionFactory; 047 import org.jboss.dna.graph.connector.RepositorySource; 048 import org.jboss.dna.graph.connector.RepositorySourceException; 049 import org.jboss.dna.graph.property.Name; 050 import org.jboss.dna.graph.property.Path; 051 import org.jboss.dna.graph.property.PathExpression; 052 import org.jboss.dna.graph.property.PathNotFoundException; 053 import org.jboss.dna.graph.property.Property; 054 import org.jboss.dna.repository.mimetype.MimeTypeDetectorConfig; 055 import org.jboss.dna.repository.observation.ObservationService; 056 import org.jboss.dna.repository.sequencer.SequencerConfig; 057 import org.jboss.dna.repository.sequencer.SequencingService; 058 import org.jboss.dna.repository.service.AdministeredService; 059 import org.jboss.dna.repository.util.JcrExecutionContext; 060 import org.jboss.dna.repository.util.SessionFactory; 061 import org.jboss.dna.repository.util.SimpleSessionFactory; 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 private final Configurator.ConfigurationRepository configuration; 074 private final ConfigurationScanner scanner; 075 private final Problems problems; 076 private final ExecutionContext context; 077 private final List<AdministeredService> services; 078 079 private final SessionFactory jcrSessionFactory; 080 private final RepositoryService repositoryService; 081 private final ObservationService observationService; 082 private final SequencingService sequencingService; 083 private final ExecutorService executorService; 084 085 private final RepositoryConnectionFactory connectionFactory; 086 087 DnaEngine( ExecutionContext context, 088 Configurator.ConfigurationRepository configuration ) { 089 this.problems = new SimpleProblems(); 090 091 // Use the configuration's context ... 092 this.context = context; 093 094 // And set up the scanner ... 095 this.configuration = configuration; 096 this.scanner = new ConfigurationScanner(this.problems, this.context, this.configuration); 097 098 // Add the configuration source to the repository library ... 099 final RepositorySource configSource = this.configuration.getRepositorySource(); 100 RepositoryLibrary library = new RepositoryLibrary(); 101 library.addSource(configSource); 102 103 // Create the RepositoryService, pointing it to the configuration repository ... 104 Path pathToConfigurationRoot = this.configuration.getPath(); 105 repositoryService = new RepositoryService(library, configSource.getName(), "", pathToConfigurationRoot, context); 106 107 for (MimeTypeDetectorConfig config : scanner.getMimeTypeDetectors()) { 108 library.getMimeTypeDetectors().addDetector(config); 109 } 110 111 // Create the sequencing service ... 112 executorService = new ScheduledThreadPoolExecutor(10); // Use a magic number for now 113 sequencingService = new SequencingService(); 114 jcrSessionFactory = createSessionFactory(); 115 JcrExecutionContext jcrContext = new JcrExecutionContext(context, jcrSessionFactory, ""); 116 sequencingService.setExecutionContext(jcrContext); 117 sequencingService.setExecutorService(executorService); 118 for (SequencerConfig sequencerConfig : scanner.getSequencingConfigurations()) { 119 sequencingService.addSequencer(sequencerConfig); 120 } 121 122 // Create the observation service ... 123 observationService = null; // new ObservationService(null); 124 125 this.services = Arrays.asList(new AdministeredService[] { /* observationService, */repositoryService, sequencingService,}); 126 127 connectionFactory = new RepositoryConnectionFactory() { 128 public RepositoryConnection createConnection( String sourceName ) throws RepositorySourceException { 129 RepositorySource source = DnaEngine.this.getRepositorySource(sourceName); 130 if (sourceName == null) { 131 throw new RepositorySourceException(sourceName); 132 } 133 134 return source.getConnection(); 135 } 136 }; 137 } 138 139 /** 140 * Method that can be overridden in subclasses to create (and populate) the SessionFactory used by the sequencing service. 141 * 142 * @return a session factory, which may not be null 143 */ 144 protected SessionFactory createSessionFactory() { 145 return new SimpleSessionFactory(); 146 } 147 148 /** 149 * Get the problems that were encountered when setting up this engine from the configuration. 150 * 151 * @return the problems, which may be empty but will never be null 152 */ 153 public Problems getProblems() { 154 return problems; 155 } 156 157 /* 158 * Lookup methods 159 */ 160 public final ExecutionContext getExecutionContext() { 161 return context; 162 } 163 164 public final RepositorySource getRepositorySource( String repositoryName ) { 165 return repositoryService.getRepositorySourceManager().getSource(repositoryName); 166 } 167 168 public final RepositoryConnectionFactory getRepositoryConnectionFactory() { 169 return connectionFactory; 170 } 171 172 public final RepositoryService getRepositoryService() { 173 return repositoryService; 174 } 175 176 public final ObservationService getObservationService() { 177 return observationService; 178 } 179 180 public final SequencingService getSequencingService() { 181 return sequencingService; 182 } 183 184 /* 185 * Lifecycle methods 186 */ 187 188 public void start() { 189 for (AdministeredService service : services) { 190 service.getAdministrator().start(); 191 } 192 } 193 194 public void shutdown() { 195 for (AdministeredService service : services) { 196 service.getAdministrator().shutdown(); 197 } 198 199 try { 200 executorService.awaitTermination(10 * 60, TimeUnit.SECONDS); // No TimeUnit.MINUTES in JDK 5 201 } catch (InterruptedException ie) { 202 // Reset the thread's status and continue this method ... 203 Thread.interrupted(); 204 } 205 executorService.shutdown(); 206 } 207 208 /** 209 * The component responsible for reading the configuration repository and (eventually) for propagating changes in the 210 * configuration repository into the services. 211 */ 212 protected class ConfigurationScanner { 213 private final Problems problems; 214 private final ExecutionContext context; 215 private final Configurator.ConfigurationRepository configurationRepository; 216 217 protected ConfigurationScanner( Problems problems, 218 ExecutionContext context, 219 Configurator.ConfigurationRepository configurationRepository ) { 220 this.problems = problems; 221 this.context = context; 222 this.configurationRepository = configurationRepository; 223 } 224 225 public List<MimeTypeDetectorConfig> getMimeTypeDetectors() { 226 List<MimeTypeDetectorConfig> detectors = new ArrayList<MimeTypeDetectorConfig>(); 227 Graph graph = Graph.create(configurationRepository.getRepositorySource(), context); 228 Path pathToSequencersNode = context.getValueFactories().getPathFactory().create(configurationRepository.getPath(), 229 DnaLexicon.MIME_TYPE_DETECTORS); 230 try { 231 Subgraph subgraph = graph.getSubgraphOfDepth(2).at(pathToSequencersNode); 232 233 Set<Name> skipProperties = new HashSet<Name>(); 234 skipProperties.add(DnaLexicon.READABLE_NAME); 235 skipProperties.add(DnaLexicon.DESCRIPTION); 236 skipProperties.add(DnaLexicon.CLASSNAME); 237 skipProperties.add(DnaLexicon.CLASSPATH); 238 skipProperties.add(DnaLexicon.PATH_EXPRESSIONS); 239 Set<String> skipNamespaces = new HashSet<String>(); 240 skipNamespaces.add(JcrLexicon.Namespace.URI); 241 skipNamespaces.add(JcrNtLexicon.Namespace.URI); 242 skipNamespaces.add(JcrMixLexicon.Namespace.URI); 243 244 for (Location detectorLocation : subgraph.getRoot().getChildren()) { 245 Node node = subgraph.getNode(detectorLocation); 246 String name = stringValueOf(node, DnaLexicon.READABLE_NAME); 247 String desc = stringValueOf(node, DnaLexicon.DESCRIPTION); 248 String classname = stringValueOf(node, DnaLexicon.CLASSNAME); 249 String[] classpath = stringValuesOf(node, DnaLexicon.CLASSPATH); 250 Map<String, Object> properties = new HashMap<String, Object>(); 251 for (Property property : node.getProperties()) { 252 Name propertyName = property.getName(); 253 if (skipNamespaces.contains(propertyName.getNamespaceUri())) continue; 254 if (skipProperties.contains(propertyName)) continue; 255 if (property.isSingle()) { 256 properties.put(propertyName.getLocalName(), property.getFirstValue()); 257 } else { 258 properties.put(propertyName.getLocalName(), property.getValuesAsArray()); 259 } 260 } 261 MimeTypeDetectorConfig config = new MimeTypeDetectorConfig(name, desc, properties, classname, classpath); 262 detectors.add(config); 263 } 264 } catch (PathNotFoundException e) { 265 // no detectors registered ... 266 } 267 return detectors; 268 } 269 270 public List<SequencerConfig> getSequencingConfigurations() { 271 List<SequencerConfig> configs = new ArrayList<SequencerConfig>(); 272 Graph graph = Graph.create(configurationRepository.getRepositorySource(), context); 273 Path pathToSequencersNode = context.getValueFactories().getPathFactory().create(configurationRepository.getPath(), 274 DnaLexicon.SEQUENCERS); 275 try { 276 Subgraph subgraph = graph.getSubgraphOfDepth(2).at(pathToSequencersNode); 277 278 Set<Name> skipProperties = new HashSet<Name>(); 279 skipProperties.add(DnaLexicon.READABLE_NAME); 280 skipProperties.add(DnaLexicon.DESCRIPTION); 281 skipProperties.add(DnaLexicon.CLASSNAME); 282 skipProperties.add(DnaLexicon.CLASSPATH); 283 skipProperties.add(DnaLexicon.PATH_EXPRESSIONS); 284 Set<String> skipNamespaces = new HashSet<String>(); 285 skipNamespaces.add(JcrLexicon.Namespace.URI); 286 skipNamespaces.add(JcrNtLexicon.Namespace.URI); 287 skipNamespaces.add(JcrMixLexicon.Namespace.URI); 288 289 for (Location sequencerLocation : subgraph.getRoot().getChildren()) { 290 Node sequencerNode = subgraph.getNode(sequencerLocation); 291 String name = stringValueOf(sequencerNode, DnaLexicon.READABLE_NAME); 292 String desc = stringValueOf(sequencerNode, DnaLexicon.DESCRIPTION); 293 String classname = stringValueOf(sequencerNode, DnaLexicon.CLASSNAME); 294 String[] classpath = stringValuesOf(sequencerNode, DnaLexicon.CLASSPATH); 295 String[] expressionStrings = stringValuesOf(sequencerNode, DnaLexicon.PATH_EXPRESSIONS); 296 List<PathExpression> pathExpressions = new ArrayList<PathExpression>(); 297 if (expressionStrings != null) { 298 for (String expressionString : expressionStrings) { 299 try { 300 pathExpressions.add(PathExpression.compile(expressionString)); 301 } catch (Throwable t) { 302 problems.addError(t, 303 RepositoryI18n.pathExpressionIsInvalidOnSequencer, 304 expressionString, 305 name, 306 t.getLocalizedMessage()); 307 } 308 } 309 } 310 String[] goodExpressionStrings = new String[pathExpressions.size()]; 311 for (int i = 0; i != pathExpressions.size(); ++i) { 312 PathExpression expression = pathExpressions.get(i); 313 goodExpressionStrings[i] = expression.getExpression(); 314 } 315 Map<String, Object> properties = new HashMap<String, Object>(); 316 for (Property property : sequencerNode.getProperties()) { 317 Name propertyName = property.getName(); 318 if (skipNamespaces.contains(propertyName.getNamespaceUri())) continue; 319 if (skipProperties.contains(propertyName)) continue; 320 if (property.isSingle()) { 321 properties.put(propertyName.getLocalName(), property.getFirstValue()); 322 } else { 323 properties.put(propertyName.getLocalName(), property.getValuesAsArray()); 324 } 325 } 326 SequencerConfig config = new SequencerConfig(name, desc, properties, classname, classpath, 327 goodExpressionStrings); 328 configs.add(config); 329 } 330 } catch (PathNotFoundException e) { 331 // no detectors registered ... 332 } 333 return configs; 334 } 335 336 private String stringValueOf( Node node, 337 Name propertyName ) { 338 Property property = node.getProperty(propertyName); 339 if (property == null) return null; 340 if (property.isEmpty()) return null; 341 return context.getValueFactories().getStringFactory().create(property.getFirstValue()); 342 } 343 344 private String[] stringValuesOf( Node node, 345 Name propertyName ) { 346 Property property = node.getProperty(propertyName); 347 if (property == null) return null; 348 return context.getValueFactories().getStringFactory().create(property.getValuesAsArray()); 349 } 350 351 } 352 }