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 }