View Javadoc

1   /*
2    * ModeShape (http://www.modeshape.org)
3    * See the COPYRIGHT.txt file distributed with this work for information
4    * regarding copyright ownership.  Some portions may be licensed
5    * to Red Hat, Inc. under one or more contributor license agreements.
6    * See the AUTHORS.txt file in the distribution for a full listing of 
7    * individual contributors.
8    *
9    * ModeShape is free software. Unless otherwise indicated, all code in ModeShape
10   * is licensed to you under the terms of the GNU Lesser General Public License as
11   * published by the Free Software Foundation; either version 2.1 of
12   * the License, or (at your option) any later version.
13   * 
14   * ModeShape is distributed in the hope that it will be useful,
15   * but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17   * Lesser General Public License for more details.
18   *
19   * You should have received a copy of the GNU Lesser General Public
20   * License along with this software; if not, write to the Free
21   * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
22   * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
23   */
24  package org.modeshape.repository;
25  
26  import java.io.File;
27  import java.io.FileInputStream;
28  import java.io.FileOutputStream;
29  import java.io.IOException;
30  import java.io.InputStream;
31  import java.io.OutputStream;
32  import java.net.URL;
33  import java.util.ArrayList;
34  import java.util.Collections;
35  import java.util.HashMap;
36  import java.util.HashSet;
37  import java.util.List;
38  import java.util.Map;
39  import java.util.Set;
40  import java.util.Stack;
41  import net.jcip.annotations.Immutable;
42  import net.jcip.annotations.NotThreadSafe;
43  import org.modeshape.common.collection.Problems;
44  import org.modeshape.common.collection.SimpleProblems;
45  import org.modeshape.common.component.ClassLoaderFactory;
46  import org.modeshape.common.component.StandardClassLoaderFactory;
47  import org.modeshape.common.util.CheckArg;
48  import org.modeshape.common.xml.StreamingContentHandler;
49  import org.modeshape.graph.DnaExecutionContext;
50  import org.modeshape.graph.ExecutionContext;
51  import org.modeshape.graph.Graph;
52  import org.modeshape.graph.Location;
53  import org.modeshape.graph.Node;
54  import org.modeshape.graph.Subgraph;
55  import org.modeshape.graph.SubgraphNode;
56  import org.modeshape.graph.Workspace;
57  import org.modeshape.graph.connector.RepositorySource;
58  import org.modeshape.graph.connector.inmemory.InMemoryRepositorySource;
59  import org.modeshape.graph.mimetype.MimeTypeDetector;
60  import org.modeshape.graph.property.Name;
61  import org.modeshape.graph.property.NamespaceRegistry;
62  import org.modeshape.graph.property.Path;
63  import org.modeshape.graph.property.PathExpression;
64  import org.modeshape.graph.property.PathNotFoundException;
65  import org.modeshape.graph.property.Property;
66  import org.modeshape.graph.property.ValueFactory;
67  import org.modeshape.graph.property.basic.RootPath;
68  import org.modeshape.graph.request.InvalidWorkspaceException;
69  import org.modeshape.graph.request.ReadBranchRequest;
70  import org.modeshape.graph.sequencer.StreamSequencer;
71  import org.xml.sax.ContentHandler;
72  import org.xml.sax.SAXException;
73  import org.xml.sax.helpers.AttributesImpl;
74  
75  /**
76   * A configuration builder for a {@link ModeShapeEngine}. This class is an internal domain-specific language (DSL), and is
77   * designed to be used in a traditional way or in a method-chained manner:
78   * 
79   * <pre>
80   * configuration.repositorySource(&quot;Source1&quot;).setClass(InMemoryRepositorySource.class).setDescription(&quot;description&quot;);
81   * configuration.mimeTypeDetector(&quot;detector&quot;).setClass(ExtensionBasedMimeTypeDetector.class).setDescription(&quot;default detector&quot;);
82   * configuration.sequencer(&quot;MicrosoftDocs&quot;)
83   *              .setClass(&quot;org.modeshape.sequencer.msoffice.MSOfficeMetadataSequencer&quot;)
84   *              .setDescription(&quot;Our primary sequencer for all .doc files&quot;)
85   *              .sequencingFrom(&quot;/public//(*.(doc|xml|ppt)[*]/jcr:content[@jcr:data]&quot;)
86   *              .andOutputtingTo(&quot;/documents/$1&quot;);
87   * configuration.save();
88   * </pre>
89   */
90  @SuppressWarnings( "deprecation" )
91  @NotThreadSafe
92  public class ModeShapeConfiguration {
93  
94      public static final String DEFAULT_WORKSPACE_NAME = "";
95      public static final String DEFAULT_PATH = "/";
96      public static final String DEFAULT_CONFIGURATION_SOURCE_NAME = "ModeShape Configuration Repository";
97  
98      private final ExecutionContext context;
99      private final Problems problems = new SimpleProblems();
100     private ConfigurationDefinition configurationContent;
101     private Graph.Batch changes;
102 
103     private final Map<String, SequencerDefinition<? extends ModeShapeConfiguration>> sequencerDefinitions = new HashMap<String, SequencerDefinition<? extends ModeShapeConfiguration>>();
104     private final Map<String, RepositorySourceDefinition<? extends ModeShapeConfiguration>> repositorySourceDefinitions = new HashMap<String, RepositorySourceDefinition<? extends ModeShapeConfiguration>>();
105     private final Map<String, MimeTypeDetectorDefinition<? extends ModeShapeConfiguration>> mimeTypeDetectorDefinitions = new HashMap<String, MimeTypeDetectorDefinition<? extends ModeShapeConfiguration>>();
106 
107     /**
108      * Create a new configuration, using a default-constructed {@link ExecutionContext}.
109      */
110     public ModeShapeConfiguration() {
111         this(new ExecutionContext());
112     }
113 
114     /**
115      * Create a new configuration using the supplied {@link ExecutionContext}.
116      * 
117      * @param context the execution context
118      * @throws IllegalArgumentException if the path is null or empty
119      */
120     public ModeShapeConfiguration( ExecutionContext context ) {
121         CheckArg.isNotNull(context, "context");
122         this.context = context;
123 
124         // Create the in-memory repository source in which the content will be stored ...
125         InMemoryRepositorySource source = new InMemoryRepositorySource();
126         source.setName(DEFAULT_CONFIGURATION_SOURCE_NAME);
127         source.setDefaultWorkspaceName(DEFAULT_WORKSPACE_NAME);
128 
129         // The file was imported successfully, so now create the content information ...
130         configurationContent = new ConfigurationDefinition("dna", source, null, null, context, null);
131     }
132 
133     /**
134      * Set the name of this configuration.
135      * 
136      * @param name the configuration name; may be null if the default name should be used
137      * @return this configuration
138      */
139     public ModeShapeConfiguration withName( String name ) {
140         if (name != null) name = "dna";
141         configurationContent = configurationContent.with(name);
142         return this;
143     }
144 
145     /**
146      * Load the configuration from a file at the given path.
147      * 
148      * @param pathToConfigurationFile the path the file containing the configuration information
149      * @return this configuration object, for convenience and method chaining
150      * @throws IOException if there is an error or problem reading the file at the supplied location
151      * @throws SAXException if the file is not a valid XML format
152      * @throws IllegalArgumentException if the path is null or empty
153      */
154     public ModeShapeConfiguration loadFrom( String pathToConfigurationFile ) throws IOException, SAXException {
155         CheckArg.isNotEmpty(pathToConfigurationFile, "pathToConfigurationFile");
156         return loadFrom(pathToConfigurationFile, DEFAULT_PATH);
157     }
158 
159     /**
160      * Load the configuration from a file at the given path.
161      * 
162      * @param pathToConfigurationFile the path the file containing the configuration information
163      * @param path path within the content to the parent containing the configuration information, or null if the
164      *        {@link #DEFAULT_PATH default path} should be used
165      * @return this configuration object, for convenience and method chaining
166      * @throws IOException if there is an error or problem reading the file at the supplied location
167      * @throws SAXException if the file is not a valid XML format
168      * @throws IllegalArgumentException if the path is null or empty
169      */
170     public ModeShapeConfiguration loadFrom( String pathToConfigurationFile,
171                                             String path ) throws IOException, SAXException {
172         CheckArg.isNotEmpty(pathToConfigurationFile, "pathToConfigurationFile");
173         return loadFrom(new File(pathToConfigurationFile), path);
174     }
175 
176     /**
177      * Load the configuration from a file.
178      * 
179      * @param configurationFile the file containing the configuration information
180      * @return this configuration object, for convenience and method chaining
181      * @throws IOException if there is an error or problem reading the supplied file
182      * @throws SAXException if the file is not a valid XML format
183      * @throws IllegalArgumentException if the file reference is null
184      */
185     public ModeShapeConfiguration loadFrom( File configurationFile ) throws IOException, SAXException {
186         CheckArg.isNotNull(configurationFile, "configurationFile");
187         return loadFrom(configurationFile, DEFAULT_PATH);
188     }
189 
190     /**
191      * Load the configuration from a file.
192      * 
193      * @param configurationFile the file containing the configuration information
194      * @param path path within the content to the parent containing the configuration information, or null if the
195      *        {@link #DEFAULT_PATH default path} should be used
196      * @return this configuration object, for convenience and method chaining
197      * @throws IOException if there is an error or problem reading the supplied file
198      * @throws SAXException if the file is not a valid XML format
199      * @throws IllegalArgumentException if the file reference is null
200      */
201     public ModeShapeConfiguration loadFrom( File configurationFile,
202                                             String path ) throws IOException, SAXException {
203         CheckArg.isNotNull(configurationFile, "configurationFile");
204         InputStream stream = new FileInputStream(configurationFile);
205         try {
206             return loadFrom(stream, path);
207         } finally {
208             stream.close();
209         }
210     }
211 
212     /**
213      * Load the configuration from a file at the supplied URL.
214      * 
215      * @param urlToConfigurationFile the URL of the file containing the configuration information
216      * @return this configuration object, for convenience and method chaining
217      * @throws IOException if there is an error or problem reading the file at the supplied URL
218      * @throws SAXException if the file is not a valid XML format
219      * @throws IllegalArgumentException if the URL is null
220      */
221     public ModeShapeConfiguration loadFrom( URL urlToConfigurationFile ) throws IOException, SAXException {
222         CheckArg.isNotNull(urlToConfigurationFile, "urlToConfigurationFile");
223         return loadFrom(urlToConfigurationFile, DEFAULT_PATH);
224     }
225 
226     /**
227      * Load the configuration from a file at the supplied URL.
228      * 
229      * @param urlToConfigurationFile the URL of the file containing the configuration information
230      * @param path path within the content to the parent containing the configuration information, or null if the
231      *        {@link #DEFAULT_PATH default path} should be used
232      * @return this configuration object, for convenience and method chaining
233      * @throws IOException if there is an error or problem reading the file at the supplied URL
234      * @throws SAXException if the file is not a valid XML format
235      * @throws IllegalArgumentException if the URL is null
236      */
237     public ModeShapeConfiguration loadFrom( URL urlToConfigurationFile,
238                                             String path ) throws IOException, SAXException {
239         CheckArg.isNotNull(urlToConfigurationFile, "urlToConfigurationFile");
240         InputStream stream = urlToConfigurationFile.openStream();
241         try {
242             return loadFrom(stream, path);
243         } finally {
244             stream.close();
245         }
246     }
247 
248     /**
249      * Load the configuration from a file at the supplied URL.
250      * 
251      * @param configurationFileInputStream the stream with the configuration information
252      * @return this configuration object, for convenience and method chaining
253      * @throws IOException if there is an error or problem reading the file at the supplied URL
254      * @throws SAXException if the file is not a valid XML format
255      * @throws IllegalArgumentException if the stream is null
256      */
257     public ModeShapeConfiguration loadFrom( InputStream configurationFileInputStream ) throws IOException, SAXException {
258         CheckArg.isNotNull(configurationFileInputStream, "configurationFileInputStream");
259         return loadFrom(configurationFileInputStream, DEFAULT_PATH);
260     }
261 
262     /**
263      * Load the configuration from a file at the supplied URL.
264      * 
265      * @param configurationFileInputStream the stream with the configuration information
266      * @param path path within the content to the parent containing the configuration information, or null if the
267      *        {@link #DEFAULT_PATH default path} should be used
268      * @return this configuration object, for convenience and method chaining
269      * @throws IOException if there is an error or problem reading the file at the supplied URL
270      * @throws SAXException if the file is not a valid XML format
271      * @throws IllegalArgumentException if the stream is null
272      */
273     public ModeShapeConfiguration loadFrom( InputStream configurationFileInputStream,
274                                             String path ) throws IOException, SAXException {
275         CheckArg.isNotNull(configurationFileInputStream, "configurationFileInputStream");
276 
277         // Create the in-memory repository source in which the content will be stored ...
278         InMemoryRepositorySource source = new InMemoryRepositorySource();
279         source.setName(DEFAULT_CONFIGURATION_SOURCE_NAME);
280         source.setDefaultWorkspaceName(DEFAULT_WORKSPACE_NAME);
281 
282         // Import the information into the source ...
283         Path pathToParent = path(path != null ? path : DEFAULT_PATH);
284         Graph graph = Graph.create(source, context);
285         graph.importXmlFrom(configurationFileInputStream).skippingRootElement(true).into(pathToParent);
286 
287         // The file was imported successfully, so now create the content information ...
288         configurationContent = new ConfigurationDefinition(configurationContent.getName(), source, null, pathToParent, context,
289                                                            null);
290         return this;
291     }
292 
293     /**
294      * Load the configuration from the repository content using the supplied repository source. This method assumes that the
295      * supplied source has already been configured and is ready to {@link RepositorySource#getConnection() create connections}.
296      * Also, the default workspace of the source will be used, and the configuration content may be found directly under the root
297      * node.
298      * 
299      * @param source the source that defines the repository with the configuration content
300      * @return this configuration object, for convenience and method chaining
301      * @throws IllegalArgumentException if the source is null
302      */
303     public ModeShapeConfiguration loadFrom( RepositorySource source ) {
304         return loadFrom(source, null, null);
305     }
306 
307     /**
308      * Load the configuration from the repository content using the workspace in the supplied repository source. This method
309      * assumes that the supplied source has already been configured and is ready to {@link RepositorySource#getConnection() create
310      * connections}. Also, the configuration content may be found directly under the root node.
311      * 
312      * @param source the source that defines the repository with the configuration content
313      * @param workspaceName the name of the workspace with the configuration content, or null if the source's default workspace
314      *        should be used
315      * @return this configuration object, for convenience and method chaining
316      * @throws IllegalArgumentException if the source is null
317      */
318     public ModeShapeConfiguration loadFrom( RepositorySource source,
319                                             String workspaceName ) {
320         CheckArg.isNotNull(source, "source");
321         return loadFrom(source, workspaceName, null);
322     }
323 
324     /**
325      * Load the configuration from the repository content at the supplied path in the workspace in the supplied repository source.
326      * This method assumes that the supplied source has already been configured and is ready to
327      * {@link RepositorySource#getConnection() create connections}.
328      * 
329      * @param source the source that defines the repository with the configuration content
330      * @param workspaceName the name of the workspace with the configuration content, or null if the source's default workspace
331      *        should be used
332      * @param pathInWorkspace the path to the parent node under which the configuration content may be found, or null if the
333      *        content may be found under the root node
334      * @return this configuration object, for convenience and method chaining
335      * @throws IllegalArgumentException if the source is null
336      */
337     public ModeShapeConfiguration loadFrom( RepositorySource source,
338                                             String workspaceName,
339                                             String pathInWorkspace ) {
340         CheckArg.isNotNull(source, "source");
341 
342         // Verify connectivity ...
343         Graph graph = Graph.create(source, context);
344         if (workspaceName != null) {
345             Workspace workspace = null;
346             try {
347                 workspace = graph.useWorkspace(workspaceName); // should throw exception if not connectable
348             } catch (InvalidWorkspaceException e) {
349                 // Try creating the workspace ...
350                 workspace = graph.createWorkspace().named(workspaceName);
351             }
352             assert workspace.getRoot() != null;
353         }
354 
355         // Verify the path ...
356         Path path = pathInWorkspace != null ? path(pathInWorkspace) : path(DEFAULT_PATH);
357         Node parent = graph.getNodeAt(path);
358         assert parent != null;
359 
360         // Now create the content information ...
361         configurationContent = new ConfigurationDefinition(configurationContent.getName(), source, workspaceName, path, context,
362                                                            null);
363         return this;
364     }
365 
366     /**
367      * Store the saved configuration to the file with the given name. Changes made without calling {@link #save()} will not be
368      * written to the file.
369      * 
370      * @param file the name of the file to which the configuration should be stored
371      * @throws SAXException if there is an error saving the configuration
372      * @throws IOException if the file cannot be created or if there is an error writing the configuration to the file.
373      */
374     public void storeTo( String file ) throws SAXException, IOException {
375         storeTo(new File(file));
376     }
377 
378     /**
379      * Store the saved configuration to the given file. Changes made without calling {@link #save()} will not be written to the
380      * file.
381      * 
382      * @param file the name of the file to which the configuration should be stored
383      * @throws SAXException if there is an error saving the configuration
384      * @throws IOException if the file cannot be created or if there is an error writing the configuration to the file.
385      */
386     public void storeTo( File file ) throws SAXException, IOException {
387         OutputStream os = null;
388         try {
389             os = new FileOutputStream(file);
390             storeTo(new StreamingContentHandler(os));
391         } finally {
392             if (os != null) os.close();
393         }
394     }
395 
396     /**
397      * Store the saved configuration to the stream. Changes made without calling {@link #save()} will not be written to the
398      * stream.
399      * 
400      * @param os the name of the file to which the configuration should be stored
401      * @throws SAXException if there is an error saving the configuration
402      */
403     public void storeTo( OutputStream os ) throws SAXException {
404         storeTo(new StreamingContentHandler(os));
405     }
406 
407     /**
408      * Traverse the saved configuration graph treating it as an XML document and calling the corresponding SAX event on the
409      * provided {@link ContentHandler}. Changes made without calling {@link #save()} will not be written to the stream.
410      * 
411      * @param handler the content handler that will receive the SAX events
412      * @throws SAXException if there is an error saving the configuration
413      */
414     public void storeTo( ContentHandler handler ) throws SAXException {
415         Subgraph allContent = configurationGraph().getSubgraphOfDepth(ReadBranchRequest.NO_MAXIMUM_DEPTH).at("/");
416 
417         Set<NamespaceRegistry.Namespace> namespaces = this.context.getNamespaceRegistry().getNamespaces();
418         Stack<String> mappedNamespacePrefixes = new Stack<String>();
419 
420         handler.startDocument();
421 
422         for (NamespaceRegistry.Namespace namespace : namespaces) {
423             handler.startPrefixMapping(namespace.getPrefix(), namespace.getNamespaceUri());
424             mappedNamespacePrefixes.push(namespace.getPrefix());
425         }
426 
427         exportNode(handler, allContent, allContent.getRoot());
428         while (!mappedNamespacePrefixes.isEmpty()) {
429             handler.endPrefixMapping(mappedNamespacePrefixes.pop());
430         }
431 
432         handler.endDocument();
433     }
434 
435     private void exportNode( ContentHandler handler,
436                              Subgraph subgraph,
437                              SubgraphNode node ) throws SAXException {
438         // Build the attributes
439 
440         NamespaceRegistry registry = this.context.getNamespaceRegistry();
441         ValueFactory<String> stringFactory = this.context.getValueFactories().getStringFactory();
442 
443         AttributesImpl atts = new AttributesImpl();
444 
445         for (Property prop : node.getProperties()) {
446             Name name = prop.getName();
447 
448             StringBuilder buff = new StringBuilder();
449             boolean first = true;
450 
451             for (Object rawValue : prop) {
452                 if (first) {
453                     first = false;
454                 } else {
455                     buff.append(",");
456                 }
457                 buff.append(stringFactory.create(rawValue));
458             }
459 
460             atts.addAttribute(name.getNamespaceUri(), name.getLocalName(), name.getString(registry), "string", buff.toString());
461         }
462 
463         // Start the node
464         Name nodeName;
465         Path nodePath = node.getLocation().getPath();
466         if (nodePath.isRoot()) {
467             nodeName = name("configuration");
468         } else {
469             nodeName = node.getLocation().getPath().getLastSegment().getName();
470         }
471         String uri = nodeName.getNamespaceUri();
472         String localName = nodeName.getLocalName();
473         String qName = nodeName.getString(registry);
474         handler.startElement(uri, localName, qName, atts);
475 
476         // Handle the children
477         for (Location childLocation : node.getChildren()) {
478             exportNode(handler, subgraph, subgraph.getNode(childLocation));
479         }
480 
481         // Finish the node
482         handler.endElement(uri, localName, qName);
483 
484     }
485 
486     /**
487      * Get the immutable representation of the information defining where the configuration content can be found.
488      * 
489      * @return the configuration definition
490      */
491     public ConfigurationDefinition getConfigurationDefinition() {
492         return configurationContent;
493     }
494 
495     protected ExecutionContext getExecutionContext() {
496         return configurationContent.getContext();
497     }
498 
499     protected Path path() {
500         return configurationContent.getPath();
501     }
502 
503     protected Path path( String path ) {
504         return context.getValueFactories().getPathFactory().create(path);
505     }
506 
507     protected Name name( String name ) {
508         return context.getValueFactories().getNameFactory().create(name);
509     }
510 
511     /**
512      * Get the problems (if any) that are associated with this configuration.
513      * 
514      * @return the problems
515      */
516     public Problems getProblems() {
517         return problems;
518     }
519 
520     protected Graph configurationGraph() {
521         ConfigurationDefinition content = getConfigurationDefinition();
522         Graph graph = Graph.create(content.getRepositorySource(), content.getContext());
523         if (content.getWorkspace() != null) {
524             graph.useWorkspace(content.getWorkspace());
525         }
526 
527         return graph;
528     }
529 
530     protected Graph.Batch changes() {
531         if (changes == null) {
532             changes = configurationGraph().batch();
533         }
534         return changes;
535     }
536 
537     /**
538      * Determine if there are any unsaved changes to this configuration that must be {@link #save() saved} before they take
539      * effect.
540      * 
541      * @return true if a {@link #save()} is required, or false no changes have been made to the configuration since the last
542      *         {@link #save()}
543      */
544     public boolean hasChanges() {
545         Graph.Batch changes = this.changes;
546         return changes != null && changes.isExecuteRequired();
547     }
548 
549     /**
550      * Persist any unsaved changes that have been made to this configuration. This method has no effect if there are currently
551      * {@link #hasChanges() no unsaved changes}.
552      * 
553      * @return this configuration, for method chaining purposes
554      */
555     public ModeShapeConfiguration save() {
556         Graph.Batch changes = this.changes;
557         if (changes != null && changes.isExecuteRequired()) {
558             changes.execute();
559         }
560         this.changes = null;
561         sequencerDefinitions.clear();
562         mimeTypeDetectorDefinitions.clear();
563         repositorySourceDefinitions.clear();
564         return this;
565     }
566 
567     /**
568      * Specify the {@link ClassLoaderFactory} that should be used to load the classes for the various components. Most of the
569      * definitions can specify the {@link LoadedFrom#loadedFrom(String...) classpath} that should be used, and that classpath is
570      * passed to the supplied ClassLoaderFactory instance to obtain a {@link ClassLoader} for the class.
571      * <p>
572      * If not called, this configuration will use the class loader that loaded this configuration's class.
573      * </p>
574      * 
575      * @param classLoaderFactory the class loader factory implementation, or null if the classes should be loaded using the class
576      *        loader of this object
577      * @return this configuration, for method chaining purposes
578      */
579     public ModeShapeConfiguration withClassLoaderFactory( ClassLoaderFactory classLoaderFactory ) {
580         this.configurationContent = this.configurationContent.with(classLoaderFactory);
581         return this;
582     }
583 
584     protected Set<String> getNamesOfComponentsUnder( Name parentName ) {
585         Set<String> names = new HashSet<String>();
586         try {
587             ConfigurationDefinition content = this.getConfigurationDefinition();
588             Path path = context.getValueFactories().getPathFactory().create(content.getPath(), parentName);
589             for (Location child : content.graph().getChildren().of(path)) {
590                 names.add(child.getPath().getLastSegment().getString(context.getNamespaceRegistry()));
591             }
592         } catch (PathNotFoundException e) {
593             // Nothing has been saved yet ...
594         }
595         return names;
596     }
597 
598     /**
599      * Get the list of MIME type detector definitions.
600      * 
601      * @return the unmodifiable set of definitions; never null but possibly empty if there are no definitions
602      */
603     public Set<MimeTypeDetectorDefinition<? extends ModeShapeConfiguration>> mimeTypeDetectors() {
604         // Get the children under the 'dna:mimeTypeDetectors' node ...
605         Set<String> names = getNamesOfComponentsUnder(ModeShapeLexicon.MIME_TYPE_DETECTORS);
606         names.addAll(this.mimeTypeDetectorDefinitions.keySet());
607         Set<MimeTypeDetectorDefinition<? extends ModeShapeConfiguration>> results = new HashSet<MimeTypeDetectorDefinition<? extends ModeShapeConfiguration>>();
608         for (String name : names) {
609             results.add(mimeTypeDetector(name));
610         }
611         return Collections.unmodifiableSet(results);
612     }
613 
614     /**
615      * Get the list of repository source definitions.
616      * 
617      * @return the unmodifiable set of definitions; never null but possibly empty if there are no definitions
618      */
619     public Set<RepositorySourceDefinition<? extends ModeShapeConfiguration>> repositorySources() {
620         // Get the children under the 'dna:mimeTypeDetectors' node ...
621         Set<String> names = getNamesOfComponentsUnder(ModeShapeLexicon.SOURCES);
622         names.addAll(this.repositorySourceDefinitions.keySet());
623         Set<RepositorySourceDefinition<? extends ModeShapeConfiguration>> results = new HashSet<RepositorySourceDefinition<? extends ModeShapeConfiguration>>();
624         for (String name : names) {
625             results.add(repositorySource(name));
626         }
627         return Collections.unmodifiableSet(results);
628     }
629 
630     /**
631      * Get the list of sequencer definitions.
632      * 
633      * @return the unmodifiable set of definitions; never null but possibly empty if there are no definitions
634      */
635     public Set<SequencerDefinition<? extends ModeShapeConfiguration>> sequencers() {
636         // Get the children under the 'dna:mimeTypeDetectors' node ...
637         Set<String> names = getNamesOfComponentsUnder(ModeShapeLexicon.SEQUENCERS);
638         names.addAll(this.sequencerDefinitions.keySet());
639         Set<SequencerDefinition<? extends ModeShapeConfiguration>> results = new HashSet<SequencerDefinition<? extends ModeShapeConfiguration>>();
640         for (String name : names) {
641             results.add(sequencer(name));
642         }
643         return Collections.unmodifiableSet(results);
644     }
645 
646     /**
647      * Obtain or create a definition for the {@link MimeTypeDetector MIME type detector} with the supplied name or identifier. A
648      * new definition will be created if there currently is no MIME type detector defined with the supplied name.
649      * 
650      * @param name the name or identifier of the detector
651      * @return the details of the MIME type detector definition; never null
652      */
653     public MimeTypeDetectorDefinition<? extends ModeShapeConfiguration> mimeTypeDetector( String name ) {
654         return mimeTypeDetectorDefinition(this, name);
655     }
656 
657     /**
658      * Obtain or create a definition for the {@link RepositorySource} with the supplied name or identifier. A new definition will
659      * be created if there currently is no repository source defined with the supplied name.
660      * 
661      * @param name the name or identifier of the repository source
662      * @return the details of the repository source definition; never null
663      */
664     public RepositorySourceDefinition<? extends ModeShapeConfiguration> repositorySource( String name ) {
665         return repositorySourceDefinition(this, name);
666     }
667 
668     /**
669      * Obtain or create a definition for the {@link StreamSequencer sequencer} with the supplied name or identifier. A new
670      * definition will be created if there currently is no sequencer defined with the supplied name.
671      * 
672      * @param name the name or identifier of the sequencer
673      * @return the details of the sequencer definition; never null
674      */
675     public SequencerDefinition<? extends ModeShapeConfiguration> sequencer( String name ) {
676         return sequencerDefinition(this, name);
677     }
678 
679     /**
680      * Convenience method to make the code that sets up this configuration easier to read. This method simply returns this object.
681      * 
682      * @return this configuration component; never null
683      */
684     public ModeShapeConfiguration and() {
685         return this;
686     }
687 
688     /**
689      * Construct an engine that reflects the current state of this configuration. This method always creates a new instance.
690      * 
691      * @return the resulting engine; never null
692      * @see #getExecutionContextForEngine()
693      */
694     public ModeShapeEngine build() {
695         save();
696         return new ModeShapeEngine(getExecutionContextForEngine(), getConfigurationDefinition());
697     }
698 
699     /**
700      * Utility method used by {@link #build()} to get the {@link ExecutionContext} instance for the engine. This method gives
701      * subclasses the ability to override this behavior.
702      * <p>
703      * Currently, this method wraps the {@link #getExecutionContext() configuration's execution context} to provide
704      * backward-compability with JBoss DNA namespaces. See MODE-647 for details.
705      * </p>
706      * 
707      * @return the execution context to be used for the engine
708      * @see #build()
709      */
710     protected ExecutionContext getExecutionContextForEngine() {
711         return new DnaExecutionContext(getExecutionContext());
712     }
713 
714     /**
715      * Interface that defines the ability to obtain the configuration component.
716      * 
717      * @param <ReturnType> the interface returned from these methods
718      */
719     public interface Returnable<ReturnType> {
720         /**
721          * Return the configuration component.
722          * 
723          * @return the configuration component; never null
724          */
725         ReturnType and();
726     }
727 
728     /**
729      * Interface that defines the ability to remove the configuration component.
730      * 
731      * @param <ReturnType> the configuration interface returned from these methods
732      */
733     public interface Removable<ReturnType> {
734         /**
735          * Remove this configuration component.
736          * 
737          * @return the configuration; never null
738          */
739         ReturnType remove();
740     }
741 
742     /**
743      * The interface used to set a description on a component.
744      * 
745      * @param <ReturnType> the interface returned from these methods
746      */
747     public interface SetDescription<ReturnType> {
748         /**
749          * Specify the description of this component.
750          * 
751          * @param description the description; may be null or empty
752          * @return the next component to continue configuration; never null
753          */
754         ReturnType setDescription( String description );
755 
756         /**
757          * Get the description of this component.
758          * 
759          * @return the description, or null if there is no description
760          */
761         String getDescription();
762     }
763 
764     /**
765      * Interface for configuring the JavaBean-style properties of an object.
766      * 
767      * @param <ReturnType> the interface returned after the property has been set.
768      * @author Randall Hauch
769      */
770     public interface SetProperties<ReturnType> {
771         /**
772          * Set the property value to an integer.
773          * 
774          * @param beanPropertyName the name of the JavaBean-style property (e.g., "retryLimit")
775          * @param value the new value for the property
776          * @return the next component to continue configuration; never null
777          */
778         ReturnType setProperty( String beanPropertyName,
779                                 int value );
780 
781         /**
782          * Set the property value to a long number.
783          * 
784          * @param beanPropertyName the name of the JavaBean-style property (e.g., "retryLimit")
785          * @param value the new value for the property
786          * @return the next component to continue configuration; never null
787          */
788         ReturnType setProperty( String beanPropertyName,
789                                 long value );
790 
791         /**
792          * Set the property value to a short.
793          * 
794          * @param beanPropertyName the name of the JavaBean-style property (e.g., "retryLimit")
795          * @param value the new value for the property
796          * @return the next component to continue configuration; never null
797          */
798         ReturnType setProperty( String beanPropertyName,
799                                 short value );
800 
801         /**
802          * Set the property value to a boolean.
803          * 
804          * @param beanPropertyName the name of the JavaBean-style property (e.g., "retryLimit")
805          * @param value the new value for the property
806          * @return the next component to continue configuration; never null
807          */
808         ReturnType setProperty( String beanPropertyName,
809                                 boolean value );
810 
811         /**
812          * Set the property value to a float.
813          * 
814          * @param beanPropertyName the name of the JavaBean-style property (e.g., "retryLimit")
815          * @param value the new value for the property
816          * @return the next component to continue configuration; never null
817          */
818         ReturnType setProperty( String beanPropertyName,
819                                 float value );
820 
821         /**
822          * Set the property value to a double.
823          * 
824          * @param beanPropertyName the name of the JavaBean-style property (e.g., "retryLimit")
825          * @param value the new value for the property
826          * @return the next component to continue configuration; never null
827          */
828         ReturnType setProperty( String beanPropertyName,
829                                 double value );
830 
831         /**
832          * Set the property value to a string.
833          * 
834          * @param beanPropertyName the name of the JavaBean-style property (e.g., "retryLimit")
835          * @param value the new value for the property
836          * @return the next component to continue configuration; never null
837          */
838         ReturnType setProperty( String beanPropertyName,
839                                 String value );
840 
841         /**
842          * Set the property value to a string.
843          * 
844          * @param beanPropertyName the name of the JavaBean-style property (e.g., "retryLimit")
845          * @param value the first string value for the property
846          * @param additionalValues the additional string values for the property
847          * @return the next component to continue configuration; never null
848          */
849         ReturnType setProperty( String beanPropertyName,
850                                 String value,
851                                 String... additionalValues );
852 
853         /**
854          * Set the property value to an object.
855          * 
856          * @param beanPropertyName the name of the JavaBean-style property (e.g., "retryLimit")
857          * @param value the new value for the property
858          * @return the next component to continue configuration; never null
859          */
860         ReturnType setProperty( String beanPropertyName,
861                                 Object value );
862 
863         /**
864          * Set the property values to an object.
865          * 
866          * @param beanPropertyName the name of the JavaBean-style property (e.g., "retryLimit")
867          * @param values the array of new values for the property
868          * @return the next component to continue configuration; never null
869          */
870         ReturnType setProperty( String beanPropertyName,
871                                 Object[] values );
872 
873         /**
874          * Get the property.
875          * 
876          * @param beanPropertyName the name of the JavaBean-style property (e.g., "retryLimit")
877          * @return the property object, or null if there is no such property
878          */
879         Property getProperty( String beanPropertyName );
880     }
881 
882     /**
883      * The interface used to configure the class used for a component.
884      * 
885      * @param <ComponentClassType> the class or interface that the component is to implement
886      * @param <ReturnType> the interface returned from these methods
887      */
888     public interface ChooseClass<ComponentClassType, ReturnType> {
889 
890         /**
891          * Specify the name of the class that should be instantiated for the instance. The classpath information will need to be
892          * defined using the returned interface.
893          * 
894          * @param classname the name of the class that should be instantiated
895          * @return the interface used to define the classpath information; never null
896          * @throws IllegalArgumentException if the class name is null, empty, blank, or not a valid class name
897          */
898         LoadedFrom<ReturnType> usingClass( String classname );
899 
900         /**
901          * Specify the class that should be instantiated for the instance. Because the class is already available to this class
902          * loader, there is no need to specify the classloader information.
903          * 
904          * @param clazz the class that should be instantiated
905          * @return the next component to continue configuration; never null
906          * @throws ModeShapeConfigurationException if the class could not be accessed and instantiated (if needed)
907          * @throws IllegalArgumentException if the class reference is null
908          */
909         ReturnType usingClass( Class<? extends ComponentClassType> clazz );
910     }
911 
912     /**
913      * Interface for specifying from where the component's class is to be loaded.
914      * 
915      * @param <ReturnType> the interface returned from these methods
916      */
917     public interface LoadedFrom<ReturnType> {
918         /**
919          * Specify the names of the classloaders that form the classpath for the component, from which the component's class (and
920          * its dependencies) can be loaded. The names correspond to the names supplied to the
921          * {@link ExecutionContext#getClassLoader(String...)} methods.
922          * 
923          * @param classPathNames the names for the classloaders, as passed to the {@link ClassLoaderFactory} implementation (e.g.,
924          *        the {@link ExecutionContext}).
925          * @return the next component to continue configuration; never null
926          * @see #loadedFromClasspath()
927          * @see ExecutionContext#getClassLoader(String...)
928          */
929         ReturnType loadedFrom( String... classPathNames );
930 
931         /**
932          * Specify that the component (and its dependencies) will be found on the current (or
933          * {@link Thread#getContextClassLoader() current context}) classloader.
934          * 
935          * @return the next component to continue configuration; never null
936          * @see #loadedFrom(String...)
937          * @see ExecutionContext#getClassLoader(String...)
938          */
939         ReturnType loadedFromClasspath();
940     }
941 
942     /**
943      * Interface for a component that has a name.
944      */
945     public interface HasName {
946         /**
947          * Get the name.
948          * 
949          * @return the name; never null
950          */
951         String getName();
952     }
953 
954     /**
955      * Interface used to set up and define a MIME type detector instance.
956      * 
957      * @param <ReturnType> the type of the configuration component that owns this definition object
958      */
959     public interface MimeTypeDetectorDefinition<ReturnType>
960         extends Returnable<ReturnType>, SetDescription<MimeTypeDetectorDefinition<ReturnType>>,
961         SetProperties<MimeTypeDetectorDefinition<ReturnType>>,
962         ChooseClass<MimeTypeDetector, MimeTypeDetectorDefinition<ReturnType>>, Removable<ReturnType> {
963     }
964 
965     /**
966      * Interface used to set up and define a RepositorySource instance.
967      * 
968      * @param <ReturnType> the type of the configuration component that owns this definition object
969      */
970     public interface RepositorySourceDefinition<ReturnType>
971         extends Returnable<ReturnType>, SetDescription<RepositorySourceDefinition<ReturnType>>,
972         SetProperties<RepositorySourceDefinition<ReturnType>>,
973         ChooseClass<RepositorySource, RepositorySourceDefinition<ReturnType>>, Removable<ReturnType>, HasName {
974 
975         /**
976          * Set the retry limit on the repository source. This is equivalent to calling {@link #setProperty(String, int)} with "
977          * {@link ModeShapeLexicon#RETRY_LIMIT dna:retryLimit}" as the property name.
978          * 
979          * @param retryLimit the retry limit
980          * @return this definition, for method chaining purposes
981          * @see RepositorySource#setRetryLimit(int)
982          */
983         RepositorySourceDefinition<ReturnType> setRetryLimit( int retryLimit );
984     }
985 
986     /**
987      * Interface used to set up and define a {@link StreamSequencer sequencer} instance.
988      * 
989      * @param <ReturnType> the type of the configuration component that owns this definition object
990      */
991     public interface SequencerDefinition<ReturnType>
992         extends Returnable<ReturnType>, SetDescription<SequencerDefinition<ReturnType>>,
993         SetProperties<SequencerDefinition<ReturnType>>, ChooseClass<StreamSequencer, SequencerDefinition<ReturnType>>,
994         Removable<ReturnType> {
995 
996         /**
997          * Specify the input {@link PathExpression path expression} represented as a string, which determines when this sequencer
998          * will be executed.
999          * 
1000          * @param inputPathExpression the path expression for nodes that, when they change, will be passed as an input to the
1001          *        sequencer
1002          * @return the interface used to specify the output path expression; never null
1003          */
1004         PathExpressionOutput<ReturnType> sequencingFrom( String inputPathExpression );
1005 
1006         /**
1007          * Specify the input {@link PathExpression path expression}, which determines when this sequencer will be executed.
1008          * 
1009          * @param inputPathExpression the path expression for nodes that, when they change, will be passed as an input to the
1010          *        sequencer
1011          * @return the interface used to continue specifying the configuration of the sequencer
1012          */
1013         SequencerDefinition<ReturnType> sequencingFrom( PathExpression inputPathExpression );
1014 
1015         /**
1016          * Get the path expressions from the saved configuration.
1017          * 
1018          * @return the set of path expressions; never null but possibly empty
1019          */
1020         Set<PathExpression> getPathExpressions();
1021     }
1022 
1023     /**
1024      * Interface used to specify the output path expression for a
1025      * {@link ModeShapeConfiguration.SequencerDefinition#sequencingFrom(PathExpression) sequencer configuration}.
1026      * 
1027      * @param <ReturnType>
1028      */
1029     public interface PathExpressionOutput<ReturnType> {
1030         /**
1031          * Specify the output {@link PathExpression path expression}, which determines where this sequencer's output will be
1032          * placed.
1033          * 
1034          * @param outputExpression the path expression for the location(s) where output generated by the sequencer is to be placed
1035          * @return the interface used to continue specifying the configuration of the sequencer
1036          */
1037         SequencerDefinition<ReturnType> andOutputtingTo( String outputExpression );
1038     }
1039 
1040     /**
1041      * Utility method to construct a definition object for the detector with the supplied name and return type.
1042      * 
1043      * @param <ReturnType> the type of the return object
1044      * @param returnObject the return object
1045      * @param name the name of the detector
1046      * @return the definition for the detector
1047      */
1048     @SuppressWarnings( "unchecked" )
1049     protected <ReturnType extends ModeShapeConfiguration> MimeTypeDetectorDefinition<ReturnType> mimeTypeDetectorDefinition( ReturnType returnObject,
1050                                                                                                                              String name ) {
1051         MimeTypeDetectorDefinition<ReturnType> definition = (MimeTypeDetectorDefinition<ReturnType>)mimeTypeDetectorDefinitions.get(name);
1052         if (definition == null) {
1053             definition = new MimeTypeDetectorBuilder<ReturnType>(returnObject, changes(), path(),
1054                                                                  ModeShapeLexicon.MIME_TYPE_DETECTORS, name(name));
1055             mimeTypeDetectorDefinitions.put(name, definition);
1056         }
1057         return definition;
1058     }
1059 
1060     /**
1061      * Utility method to construct a definition object for the repository source with the supplied name and return type.
1062      * 
1063      * @param <ReturnType> the type of the return object
1064      * @param returnObject the return object
1065      * @param name the name of the repository source
1066      * @return the definition for the repository source
1067      */
1068     @SuppressWarnings( "unchecked" )
1069     protected <ReturnType extends ModeShapeConfiguration> RepositorySourceDefinition<ReturnType> repositorySourceDefinition( ReturnType returnObject,
1070                                                                                                                              String name ) {
1071         RepositorySourceDefinition<ReturnType> definition = (RepositorySourceDefinition<ReturnType>)repositorySourceDefinitions.get(name);
1072         if (definition == null) {
1073             definition = new SourceBuilder<ReturnType>(returnObject, changes(), path(), ModeShapeLexicon.SOURCES, name(name));
1074             repositorySourceDefinitions.put(name, definition);
1075         }
1076         return definition;
1077     }
1078 
1079     /**
1080      * Utility method to construct a definition object for the sequencer with the supplied name and return type.
1081      * 
1082      * @param <ReturnType> the type of the return object
1083      * @param returnObject the return object
1084      * @param name the name of the sequencer
1085      * @return the definition for the sequencer
1086      */
1087     @SuppressWarnings( "unchecked" )
1088     protected <ReturnType extends ModeShapeConfiguration> SequencerDefinition<ReturnType> sequencerDefinition( ReturnType returnObject,
1089                                                                                                                String name ) {
1090         SequencerDefinition<ReturnType> definition = (SequencerDefinition<ReturnType>)sequencerDefinitions.get(name);
1091         if (definition == null) {
1092             definition = new SequencerBuilder<ReturnType>(returnObject, changes(), path(), ModeShapeLexicon.SEQUENCERS,
1093                                                           name(name));
1094             sequencerDefinitions.put(name, definition);
1095         }
1096         return definition;
1097     }
1098 
1099     protected static class BaseReturnable<ReturnType> implements Returnable<ReturnType> {
1100         protected final ReturnType returnObject;
1101 
1102         protected BaseReturnable( ReturnType returnObject ) {
1103             this.returnObject = returnObject;
1104         }
1105 
1106         /**
1107          * {@inheritDoc}
1108          * 
1109          * @see org.modeshape.repository.ModeShapeConfiguration.Returnable#and()
1110          */
1111         public ReturnType and() {
1112             return returnObject;
1113         }
1114     }
1115 
1116     /**
1117      * Base class for {@link Returnable} types that work on a node in the graph.
1118      * 
1119      * @param <ReturnType> the type to be returned
1120      * @param <ThisType> the type to be returned by the set properties, set description, etc. methods
1121      */
1122     protected static abstract class GraphReturnable<ReturnType, ThisType> extends BaseReturnable<ReturnType>
1123         implements SetDescription<ThisType>, SetProperties<ThisType>, Removable<ReturnType> {
1124         protected final ExecutionContext context;
1125         protected final Graph.Batch batch;
1126         protected final Path path;
1127         private Map<Name, Property> properties = new HashMap<Name, Property>();
1128 
1129         protected GraphReturnable( ReturnType returnObject,
1130                                    Graph.Batch batch,
1131                                    Path path,
1132                                    Name... names ) {
1133             super(returnObject);
1134             assert batch != null;
1135             assert path != null;
1136             assert names.length > 0;
1137             this.context = batch.getGraph().getContext();
1138             this.batch = batch;
1139             // Make sure there are nodes down to the supplied path ...
1140             createIfMissing(path, names).and();
1141             this.path = context.getValueFactories().getPathFactory().create(path, names);
1142             try {
1143                 properties = batch.getGraph().getPropertiesByName().on(this.path);
1144             } catch (PathNotFoundException e) {
1145                 // The node doesn't exist yet (wasn't yet saved)
1146                 properties = new HashMap<Name, Property>();
1147             }
1148         }
1149 
1150         /**
1151          * Create the node at the supplied path under the current path, and return the Create operation for the last node created.
1152          * The caller <i>must</i> call {@link Graph.Create#and()} to complete the operation.
1153          * 
1154          * @param child the name of the child
1155          * @param segments the segments in the remainder of the path
1156          * @return the newly-created but incomplete operation
1157          */
1158         protected Graph.Create<Graph.Batch> createIfMissing( Name child,
1159                                                              String... segments ) {
1160             Path nodePath = context.getValueFactories().getPathFactory().create(path, child);
1161             Graph.Create<Graph.Batch> result = batch.create(nodePath).orUpdate();
1162             for (String name : segments) {
1163                 result.and();
1164                 nodePath = context.getValueFactories().getPathFactory().create(nodePath, name);
1165                 result = batch.create(nodePath).orUpdate();
1166             }
1167             return result;
1168         }
1169 
1170         /**
1171          * Create the node at the supplied path under the current path, and return the Create operation for the last node created.
1172          * The caller <i>must</i> call {@link Graph.Create#and()} to complete the operation.
1173          * 
1174          * @param segment the name segment for the child
1175          * @return the newly-created but incomplete operation
1176          */
1177         protected Graph.Create<Graph.Batch> createIfMissing( Name segment ) {
1178             Path nodePath = context.getValueFactories().getPathFactory().create(path, segment);
1179             Graph.Create<Graph.Batch> result = batch.create(nodePath).orUpdate();
1180             return result;
1181         }
1182 
1183         /**
1184          * Create the node at the supplied path under the current path, and return the Create operation for the last node created.
1185          * The caller <i>must</i> call {@link Graph.Create#and()} to complete the operation.
1186          * 
1187          * @param path the path to the node
1188          * @param segments the segments in the remainder of the path
1189          * @return the newly-created but incomplete operation
1190          */
1191         protected Graph.Create<Graph.Batch> createIfMissing( Path path,
1192                                                              Name... segments ) {
1193             Path nodePath = path;
1194             Graph.Create<Graph.Batch> result = null;
1195             for (Name name : segments) {
1196                 if (result != null) result.and();
1197                 nodePath = context.getValueFactories().getPathFactory().create(nodePath, name);
1198                 result = batch.create(nodePath).orUpdate();
1199             }
1200             return result;
1201         }
1202 
1203         protected Path subpath( Name... segments ) {
1204             return context.getValueFactories().getPathFactory().create(path, segments);
1205         }
1206 
1207         protected abstract ThisType thisType();
1208 
1209         public String getName() {
1210             return path.getLastSegment().getName().getString(context.getNamespaceRegistry());
1211         }
1212 
1213         public ThisType setDescription( String description ) {
1214             return setProperty(ModeShapeLexicon.DESCRIPTION, description);
1215         }
1216 
1217         public String getDescription() {
1218             Property property = getProperty(ModeShapeLexicon.DESCRIPTION);
1219             if (property != null && !property.isEmpty()) {
1220                 return context.getValueFactories().getStringFactory().create(property.getFirstValue());
1221             }
1222             return null;
1223         }
1224 
1225         protected ThisType setProperty( Name propertyName,
1226                                         Object value ) {
1227             // Set the property via the batch ...
1228             batch.set(propertyName).on(path).to(value).and();
1229             // Record that we changed this property ...
1230             properties.put(propertyName, context.getPropertyFactory().create(propertyName, value));
1231             return thisType();
1232         }
1233 
1234         public ThisType setProperty( String propertyName,
1235                                      Object value ) {
1236             return setProperty(context.getValueFactories().getNameFactory().create(propertyName), value);
1237         }
1238 
1239         public ThisType setProperty( Name propertyName,
1240                                      Object[] values ) {
1241             // Set the property via the batch ...
1242             batch.set(propertyName).on(path).to(values).and();
1243             // Record that we changed this property ...
1244             properties.put(propertyName, context.getPropertyFactory().create(propertyName, values));
1245             return thisType();
1246         }
1247 
1248         public ThisType setProperty( String propertyName,
1249                                      Object[] values ) {
1250             return setProperty(context.getValueFactories().getNameFactory().create(propertyName), values);
1251         }
1252 
1253         public ThisType setProperty( String beanPropertyName,
1254                                      boolean value ) {
1255             return setProperty(beanPropertyName, (Object)value);
1256         }
1257 
1258         public ThisType setProperty( String beanPropertyName,
1259                                      int value ) {
1260             return setProperty(beanPropertyName, (Object)value);
1261         }
1262 
1263         public ThisType setProperty( String beanPropertyName,
1264                                      short value ) {
1265             return setProperty(beanPropertyName, (Object)value);
1266         }
1267 
1268         public ThisType setProperty( String beanPropertyName,
1269                                      long value ) {
1270             return setProperty(beanPropertyName, (Object)value);
1271         }
1272 
1273         public ThisType setProperty( String beanPropertyName,
1274                                      double value ) {
1275             return setProperty(beanPropertyName, (Object)value);
1276         }
1277 
1278         public ThisType setProperty( String beanPropertyName,
1279                                      float value ) {
1280             return setProperty(beanPropertyName, (Object)value);
1281         }
1282 
1283         public ThisType setProperty( String beanPropertyName,
1284                                      String value ) {
1285             return setProperty(beanPropertyName, (Object)value);
1286         }
1287 
1288         public ThisType setProperty( String beanPropertyName,
1289                                      String firstValue,
1290                                      String... additionalValues ) {
1291             Object[] values = new Object[1 + additionalValues.length];
1292             values[0] = firstValue;
1293             System.arraycopy(additionalValues, 0, values, 1, additionalValues.length);
1294             return setProperty(beanPropertyName, values);
1295         }
1296 
1297         public Property getProperty( String beanPropertyName ) {
1298             return properties.get(context.getValueFactories().getNameFactory().create(beanPropertyName));
1299         }
1300 
1301         public Property getProperty( Name beanPropertyName ) {
1302             return properties.get(beanPropertyName);
1303         }
1304 
1305         public ReturnType remove() {
1306             batch.delete(path);
1307             properties.clear();
1308             return and();
1309         }
1310     }
1311 
1312     /**
1313      * Base class for {@link Returnable} types that work on a node in the graph.
1314      * 
1315      * @param <ReturnType> the type to be returned
1316      * @param <ThisType> the type to be returned by the set properties, set description, etc. methods
1317      * @param <ComponentType> the type of the component being configured
1318      */
1319     protected static abstract class GraphComponentBuilder<ReturnType, ThisType, ComponentType>
1320         extends GraphReturnable<ReturnType, ThisType> implements ChooseClass<ComponentType, ThisType> {
1321         protected GraphComponentBuilder( ReturnType returnObject,
1322                                          Graph.Batch batch,
1323                                          Path path,
1324                                          Name... names ) {
1325             super(returnObject, batch, path, names);
1326         }
1327 
1328         public LoadedFrom<ThisType> usingClass( final String classname ) {
1329             return new LoadedFrom<ThisType>() {
1330                 public ThisType loadedFromClasspath() {
1331                     return setProperty(ModeShapeLexicon.CLASSNAME, classname);
1332                 }
1333 
1334                 public ThisType loadedFrom( String... classpath ) {
1335                     List<String> classpaths = new ArrayList<String>();
1336                     // Ignore any null, zero-length, or duplicate elements ...
1337                     for (String value : classpath) {
1338                         if (value == null) continue;
1339                         value = value.trim();
1340                         if (value.length() == 0) continue;
1341                         if (!classpaths.contains(value)) classpaths.add(value);
1342                     }
1343                     if (classpaths.size() != 0) {
1344                         classpath = classpaths.toArray(new String[classpaths.size()]);
1345                         setProperty(ModeShapeLexicon.CLASSPATH, classpath);
1346                     }
1347                     return setProperty(ModeShapeLexicon.CLASSNAME, classname);
1348                 }
1349             };
1350         }
1351 
1352         public ThisType usingClass( Class<? extends ComponentType> componentClass ) {
1353             return setProperty(ModeShapeLexicon.CLASSNAME, componentClass.getCanonicalName());
1354         }
1355     }
1356 
1357     protected static class MimeTypeDetectorBuilder<ReturnType>
1358         extends GraphComponentBuilder<ReturnType, MimeTypeDetectorDefinition<ReturnType>, MimeTypeDetector>
1359         implements MimeTypeDetectorDefinition<ReturnType> {
1360         protected MimeTypeDetectorBuilder( ReturnType returnObject,
1361                                            Graph.Batch batch,
1362                                            Path path,
1363                                            Name... names ) {
1364             super(returnObject, batch, path, names);
1365         }
1366 
1367         @Override
1368         protected MimeTypeDetectorBuilder<ReturnType> thisType() {
1369             return this;
1370         }
1371 
1372     }
1373 
1374     protected static class SourceBuilder<ReturnType>
1375         extends GraphComponentBuilder<ReturnType, RepositorySourceDefinition<ReturnType>, RepositorySource>
1376         implements RepositorySourceDefinition<ReturnType> {
1377         protected SourceBuilder( ReturnType returnObject,
1378                                  Graph.Batch batch,
1379                                  Path path,
1380                                  Name... names ) {
1381             super(returnObject, batch, path, names);
1382         }
1383 
1384         @Override
1385         protected RepositorySourceDefinition<ReturnType> thisType() {
1386             return this;
1387         }
1388 
1389         public RepositorySourceDefinition<ReturnType> setRetryLimit( int retryLimit ) {
1390             return setProperty(ModeShapeLexicon.RETRY_LIMIT, retryLimit);
1391         }
1392 
1393         @Override
1394         public RepositorySourceDefinition<ReturnType> setProperty( String propertyName,
1395                                                                    Object value ) {
1396             Name name = context.getValueFactories().getNameFactory().create(propertyName);
1397             // Check the "standard" names that should be prefixed with 'dna:'
1398             if (name.getLocalName().equals(ModeShapeLexicon.RETRY_LIMIT.getLocalName())) name = ModeShapeLexicon.RETRY_LIMIT;
1399             if (name.getLocalName().equals(ModeShapeLexicon.DESCRIPTION.getLocalName())) name = ModeShapeLexicon.DESCRIPTION;
1400             return super.setProperty(name, value);
1401         }
1402 
1403         @Override
1404         public Property getProperty( Name name ) {
1405             // Check the "standard" names that should be prefixed with 'dna:'
1406             if (name.getLocalName().equals(ModeShapeLexicon.RETRY_LIMIT.getLocalName())) name = ModeShapeLexicon.RETRY_LIMIT;
1407             if (name.getLocalName().equals(ModeShapeLexicon.DESCRIPTION.getLocalName())) name = ModeShapeLexicon.DESCRIPTION;
1408             return super.getProperty(name);
1409         }
1410     }
1411 
1412     protected static class SequencerBuilder<ReturnType>
1413         extends GraphComponentBuilder<ReturnType, SequencerDefinition<ReturnType>, StreamSequencer>
1414         implements SequencerDefinition<ReturnType> {
1415 
1416         protected SequencerBuilder( ReturnType returnObject,
1417                                     Graph.Batch batch,
1418                                     Path path,
1419                                     Name... names ) {
1420             super(returnObject, batch, path, names);
1421         }
1422 
1423         @Override
1424         protected SequencerDefinition<ReturnType> thisType() {
1425             return this;
1426         }
1427 
1428         public Set<PathExpression> getPathExpressions() {
1429             Set<PathExpression> expressions = new HashSet<PathExpression>();
1430             try {
1431                 Property existingExpressions = getProperty(ModeShapeLexicon.PATH_EXPRESSION);
1432                 if (existingExpressions != null) {
1433                     for (Object existing : existingExpressions.getValuesAsArray()) {
1434                         String existingExpression = context.getValueFactories().getStringFactory().create(existing);
1435                         expressions.add(PathExpression.compile(existingExpression));
1436                     }
1437                 }
1438             } catch (PathNotFoundException e) {
1439                 // Nothing saved yet ...
1440             }
1441             return expressions;
1442         }
1443 
1444         public SequencerDefinition<ReturnType> sequencingFrom( PathExpression expression ) {
1445             CheckArg.isNotNull(expression, "expression");
1446             Set<PathExpression> compiledExpressions = getPathExpressions();
1447             compiledExpressions.add(expression);
1448             String[] strings = new String[compiledExpressions.size()];
1449             int index = 0;
1450             for (PathExpression compiledExpression : compiledExpressions) {
1451                 strings[index++] = compiledExpression.getExpression();
1452             }
1453             setProperty(ModeShapeLexicon.PATH_EXPRESSION, strings);
1454             return this;
1455         }
1456 
1457         public PathExpressionOutput<ReturnType> sequencingFrom( final String fromPathExpression ) {
1458             CheckArg.isNotEmpty(fromPathExpression, "fromPathExpression");
1459             return new PathExpressionOutput<ReturnType>() {
1460                 public SequencerDefinition<ReturnType> andOutputtingTo( String into ) {
1461                     CheckArg.isNotEmpty(into, "into");
1462                     return sequencingFrom(PathExpression.compile(fromPathExpression + " => " + into));
1463                 }
1464             };
1465         }
1466     }
1467 
1468     /**
1469      * Representation of the current configuration content.
1470      */
1471     @Immutable
1472     public static class ConfigurationDefinition {
1473         private final String name;
1474         private final ClassLoaderFactory classLoaderFactory;
1475         private final RepositorySource source;
1476         private final Path path;
1477         private final String workspace;
1478         private final ExecutionContext context;
1479         private Graph graph;
1480 
1481         protected ConfigurationDefinition( String configurationName,
1482                                            RepositorySource source,
1483                                            String workspace,
1484                                            Path path,
1485                                            ExecutionContext context,
1486                                            ClassLoaderFactory classLoaderFactory ) {
1487             assert configurationName != null;
1488             this.name = configurationName;
1489             this.source = source;
1490             this.path = path != null ? path : RootPath.INSTANCE;
1491             this.workspace = workspace;
1492             this.context = context;
1493             this.classLoaderFactory = classLoaderFactory != null ? classLoaderFactory : new StandardClassLoaderFactory();
1494         }
1495 
1496         /**
1497          * Get the name of this configuration.
1498          * 
1499          * @return the configuration's name; never null
1500          */
1501         public String getName() {
1502             return name;
1503         }
1504 
1505         /**
1506          * Get the repository source where the configuration content may be found
1507          * 
1508          * @return the source for the configuration repository; never null
1509          */
1510         public RepositorySource getRepositorySource() {
1511             return source;
1512         }
1513 
1514         /**
1515          * Get the path in the configuration repository where the configuration content may be found
1516          * 
1517          * @return the path to the configuration content; never null
1518          */
1519         public Path getPath() {
1520             return path;
1521         }
1522 
1523         /**
1524          * Get the name of the workspace used for the configuration repository.
1525          * 
1526          * @return the name of the workspace, or null if the default workspace should be used
1527          */
1528         public String getWorkspace() {
1529             return workspace;
1530         }
1531 
1532         /**
1533          * @return context
1534          */
1535         public ExecutionContext getContext() {
1536             return context;
1537         }
1538 
1539         /**
1540          * @return classLoaderFactory
1541          */
1542         public ClassLoaderFactory getClassLoaderFactory() {
1543             return classLoaderFactory;
1544         }
1545 
1546         /**
1547          * Return a copy of this configuration that uses the supplied name instead of this object's {@link #getPath() path}.
1548          * 
1549          * @param name the desired name for the new configuration; if null, then the name of this configuration is used
1550          * @return the new configuration
1551          */
1552         public ConfigurationDefinition with( String name ) {
1553             if (name == null) name = this.name;
1554             return new ConfigurationDefinition(name, source, workspace, path, context, classLoaderFactory);
1555         }
1556 
1557         /**
1558          * Return a copy of this configuration that uses the supplied path instead of this object's {@link #getPath() path}.
1559          * 
1560          * @param path the desired path for the new configuration; if null, then "/" is used
1561          * @return the new configuration
1562          */
1563         public ConfigurationDefinition with( Path path ) {
1564             return new ConfigurationDefinition(name, source, workspace, path, context, classLoaderFactory);
1565         }
1566 
1567         /**
1568          * Return a copy of this configuration that uses the supplied workspace name instead of this object's
1569          * {@link #getWorkspace() workspace}.
1570          * 
1571          * @param workspace the desired workspace name for the new configuration; if null, then the default workspace will be used
1572          * @return the new configuration
1573          */
1574         public ConfigurationDefinition withWorkspace( String workspace ) {
1575             return new ConfigurationDefinition(name, source, workspace, path, context, classLoaderFactory);
1576         }
1577 
1578         /**
1579          * Return a copy of this configuration that uses the supplied class loader factory instead of this object's
1580          * {@link #getClassLoaderFactory() class loader factory}.
1581          * 
1582          * @param classLoaderFactory the classloader factory, or null if the default factory should be used
1583          * @return the new configuration
1584          */
1585         public ConfigurationDefinition with( ClassLoaderFactory classLoaderFactory ) {
1586             return new ConfigurationDefinition(name, source, workspace, path, context, classLoaderFactory);
1587         }
1588 
1589         /**
1590          * Obtain a graph to this configuration repository. This method will always return the same graph instance.
1591          * 
1592          * @return the graph; never null
1593          */
1594         public Graph graph() {
1595             if (graph == null) {
1596                 graph = Graph.create(source, context);
1597                 if (workspace != null) graph.useWorkspace(workspace);
1598             }
1599             return graph;
1600         }
1601     }
1602 }