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