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    }