001    /*
002     * JBoss DNA (http://www.jboss.org/dna)
003     * See the COPYRIGHT.txt file distributed with this work for information
004     * regarding copyright ownership.  Some portions may be licensed
005     * to Red Hat, Inc. under one or more contributor license agreements.
006     * See the AUTHORS.txt file in the distribution for a full listing of 
007     * individual contributors.
008     *
009     * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
010     * is licensed to you under the terms of the GNU Lesser General Public License as
011     * published by the Free Software Foundation; either version 2.1 of
012     * the License, or (at your option) any later version.
013     * 
014     * JBoss DNA is distributed in the hope that it will be useful,
015     * but WITHOUT ANY WARRANTY; without even the implied warranty of
016     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
017     * Lesser General Public License for more details.
018     *
019     * You should have received a copy of the GNU Lesser General Public
020     * License along with this software; if not, write to the Free
021     * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
022     * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
023     */
024    package org.jboss.dna.repository;
025    
026    import java.io.File;
027    import java.io.FileInputStream;
028    import java.io.IOException;
029    import java.io.InputStream;
030    import java.net.URL;
031    import java.util.ArrayList;
032    import java.util.Collections;
033    import java.util.HashMap;
034    import java.util.HashSet;
035    import java.util.List;
036    import java.util.Map;
037    import java.util.Set;
038    import net.jcip.annotations.Immutable;
039    import net.jcip.annotations.NotThreadSafe;
040    import org.jboss.dna.common.collection.Problems;
041    import org.jboss.dna.common.collection.SimpleProblems;
042    import org.jboss.dna.common.component.ClassLoaderFactory;
043    import org.jboss.dna.common.component.StandardClassLoaderFactory;
044    import org.jboss.dna.common.util.CheckArg;
045    import org.jboss.dna.graph.ExecutionContext;
046    import org.jboss.dna.graph.Graph;
047    import org.jboss.dna.graph.Location;
048    import org.jboss.dna.graph.Node;
049    import org.jboss.dna.graph.Workspace;
050    import org.jboss.dna.graph.connector.RepositorySource;
051    import org.jboss.dna.graph.connector.inmemory.InMemoryRepositorySource;
052    import org.jboss.dna.graph.mimetype.MimeTypeDetector;
053    import org.jboss.dna.graph.property.Name;
054    import org.jboss.dna.graph.property.Path;
055    import org.jboss.dna.graph.property.PathExpression;
056    import org.jboss.dna.graph.property.PathNotFoundException;
057    import org.jboss.dna.graph.property.Property;
058    import org.jboss.dna.graph.property.basic.RootPath;
059    import org.jboss.dna.graph.request.InvalidWorkspaceException;
060    import org.jboss.dna.graph.sequencer.StreamSequencer;
061    import org.xml.sax.SAXException;
062    
063    /**
064     * A configuration builder for a {@link DnaEngine}. This class is an internal domain-specific language (DSL), and is designed to
065     * be used in a traditional way or in a method-chained manner:
066     * 
067     * <pre>
068     * configuration.repositorySource(&quot;Source1&quot;).setClass(InMemoryRepositorySource.class).setDescription(&quot;description&quot;);
069     * configuration.mimeTypeDetector(&quot;detector&quot;).setClass(ExtensionBasedMimeTypeDetector.class).setDescription(&quot;default detector&quot;);
070     * configuration.sequencer(&quot;MicrosoftDocs&quot;)
071     *              .setClass(&quot;org.jboss.dna.sequencer.msoffice.MSOfficeMetadataSequencer&quot;)
072     *              .setDescription(&quot;Our primary sequencer for all .doc files&quot;)
073     *              .sequencingFrom(&quot;/public//(*.(doc|xml|ppt)[*]/jcr:content[@jcr:data]&quot;)
074     *              .andOutputtingTo(&quot;/documents/$1&quot;);
075     * configuration.save();
076     * </pre>
077     */
078    @NotThreadSafe
079    public class DnaConfiguration {
080    
081        public static final String DEFAULT_WORKSPACE_NAME = "";
082        public static final String DEFAULT_PATH = "/";
083        public static final String DEFAULT_CONFIGURATION_SOURCE_NAME = "DNA Configuration Repository";
084    
085        private final ExecutionContext context;
086        private final Problems problems = new SimpleProblems();
087        private ConfigurationDefinition configurationContent;
088        private Graph.Batch changes;
089    
090        private final Map<String, SequencerDefinition<? extends DnaConfiguration>> sequencerDefinitions = new HashMap<String, SequencerDefinition<? extends DnaConfiguration>>();
091        private final Map<String, RepositorySourceDefinition<? extends DnaConfiguration>> repositorySourceDefinitions = new HashMap<String, RepositorySourceDefinition<? extends DnaConfiguration>>();
092        private final Map<String, MimeTypeDetectorDefinition<? extends DnaConfiguration>> mimeTypeDetectorDefinitions = new HashMap<String, MimeTypeDetectorDefinition<? extends DnaConfiguration>>();
093    
094        /**
095         * Create a new configuration, using a default-constructed {@link ExecutionContext}.
096         */
097        public DnaConfiguration() {
098            this(new ExecutionContext());
099        }
100    
101        /**
102         * Create a new configuration using the supplied {@link ExecutionContext}.
103         * 
104         * @param context the execution context
105         * @throws IllegalArgumentException if the path is null or empty
106         */
107        public DnaConfiguration( ExecutionContext context ) {
108            CheckArg.isNotNull(context, "context");
109            this.context = context;
110    
111            // Create the in-memory repository source in which the content will be stored ...
112            InMemoryRepositorySource source = new InMemoryRepositorySource();
113            source.setName(DEFAULT_CONFIGURATION_SOURCE_NAME);
114            source.setDefaultWorkspaceName(DEFAULT_WORKSPACE_NAME);
115    
116            // The file was imported successfully, so now create the content information ...
117            configurationContent = new ConfigurationDefinition(source, null, null, context, null);
118        }
119    
120        /**
121         * Load the configuration from a file at the given path.
122         * 
123         * @param pathToConfigurationFile the path the file containing the configuration information
124         * @return this configuration object, for convenience and method chaining
125         * @throws IOException if there is an error or problem reading the file at the supplied location
126         * @throws SAXException if the file is not a valid XML format
127         * @throws IllegalArgumentException if the path is null or empty
128         */
129        public DnaConfiguration loadFrom( String pathToConfigurationFile ) throws IOException, SAXException {
130            CheckArg.isNotEmpty(pathToConfigurationFile, "pathToConfigurationFile");
131            return loadFrom(pathToConfigurationFile, DEFAULT_PATH);
132        }
133    
134        /**
135         * Load the configuration from a file at the given path.
136         * 
137         * @param pathToConfigurationFile the path the file containing the configuration information
138         * @param path path within the content to the parent containing the configuration information, or null if the
139         *        {@link #DEFAULT_PATH default path} should be used
140         * @return this configuration object, for convenience and method chaining
141         * @throws IOException if there is an error or problem reading the file at the supplied location
142         * @throws SAXException if the file is not a valid XML format
143         * @throws IllegalArgumentException if the path is null or empty
144         */
145        public DnaConfiguration loadFrom( String pathToConfigurationFile,
146                                          String path ) throws IOException, SAXException {
147            CheckArg.isNotEmpty(pathToConfigurationFile, "pathToConfigurationFile");
148            return loadFrom(new File(pathToConfigurationFile), path);
149        }
150    
151        /**
152         * Load the configuration from a file.
153         * 
154         * @param configurationFile the file containing the configuration information
155         * @return this configuration object, for convenience and method chaining
156         * @throws IOException if there is an error or problem reading the supplied file
157         * @throws SAXException if the file is not a valid XML format
158         * @throws IllegalArgumentException if the file reference is null
159         */
160        public DnaConfiguration loadFrom( File configurationFile ) throws IOException, SAXException {
161            CheckArg.isNotNull(configurationFile, "configurationFile");
162            return loadFrom(configurationFile, DEFAULT_PATH);
163        }
164    
165        /**
166         * Load the configuration from a file.
167         * 
168         * @param configurationFile the file containing the configuration information
169         * @param path path within the content to the parent containing the configuration information, or null if the
170         *        {@link #DEFAULT_PATH default path} should be used
171         * @return this configuration object, for convenience and method chaining
172         * @throws IOException if there is an error or problem reading the supplied file
173         * @throws SAXException if the file is not a valid XML format
174         * @throws IllegalArgumentException if the file reference is null
175         */
176        public DnaConfiguration loadFrom( File configurationFile,
177                                          String path ) throws IOException, SAXException {
178            CheckArg.isNotNull(configurationFile, "configurationFile");
179            InputStream stream = new FileInputStream(configurationFile);
180            try {
181                return loadFrom(stream, path);
182            } finally {
183                stream.close();
184            }
185        }
186    
187        /**
188         * Load the configuration from a file at the supplied URL.
189         * 
190         * @param urlToConfigurationFile the URL of the file containing the configuration information
191         * @return this configuration object, for convenience and method chaining
192         * @throws IOException if there is an error or problem reading the file at the supplied URL
193         * @throws SAXException if the file is not a valid XML format
194         * @throws IllegalArgumentException if the URL is null
195         */
196        public DnaConfiguration loadFrom( URL urlToConfigurationFile ) throws IOException, SAXException {
197            CheckArg.isNotNull(urlToConfigurationFile, "urlToConfigurationFile");
198            return loadFrom(urlToConfigurationFile, DEFAULT_PATH);
199        }
200    
201        /**
202         * Load the configuration from a file at the supplied URL.
203         * 
204         * @param urlToConfigurationFile the URL of the file containing the configuration information
205         * @param path path within the content to the parent containing the configuration information, or null if the
206         *        {@link #DEFAULT_PATH default path} should be used
207         * @return this configuration object, for convenience and method chaining
208         * @throws IOException if there is an error or problem reading the file at the supplied URL
209         * @throws SAXException if the file is not a valid XML format
210         * @throws IllegalArgumentException if the URL is null
211         */
212        public DnaConfiguration loadFrom( URL urlToConfigurationFile,
213                                          String path ) throws IOException, SAXException {
214            CheckArg.isNotNull(urlToConfigurationFile, "urlToConfigurationFile");
215            InputStream stream = urlToConfigurationFile.openStream();
216            try {
217                return loadFrom(stream, path);
218            } finally {
219                stream.close();
220            }
221        }
222    
223        /**
224         * Load the configuration from a file at the supplied URL.
225         * 
226         * @param configurationFileInputStream the stream with the configuration information
227         * @return this configuration object, for convenience and method chaining
228         * @throws IOException if there is an error or problem reading the file at the supplied URL
229         * @throws SAXException if the file is not a valid XML format
230         * @throws IllegalArgumentException if the stream is null
231         */
232        public DnaConfiguration loadFrom( InputStream configurationFileInputStream ) throws IOException, SAXException {
233            CheckArg.isNotNull(configurationFileInputStream, "configurationFileInputStream");
234            return loadFrom(configurationFileInputStream, DEFAULT_PATH);
235        }
236    
237        /**
238         * Load the configuration from a file at the supplied URL.
239         * 
240         * @param configurationFileInputStream the stream with the configuration information
241         * @param path path within the content to the parent containing the configuration information, or null if the
242         *        {@link #DEFAULT_PATH default path} should be used
243         * @return this configuration object, for convenience and method chaining
244         * @throws IOException if there is an error or problem reading the file at the supplied URL
245         * @throws SAXException if the file is not a valid XML format
246         * @throws IllegalArgumentException if the stream is null
247         */
248        public DnaConfiguration loadFrom( InputStream configurationFileInputStream,
249                                          String path ) throws IOException, SAXException {
250            CheckArg.isNotNull(configurationFileInputStream, "configurationFileInputStream");
251    
252            // Create the in-memory repository source in which the content will be stored ...
253            InMemoryRepositorySource source = new InMemoryRepositorySource();
254            source.setName(DEFAULT_CONFIGURATION_SOURCE_NAME);
255            source.setDefaultWorkspaceName(DEFAULT_WORKSPACE_NAME);
256    
257            // Import the information into the source ...
258            Path pathToParent = path(path != null ? path : DEFAULT_PATH);
259            Graph graph = Graph.create(source, context);
260            graph.importXmlFrom(configurationFileInputStream).skippingRootElement(true).into(pathToParent);
261    
262            // The file was imported successfully, so now create the content information ...
263            configurationContent = new ConfigurationDefinition(source, null, pathToParent, context, null);
264            return this;
265        }
266    
267        /**
268         * Load the configuration from the repository content using the supplied repository source. This method assumes that the
269         * supplied source has already been configured and is ready to {@link RepositorySource#getConnection() create connections}.
270         * Also, the default workspace of the source will be used, and the configuration content may be found directly under the root
271         * node.
272         * 
273         * @param source the source that defines the repository with the configuration content
274         * @return this configuration object, for convenience and method chaining
275         * @throws IllegalArgumentException if the source is null
276         */
277        public DnaConfiguration loadFrom( RepositorySource source ) {
278            return loadFrom(source, null, null);
279        }
280    
281        /**
282         * Load the configuration from the repository content using the workspace in the supplied repository source. This method
283         * assumes that the supplied source has already been configured and is ready to {@link RepositorySource#getConnection() create
284         * connections}. Also, the configuration content may be found directly under the root node.
285         * 
286         * @param source the source that defines the repository with the configuration content
287         * @param workspaceName the name of the workspace with the configuration content, or null if the source's default workspace
288         *        should be used
289         * @return this configuration object, for convenience and method chaining
290         * @throws IllegalArgumentException if the source is null
291         */
292        public DnaConfiguration loadFrom( RepositorySource source,
293                                          String workspaceName ) {
294            CheckArg.isNotNull(source, "source");
295            return loadFrom(source, workspaceName, null);
296        }
297    
298        /**
299         * Load the configuration from the repository content at the supplied path in the workspace in the supplied repository source.
300         * This method assumes that the supplied source has already been configured and is ready to
301         * {@link RepositorySource#getConnection() create connections}.
302         * 
303         * @param source the source that defines the repository with the configuration content
304         * @param workspaceName the name of the workspace with the configuration content, or null if the source's default workspace
305         *        should be used
306         * @param pathInWorkspace the path to the parent node under which the configuration content may be found, or null if the
307         *        content may be found under the root node
308         * @return this configuration object, for convenience and method chaining
309         * @throws IllegalArgumentException if the source is null
310         */
311        public DnaConfiguration loadFrom( RepositorySource source,
312                                          String workspaceName,
313                                          String pathInWorkspace ) {
314            CheckArg.isNotNull(source, "source");
315    
316            // Verify connectivity ...
317            Graph graph = Graph.create(source, context);
318            if (workspaceName != null) {
319                Workspace workspace = null;
320                try {
321                    workspace = graph.useWorkspace(workspaceName); // should throw exception if not connectable
322                } catch (InvalidWorkspaceException e) {
323                    // Try creating the workspace ...
324                    workspace = graph.createWorkspace().named(workspaceName);
325                }
326                assert workspace.getRoot() != null;
327            }
328    
329            // Verify the path ...
330            Path path = pathInWorkspace != null ? path(pathInWorkspace) : path(DEFAULT_PATH);
331            Node parent = graph.getNodeAt(path);
332            assert parent != null;
333    
334            // Now create the content information ...
335            configurationContent = new ConfigurationDefinition(source, workspaceName, path, context, null);
336            return this;
337        }
338    
339        /**
340         * Get the immutable representation of the information defining where the configuration content can be found.
341         * 
342         * @return the configuration definition
343         */
344        public ConfigurationDefinition getConfigurationDefinition() {
345            return configurationContent;
346        }
347    
348        protected ExecutionContext getExecutionContext() {
349            return configurationContent.getContext();
350        }
351    
352        protected Path path() {
353            return configurationContent.getPath();
354        }
355    
356        protected Path path( String path ) {
357            return context.getValueFactories().getPathFactory().create(path);
358        }
359    
360        protected Name name( String name ) {
361            return context.getValueFactories().getNameFactory().create(name);
362        }
363    
364        /**
365         * Get the problems (if any) that are associated with this configuration.
366         * 
367         * @return the problems
368         */
369        public Problems getProblems() {
370            return problems;
371        }
372    
373        protected Graph.Batch changes() {
374            if (changes == null) {
375                ConfigurationDefinition content = getConfigurationDefinition();
376                Graph graph = Graph.create(content.getRepositorySource(), content.getContext());
377                if (content.getWorkspace() != null) {
378                    graph.useWorkspace(content.getWorkspace());
379                }
380                changes = graph.batch();
381            }
382            return changes;
383        }
384    
385        /**
386         * Determine if there are any unsaved changes to this configuration that must be {@link #save() saved} before they take
387         * effect.
388         * 
389         * @return true if a {@link #save()} is required, or false no changes have been made to the configuration since the last
390         *         {@link #save()}
391         */
392        public boolean hasChanges() {
393            Graph.Batch changes = this.changes;
394            return changes != null && changes.isExecuteRequired();
395        }
396    
397        /**
398         * Persist any unsaved changes that have been made to this configuration. This method has no effect if there are currently
399         * {@link #hasChanges() no unsaved changes}.
400         * 
401         * @return this configuration, for method chaining purposes
402         */
403        public DnaConfiguration save() {
404            Graph.Batch changes = this.changes;
405            if (changes != null && changes.isExecuteRequired()) {
406                changes.execute();
407            }
408            this.changes = null;
409            sequencerDefinitions.clear();
410            mimeTypeDetectorDefinitions.clear();
411            repositorySourceDefinitions.clear();
412            return this;
413        }
414    
415        /**
416         * Specify the {@link ClassLoaderFactory} that should be used to load the classes for the various components. Most of the
417         * definitions can specify the {@link LoadedFrom#loadedFrom(String...) classpath} that should be used, and that classpath is
418         * passed to the supplied ClassLoaderFactory instance to obtain a {@link ClassLoader} for the class.
419         * <p>
420         * If not called, this configuration will use the class loader that loaded this configuration's class.
421         * </p>
422         * 
423         * @param classLoaderFactory the class loader factory implementation, or null if the classes should be loaded using the class
424         *        loader of this object
425         * @return this configuration, for method chaining purposes
426         */
427        public DnaConfiguration withClassLoaderFactory( ClassLoaderFactory classLoaderFactory ) {
428            this.configurationContent = this.configurationContent.with(classLoaderFactory);
429            return this;
430        }
431    
432        protected Set<String> getNamesOfComponentsUnder( Name parentName ) {
433            Set<String> names = new HashSet<String>();
434            try {
435                ConfigurationDefinition content = this.getConfigurationDefinition();
436                Path path = context.getValueFactories().getPathFactory().create(content.getPath(), parentName);
437                for (Location child : content.graph().getChildren().of(path)) {
438                    names.add(child.getPath().getLastSegment().getString(context.getNamespaceRegistry()));
439                }
440            } catch (PathNotFoundException e) {
441                // Nothing has been saved yet ...
442            }
443            return names;
444        }
445    
446        /**
447         * Get the list of MIME type detector definitions.
448         * 
449         * @return the unmodifiable set of definitions; never null but possibly empty if there are no definitions
450         */
451        public Set<MimeTypeDetectorDefinition<? extends DnaConfiguration>> mimeTypeDetectors() {
452            // Get the children under the 'dna:mimeTypeDetectors' node ...
453            Set<String> names = getNamesOfComponentsUnder(DnaLexicon.MIME_TYPE_DETECTORS);
454            names.addAll(this.mimeTypeDetectorDefinitions.keySet());
455            Set<MimeTypeDetectorDefinition<? extends DnaConfiguration>> results = new HashSet<MimeTypeDetectorDefinition<? extends DnaConfiguration>>();
456            for (String name : names) {
457                results.add(mimeTypeDetector(name));
458            }
459            return Collections.unmodifiableSet(results);
460        }
461    
462        /**
463         * Get the list of repository source definitions.
464         * 
465         * @return the unmodifiable set of definitions; never null but possibly empty if there are no definitions
466         */
467        public Set<RepositorySourceDefinition<? extends DnaConfiguration>> repositorySources() {
468            // Get the children under the 'dna:mimeTypeDetectors' node ...
469            Set<String> names = getNamesOfComponentsUnder(DnaLexicon.SOURCES);
470            names.addAll(this.repositorySourceDefinitions.keySet());
471            Set<RepositorySourceDefinition<? extends DnaConfiguration>> results = new HashSet<RepositorySourceDefinition<? extends DnaConfiguration>>();
472            for (String name : names) {
473                results.add(repositorySource(name));
474            }
475            return Collections.unmodifiableSet(results);
476        }
477    
478        /**
479         * Get the list of sequencer definitions.
480         * 
481         * @return the unmodifiable set of definitions; never null but possibly empty if there are no definitions
482         */
483        public Set<SequencerDefinition<? extends DnaConfiguration>> sequencers() {
484            // Get the children under the 'dna:mimeTypeDetectors' node ...
485            Set<String> names = getNamesOfComponentsUnder(DnaLexicon.SEQUENCERS);
486            names.addAll(this.sequencerDefinitions.keySet());
487            Set<SequencerDefinition<? extends DnaConfiguration>> results = new HashSet<SequencerDefinition<? extends DnaConfiguration>>();
488            for (String name : names) {
489                results.add(sequencer(name));
490            }
491            return Collections.unmodifiableSet(results);
492        }
493    
494        /**
495         * Obtain or create a definition for the {@link MimeTypeDetector MIME type detector} with the supplied name or identifier. A
496         * new definition will be created if there currently is no MIME type detector defined with the supplied name.
497         * 
498         * @param name the name or identifier of the detector
499         * @return the details of the MIME type detector definition; never null
500         */
501        public MimeTypeDetectorDefinition<? extends DnaConfiguration> mimeTypeDetector( String name ) {
502            return mimeTypeDetectorDefinition(this, name);
503        }
504    
505        /**
506         * Obtain or create a definition for the {@link RepositorySource} with the supplied name or identifier. A new definition will
507         * be created if there currently is no repository source defined with the supplied name.
508         * 
509         * @param name the name or identifier of the repository source
510         * @return the details of the repository source definition; never null
511         */
512        public RepositorySourceDefinition<? extends DnaConfiguration> repositorySource( String name ) {
513            return repositorySourceDefinition(this, name);
514        }
515    
516        /**
517         * Obtain or create a definition for the {@link StreamSequencer sequencer} with the supplied name or identifier. A new
518         * definition will be created if there currently is no sequencer defined with the supplied name.
519         * 
520         * @param name the name or identifier of the sequencer
521         * @return the details of the sequencer definition; never null
522         */
523        public SequencerDefinition<? extends DnaConfiguration> sequencer( String name ) {
524            return sequencerDefinition(this, name);
525        }
526    
527        /**
528         * Convenience method to make the code that sets up this configuration easier to read. This method simply returns this object.
529         * 
530         * @return this configuration component; never null
531         */
532        public DnaConfiguration and() {
533            return this;
534        }
535    
536        /**
537         * Construct an engine that reflects the current state of this configuration. This method always creates a new instance.
538         * 
539         * @return the resulting engine; never null
540         */
541        public DnaEngine build() {
542            save();
543            return new DnaEngine(getExecutionContext(), getConfigurationDefinition());
544        }
545    
546        /**
547         * Interface that defines the ability to obtain the configuration component.
548         * 
549         * @param <ReturnType> the interface returned from these methods
550         */
551        public interface Returnable<ReturnType> {
552            /**
553             * Return the configuration component.
554             * 
555             * @return the configuration component; never null
556             */
557            ReturnType and();
558        }
559    
560        /**
561         * Interface that defines the ability to remove the configuration component.
562         * 
563         * @param <ReturnType> the configuration interface returned from these methods
564         */
565        public interface Removable<ReturnType> {
566            /**
567             * Remove this configuration component.
568             * 
569             * @return the configuration; never null
570             */
571            ReturnType remove();
572        }
573    
574        /**
575         * The interface used to set a description on a component.
576         * 
577         * @param <ReturnType> the interface returned from these methods
578         */
579        public interface SetDescription<ReturnType> {
580            /**
581             * Specify the description of this component.
582             * 
583             * @param description the description; may be null or empty
584             * @return the next component to continue configuration; never null
585             */
586            ReturnType setDescription( String description );
587    
588            /**
589             * Get the description of this component.
590             * 
591             * @return the description, or null if there is no description
592             */
593            String getDescription();
594        }
595    
596        /**
597         * Interface for configuring the JavaBean-style properties of an object.
598         * 
599         * @param <ReturnType> the interface returned after the property has been set.
600         * @author Randall Hauch
601         */
602        public interface SetProperties<ReturnType> {
603            /**
604             * Set the property value to an integer.
605             * 
606             * @param beanPropertyName the name of the JavaBean-style property (e.g., "retryLimit")
607             * @param value the new value for the property
608             * @return the next component to continue configuration; never null
609             */
610            ReturnType setProperty( String beanPropertyName,
611                                    int value );
612    
613            /**
614             * Set the property value to a long number.
615             * 
616             * @param beanPropertyName the name of the JavaBean-style property (e.g., "retryLimit")
617             * @param value the new value for the property
618             * @return the next component to continue configuration; never null
619             */
620            ReturnType setProperty( String beanPropertyName,
621                                    long value );
622    
623            /**
624             * Set the property value to a short.
625             * 
626             * @param beanPropertyName the name of the JavaBean-style property (e.g., "retryLimit")
627             * @param value the new value for the property
628             * @return the next component to continue configuration; never null
629             */
630            ReturnType setProperty( String beanPropertyName,
631                                    short value );
632    
633            /**
634             * Set the property value to a boolean.
635             * 
636             * @param beanPropertyName the name of the JavaBean-style property (e.g., "retryLimit")
637             * @param value the new value for the property
638             * @return the next component to continue configuration; never null
639             */
640            ReturnType setProperty( String beanPropertyName,
641                                    boolean value );
642    
643            /**
644             * Set the property value to a float.
645             * 
646             * @param beanPropertyName the name of the JavaBean-style property (e.g., "retryLimit")
647             * @param value the new value for the property
648             * @return the next component to continue configuration; never null
649             */
650            ReturnType setProperty( String beanPropertyName,
651                                    float value );
652    
653            /**
654             * Set the property value to a double.
655             * 
656             * @param beanPropertyName the name of the JavaBean-style property (e.g., "retryLimit")
657             * @param value the new value for the property
658             * @return the next component to continue configuration; never null
659             */
660            ReturnType setProperty( String beanPropertyName,
661                                    double value );
662    
663            /**
664             * Set the property value to a string.
665             * 
666             * @param beanPropertyName the name of the JavaBean-style property (e.g., "retryLimit")
667             * @param value the new value for the property
668             * @return the next component to continue configuration; never null
669             */
670            ReturnType setProperty( String beanPropertyName,
671                                    String value );
672    
673            /**
674             * Set the property value to an object.
675             * 
676             * @param beanPropertyName the name of the JavaBean-style property (e.g., "retryLimit")
677             * @param value the new value for the property
678             * @return the next component to continue configuration; never null
679             */
680            ReturnType setProperty( String beanPropertyName,
681                                    Object value );
682    
683            /**
684             * Set the property values to an object.
685             * 
686             * @param beanPropertyName the name of the JavaBean-style property (e.g., "retryLimit")
687             * @param values the array of new values for the property
688             * @return the next component to continue configuration; never null
689             */
690            ReturnType setProperty( String beanPropertyName,
691                                    Object[] values );
692    
693            /**
694             * Get the property.
695             * 
696             * @param beanPropertyName the name of the JavaBean-style property (e.g., "retryLimit")
697             * @return the property object, or null if there is no such property
698             */
699            Property getProperty( String beanPropertyName );
700        }
701    
702        /**
703         * The interface used to configure the class used for a component.
704         * 
705         * @param <ComponentClassType> the class or interface that the component is to implement
706         * @param <ReturnType> the interface returned from these methods
707         */
708        public interface ChooseClass<ComponentClassType, ReturnType> {
709    
710            /**
711             * Specify the name of the class that should be instantiated for the instance. The classpath information will need to be
712             * defined using the returned interface.
713             * 
714             * @param classname the name of the class that should be instantiated
715             * @return the interface used to define the classpath information; never null
716             * @throws IllegalArgumentException if the class name is null, empty, blank, or not a valid class name
717             */
718            LoadedFrom<ReturnType> usingClass( String classname );
719    
720            /**
721             * Specify the class that should be instantiated for the instance. Because the class is already available to this class
722             * loader, there is no need to specify the classloader information.
723             * 
724             * @param clazz the class that should be instantiated
725             * @return the next component to continue configuration; never null
726             * @throws DnaConfigurationException if the class could not be accessed and instantiated (if needed)
727             * @throws IllegalArgumentException if the class reference is null
728             */
729            ReturnType usingClass( Class<? extends ComponentClassType> clazz );
730        }
731    
732        /**
733         * Interface for specifying from where the component's class is to be loaded.
734         * 
735         * @param <ReturnType> the interface returned from these methods
736         */
737        public interface LoadedFrom<ReturnType> {
738            /**
739             * Specify the names of the classloaders that form the classpath for the component, from which the component's class (and
740             * its dependencies) can be loaded. The names correspond to the names supplied to the
741             * {@link ExecutionContext#getClassLoader(String...)} methods.
742             * 
743             * @param classPathNames the names for the classloaders, as passed to the {@link ClassLoaderFactory} implementation (e.g.,
744             *        the {@link ExecutionContext}).
745             * @return the next component to continue configuration; never null
746             * @see #loadedFromClasspath()
747             * @see ExecutionContext#getClassLoader(String...)
748             */
749            ReturnType loadedFrom( String... classPathNames );
750    
751            /**
752             * Specify that the component (and its dependencies) will be found on the current (or
753             * {@link Thread#getContextClassLoader() current context}) classloader.
754             * 
755             * @return the next component to continue configuration; never null
756             * @see #loadedFrom(String...)
757             * @see ExecutionContext#getClassLoader(String...)
758             */
759            ReturnType loadedFromClasspath();
760        }
761    
762        /**
763         * Interface for a component that has a name.
764         */
765        public interface HasName {
766            /**
767             * Get the name.
768             * 
769             * @return the name; never null
770             */
771            String getName();
772        }
773    
774        /**
775         * Interface used to set up and define a MIME type detector instance.
776         * 
777         * @param <ReturnType> the type of the configuration component that owns this definition object
778         */
779        public interface MimeTypeDetectorDefinition<ReturnType>
780            extends Returnable<ReturnType>, SetDescription<MimeTypeDetectorDefinition<ReturnType>>,
781            SetProperties<MimeTypeDetectorDefinition<ReturnType>>,
782            ChooseClass<MimeTypeDetector, MimeTypeDetectorDefinition<ReturnType>>, Removable<ReturnType> {
783        }
784    
785        /**
786         * Interface used to set up and define a RepositorySource instance.
787         * 
788         * @param <ReturnType> the type of the configuration component that owns this definition object
789         */
790        public interface RepositorySourceDefinition<ReturnType>
791            extends Returnable<ReturnType>, SetDescription<RepositorySourceDefinition<ReturnType>>,
792            SetProperties<RepositorySourceDefinition<ReturnType>>,
793            ChooseClass<RepositorySource, RepositorySourceDefinition<ReturnType>>, Removable<ReturnType>, HasName {
794    
795            /**
796             * Set the retry limit on the repository source. This is equivalent to calling {@link #setProperty(String, int)} with "
797             * {@link DnaLexicon#RETRY_LIMIT dna:retryLimit}" as the property name.
798             * 
799             * @param retryLimit the retry limit
800             * @return this definition, for method chaining purposes
801             * @see RepositorySource#setRetryLimit(int)
802             */
803            RepositorySourceDefinition<ReturnType> setRetryLimit( int retryLimit );
804        }
805    
806        /**
807         * Interface used to set up and define a {@link StreamSequencer sequencer} instance.
808         * 
809         * @param <ReturnType> the type of the configuration component that owns this definition object
810         */
811        public interface SequencerDefinition<ReturnType>
812            extends Returnable<ReturnType>, SetDescription<SequencerDefinition<ReturnType>>,
813            SetProperties<SequencerDefinition<ReturnType>>, ChooseClass<StreamSequencer, SequencerDefinition<ReturnType>>,
814            Removable<ReturnType> {
815    
816            /**
817             * Specify the input {@link PathExpression path expression} represented as a string, which determines when this sequencer
818             * will be executed.
819             * 
820             * @param inputPathExpression the path expression for nodes that, when they change, will be passed as an input to the
821             *        sequencer
822             * @return the interface used to specify the output path expression; never null
823             */
824            PathExpressionOutput<ReturnType> sequencingFrom( String inputPathExpression );
825    
826            /**
827             * Specify the input {@link PathExpression path expression}, which determines when this sequencer will be executed.
828             * 
829             * @param inputPathExpression the path expression for nodes that, when they change, will be passed as an input to the
830             *        sequencer
831             * @return the interface used to continue specifying the configuration of the sequencer
832             */
833            SequencerDefinition<ReturnType> sequencingFrom( PathExpression inputPathExpression );
834    
835            /**
836             * Get the path expressions from the saved configuration.
837             * 
838             * @return the set of path expressions; never null but possibly empty
839             */
840            Set<PathExpression> getPathExpressions();
841        }
842    
843        /**
844         * Interface used to specify the output path expression for a
845         * {@link DnaConfiguration.SequencerDefinition#sequencingFrom(PathExpression) sequencer configuration}.
846         * 
847         * @param <ReturnType>
848         */
849        public interface PathExpressionOutput<ReturnType> {
850            /**
851             * Specify the output {@link PathExpression path expression}, which determines where this sequencer's output will be
852             * placed.
853             * 
854             * @param outputExpression the path expression for the location(s) where output generated by the sequencer is to be placed
855             * @return the interface used to continue specifying the configuration of the sequencer
856             */
857            SequencerDefinition<ReturnType> andOutputtingTo( String outputExpression );
858        }
859    
860        /**
861         * Utility method to construct a definition object for the detector with the supplied name and return type.
862         * 
863         * @param <ReturnType> the type of the return object
864         * @param returnObject the return object
865         * @param name the name of the detector
866         * @return the definition for the detector
867         */
868        @SuppressWarnings( "unchecked" )
869        protected <ReturnType extends DnaConfiguration> MimeTypeDetectorDefinition<ReturnType> mimeTypeDetectorDefinition( ReturnType returnObject,
870                                                                                                                           String name ) {
871            MimeTypeDetectorDefinition<ReturnType> definition = (MimeTypeDetectorDefinition<ReturnType>)mimeTypeDetectorDefinitions.get(name);
872            if (definition == null) {
873                definition = new MimeTypeDetectorBuilder<ReturnType>(returnObject, changes(), path(), DnaLexicon.MIME_TYPE_DETECTORS,
874                                                                     name(name));
875                mimeTypeDetectorDefinitions.put(name, definition);
876            }
877            return definition;
878        }
879    
880        /**
881         * Utility method to construct a definition object for the repository source with the supplied name and return type.
882         * 
883         * @param <ReturnType> the type of the return object
884         * @param returnObject the return object
885         * @param name the name of the repository source
886         * @return the definition for the repository source
887         */
888        @SuppressWarnings( "unchecked" )
889        protected <ReturnType extends DnaConfiguration> RepositorySourceDefinition<ReturnType> repositorySourceDefinition( ReturnType returnObject,
890                                                                                                                           String name ) {
891            RepositorySourceDefinition<ReturnType> definition = (RepositorySourceDefinition<ReturnType>)repositorySourceDefinitions.get(name);
892            if (definition == null) {
893                definition = new SourceBuilder<ReturnType>(returnObject, changes(), path(), DnaLexicon.SOURCES, name(name));
894                repositorySourceDefinitions.put(name, definition);
895            }
896            return definition;
897        }
898    
899        /**
900         * Utility method to construct a definition object for the sequencer with the supplied name and return type.
901         * 
902         * @param <ReturnType> the type of the return object
903         * @param returnObject the return object
904         * @param name the name of the sequencer
905         * @return the definition for the sequencer
906         */
907        @SuppressWarnings( "unchecked" )
908        protected <ReturnType extends DnaConfiguration> SequencerDefinition<ReturnType> sequencerDefinition( ReturnType returnObject,
909                                                                                                             String name ) {
910            SequencerDefinition<ReturnType> definition = (SequencerDefinition<ReturnType>)sequencerDefinitions.get(name);
911            if (definition == null) {
912                definition = new SequencerBuilder<ReturnType>(returnObject, changes(), path(), DnaLexicon.SEQUENCERS, name(name));
913                sequencerDefinitions.put(name, definition);
914            }
915            return definition;
916        }
917    
918        protected static class BaseReturnable<ReturnType> implements Returnable<ReturnType> {
919            protected final ReturnType returnObject;
920    
921            protected BaseReturnable( ReturnType returnObject ) {
922                this.returnObject = returnObject;
923            }
924    
925            /**
926             * {@inheritDoc}
927             * 
928             * @see org.jboss.dna.repository.DnaConfiguration.Returnable#and()
929             */
930            public ReturnType and() {
931                return returnObject;
932            }
933        }
934    
935        /**
936         * Base class for {@link Returnable} types that work on a node in the graph.
937         * 
938         * @param <ReturnType> the type to be returned
939         * @param <ThisType> the type to be returned by the set properties, set description, etc. methods
940         */
941        protected static abstract class GraphReturnable<ReturnType, ThisType> extends BaseReturnable<ReturnType>
942            implements SetDescription<ThisType>, SetProperties<ThisType>, Removable<ReturnType> {
943            protected final ExecutionContext context;
944            protected final Graph.Batch batch;
945            protected final Path path;
946            private Map<Name, Property> properties = new HashMap<Name, Property>();
947    
948            protected GraphReturnable( ReturnType returnObject,
949                                       Graph.Batch batch,
950                                       Path path,
951                                       Name... names ) {
952                super(returnObject);
953                assert batch != null;
954                assert path != null;
955                assert names.length > 0;
956                this.context = batch.getGraph().getContext();
957                this.batch = batch;
958                // Make sure there are nodes down to the supplied path ...
959                createIfMissing(path, names).and();
960                this.path = context.getValueFactories().getPathFactory().create(path, names);
961                try {
962                    properties = batch.getGraph().getPropertiesByName().on(this.path);
963                } catch (PathNotFoundException e) {
964                    // The node doesn't exist yet (wasn't yet saved)
965                    properties = new HashMap<Name, Property>();
966                }
967            }
968    
969            /**
970             * Create the node at the supplied path under the current path, and return the Create operation for the last node created.
971             * The caller <i>must</i> call {@link Graph.Create#and()} to complete the operation.
972             * 
973             * @param child the name of the child
974             * @param segments the segments in the remainder of the path
975             * @return the newly-created but incomplete operation
976             */
977            protected Graph.Create<Graph.Batch> createIfMissing( Name child,
978                                                                 String... segments ) {
979                Path nodePath = context.getValueFactories().getPathFactory().create(path, child);
980                Graph.Create<Graph.Batch> result = batch.create(nodePath).orUpdate();
981                for (String name : segments) {
982                    result.and();
983                    nodePath = context.getValueFactories().getPathFactory().create(nodePath, name);
984                    result = batch.create(nodePath).orUpdate();
985                }
986                return result;
987            }
988    
989            /**
990             * Create the node at the supplied path under the current path, and return the Create operation for the last node created.
991             * The caller <i>must</i> call {@link Graph.Create#and()} to complete the operation.
992             * 
993             * @param segment the name segment for the child
994             * @return the newly-created but incomplete operation
995             */
996            protected Graph.Create<Graph.Batch> createIfMissing( Name segment ) {
997                Path nodePath = context.getValueFactories().getPathFactory().create(path, segment);
998                Graph.Create<Graph.Batch> result = batch.create(nodePath).orUpdate();
999                return result;
1000            }
1001    
1002            /**
1003             * Create the node at the supplied path under the current path, and return the Create operation for the last node created.
1004             * The caller <i>must</i> call {@link Graph.Create#and()} to complete the operation.
1005             * 
1006             * @param path the path to the node
1007             * @param segments the segments in the remainder of the path
1008             * @return the newly-created but incomplete operation
1009             */
1010            protected Graph.Create<Graph.Batch> createIfMissing( Path path,
1011                                                                 Name... segments ) {
1012                Path nodePath = path;
1013                Graph.Create<Graph.Batch> result = null;
1014                for (Name name : segments) {
1015                    if (result != null) result.and();
1016                    nodePath = context.getValueFactories().getPathFactory().create(nodePath, name);
1017                    result = batch.create(nodePath).orUpdate();
1018                }
1019                return result;
1020            }
1021    
1022            protected Path subpath( Name... segments ) {
1023                return context.getValueFactories().getPathFactory().create(path, segments);
1024            }
1025    
1026            protected abstract ThisType thisType();
1027    
1028            public String getName() {
1029                return path.getLastSegment().getName().getString(context.getNamespaceRegistry());
1030            }
1031    
1032            public ThisType setDescription( String description ) {
1033                return setProperty(DnaLexicon.DESCRIPTION, description);
1034            }
1035    
1036            public String getDescription() {
1037                Property property = getProperty(DnaLexicon.DESCRIPTION);
1038                if (property != null && !property.isEmpty()) {
1039                    return context.getValueFactories().getStringFactory().create(property.getFirstValue());
1040                }
1041                return null;
1042            }
1043    
1044            protected ThisType setProperty( Name propertyName,
1045                                            Object value ) {
1046                // Set the property via the batch ...
1047                batch.set(propertyName).on(path).to(value).and();
1048                // Record that we changed this property ...
1049                properties.put(propertyName, context.getPropertyFactory().create(propertyName, value));
1050                return thisType();
1051            }
1052    
1053            public ThisType setProperty( String propertyName,
1054                                         Object value ) {
1055                return setProperty(context.getValueFactories().getNameFactory().create(propertyName), value);
1056            }
1057    
1058            public ThisType setProperty( Name propertyName,
1059                                         Object[] values ) {
1060                // Set the property via the batch ...
1061                batch.set(propertyName).on(path).to(values).and();
1062                // Record that we changed this property ...
1063                properties.put(propertyName, context.getPropertyFactory().create(propertyName, values));
1064                return thisType();
1065            }
1066    
1067            public ThisType setProperty( String propertyName,
1068                                         Object[] values ) {
1069                return setProperty(context.getValueFactories().getNameFactory().create(propertyName), values);
1070            }
1071    
1072            public ThisType setProperty( String beanPropertyName,
1073                                         boolean value ) {
1074                return setProperty(beanPropertyName, (Object)value);
1075            }
1076    
1077            public ThisType setProperty( String beanPropertyName,
1078                                         int value ) {
1079                return setProperty(beanPropertyName, (Object)value);
1080            }
1081    
1082            public ThisType setProperty( String beanPropertyName,
1083                                         short value ) {
1084                return setProperty(beanPropertyName, (Object)value);
1085            }
1086    
1087            public ThisType setProperty( String beanPropertyName,
1088                                         long value ) {
1089                return setProperty(beanPropertyName, (Object)value);
1090            }
1091    
1092            public ThisType setProperty( String beanPropertyName,
1093                                         double value ) {
1094                return setProperty(beanPropertyName, (Object)value);
1095            }
1096    
1097            public ThisType setProperty( String beanPropertyName,
1098                                         float value ) {
1099                return setProperty(beanPropertyName, (Object)value);
1100            }
1101    
1102            public ThisType setProperty( String beanPropertyName,
1103                                         String value ) {
1104                return setProperty(beanPropertyName, (Object)value);
1105            }
1106    
1107            public Property getProperty( String beanPropertyName ) {
1108                return properties.get(context.getValueFactories().getNameFactory().create(beanPropertyName));
1109            }
1110    
1111            public Property getProperty( Name beanPropertyName ) {
1112                return properties.get(beanPropertyName);
1113            }
1114    
1115            public ReturnType remove() {
1116                batch.delete(path);
1117                properties.clear();
1118                return and();
1119            }
1120        }
1121    
1122        /**
1123         * Base class for {@link Returnable} types that work on a node in the graph.
1124         * 
1125         * @param <ReturnType> the type to be returned
1126         * @param <ThisType> the type to be returned by the set properties, set description, etc. methods
1127         * @param <ComponentType> the type of the component being configured
1128         */
1129        protected static abstract class GraphComponentBuilder<ReturnType, ThisType, ComponentType>
1130            extends GraphReturnable<ReturnType, ThisType> implements ChooseClass<ComponentType, ThisType> {
1131            protected GraphComponentBuilder( ReturnType returnObject,
1132                                             Graph.Batch batch,
1133                                             Path path,
1134                                             Name... names ) {
1135                super(returnObject, batch, path, names);
1136            }
1137    
1138            public LoadedFrom<ThisType> usingClass( final String classname ) {
1139                return new LoadedFrom<ThisType>() {
1140                    public ThisType loadedFromClasspath() {
1141                        return setProperty(DnaLexicon.CLASSNAME, classname);
1142                    }
1143    
1144                    public ThisType loadedFrom( String... classpath ) {
1145                        List<String> classpaths = new ArrayList<String>();
1146                        // Ignore any null, zero-length, or duplicate elements ...
1147                        for (String value : classpath) {
1148                            if (value == null) continue;
1149                            value = value.trim();
1150                            if (value.length() == 0) continue;
1151                            if (!classpaths.contains(value)) classpaths.add(value);
1152                        }
1153                        if (classpaths.size() != 0) {
1154                            classpath = classpaths.toArray(new String[classpaths.size()]);
1155                            setProperty(DnaLexicon.CLASSPATH, classpath);
1156                        }
1157                        return setProperty(DnaLexicon.CLASSNAME, classname);
1158                    }
1159                };
1160            }
1161    
1162            public ThisType usingClass( Class<? extends ComponentType> componentClass ) {
1163                return setProperty(DnaLexicon.CLASSNAME, componentClass.getCanonicalName());
1164            }
1165        }
1166    
1167        protected static class MimeTypeDetectorBuilder<ReturnType>
1168            extends GraphComponentBuilder<ReturnType, MimeTypeDetectorDefinition<ReturnType>, MimeTypeDetector>
1169            implements MimeTypeDetectorDefinition<ReturnType> {
1170            protected MimeTypeDetectorBuilder( ReturnType returnObject,
1171                                               Graph.Batch batch,
1172                                               Path path,
1173                                               Name... names ) {
1174                super(returnObject, batch, path, names);
1175            }
1176    
1177            @Override
1178            protected MimeTypeDetectorBuilder<ReturnType> thisType() {
1179                return this;
1180            }
1181    
1182        }
1183    
1184        protected static class SourceBuilder<ReturnType>
1185            extends GraphComponentBuilder<ReturnType, RepositorySourceDefinition<ReturnType>, RepositorySource>
1186            implements RepositorySourceDefinition<ReturnType> {
1187            protected SourceBuilder( ReturnType returnObject,
1188                                     Graph.Batch batch,
1189                                     Path path,
1190                                     Name... names ) {
1191                super(returnObject, batch, path, names);
1192            }
1193    
1194            @Override
1195            protected RepositorySourceDefinition<ReturnType> thisType() {
1196                return this;
1197            }
1198    
1199            public RepositorySourceDefinition<ReturnType> setRetryLimit( int retryLimit ) {
1200                return setProperty(DnaLexicon.RETRY_LIMIT, retryLimit);
1201            }
1202    
1203            @Override
1204            public RepositorySourceDefinition<ReturnType> setProperty( String propertyName,
1205                                                                       Object value ) {
1206                Name name = context.getValueFactories().getNameFactory().create(propertyName);
1207                // Check the "standard" names that should be prefixed with 'dna:'
1208                if (name.getLocalName().equals(DnaLexicon.RETRY_LIMIT.getLocalName())) name = DnaLexicon.RETRY_LIMIT;
1209                if (name.getLocalName().equals(DnaLexicon.DESCRIPTION.getLocalName())) name = DnaLexicon.DESCRIPTION;
1210                return super.setProperty(name, value);
1211            }
1212    
1213            @Override
1214            public Property getProperty( Name name ) {
1215                // Check the "standard" names that should be prefixed with 'dna:'
1216                if (name.getLocalName().equals(DnaLexicon.RETRY_LIMIT.getLocalName())) name = DnaLexicon.RETRY_LIMIT;
1217                if (name.getLocalName().equals(DnaLexicon.DESCRIPTION.getLocalName())) name = DnaLexicon.DESCRIPTION;
1218                return super.getProperty(name);
1219            }
1220        }
1221    
1222        protected static class SequencerBuilder<ReturnType>
1223            extends GraphComponentBuilder<ReturnType, SequencerDefinition<ReturnType>, StreamSequencer>
1224            implements SequencerDefinition<ReturnType> {
1225    
1226            protected SequencerBuilder( ReturnType returnObject,
1227                                        Graph.Batch batch,
1228                                        Path path,
1229                                        Name... names ) {
1230                super(returnObject, batch, path, names);
1231            }
1232    
1233            @Override
1234            protected SequencerDefinition<ReturnType> thisType() {
1235                return this;
1236            }
1237    
1238            public Set<PathExpression> getPathExpressions() {
1239                Set<PathExpression> expressions = new HashSet<PathExpression>();
1240                try {
1241                    Property existingExpressions = getProperty(DnaLexicon.PATH_EXPRESSION);
1242                    if (existingExpressions != null) {
1243                        for (Object existing : existingExpressions.getValuesAsArray()) {
1244                            String existingExpression = context.getValueFactories().getStringFactory().create(existing);
1245                            expressions.add(PathExpression.compile(existingExpression));
1246                        }
1247                    }
1248                } catch (PathNotFoundException e) {
1249                    // Nothing saved yet ...
1250                }
1251                return expressions;
1252            }
1253    
1254            public SequencerDefinition<ReturnType> sequencingFrom( PathExpression expression ) {
1255                CheckArg.isNotNull(expression, "expression");
1256                Set<PathExpression> compiledExpressions = getPathExpressions();
1257                compiledExpressions.add(expression);
1258                String[] strings = new String[compiledExpressions.size()];
1259                int index = 0;
1260                for (PathExpression compiledExpression : compiledExpressions) {
1261                    strings[index++] = compiledExpression.getExpression();
1262                }
1263                setProperty(DnaLexicon.PATH_EXPRESSION, strings);
1264                return this;
1265            }
1266    
1267            public PathExpressionOutput<ReturnType> sequencingFrom( final String fromPathExpression ) {
1268                CheckArg.isNotEmpty(fromPathExpression, "fromPathExpression");
1269                return new PathExpressionOutput<ReturnType>() {
1270                    public SequencerDefinition<ReturnType> andOutputtingTo( String into ) {
1271                        CheckArg.isNotEmpty(into, "into");
1272                        return sequencingFrom(PathExpression.compile(fromPathExpression + " => " + into));
1273                    }
1274                };
1275            }
1276        }
1277    
1278        /**
1279         * Representation of the current configuration content.
1280         */
1281        @Immutable
1282        public static class ConfigurationDefinition {
1283            private final ClassLoaderFactory classLoaderFactory;
1284            private final RepositorySource source;
1285            private final Path path;
1286            private final String workspace;
1287            private final ExecutionContext context;
1288            private Graph graph;
1289    
1290            protected ConfigurationDefinition( RepositorySource source,
1291                                               String workspace,
1292                                               Path path,
1293                                               ExecutionContext context,
1294                                               ClassLoaderFactory classLoaderFactory ) {
1295                this.source = source;
1296                this.path = path != null ? path : RootPath.INSTANCE;
1297                this.workspace = workspace;
1298                this.context = context;
1299                this.classLoaderFactory = classLoaderFactory != null ? classLoaderFactory : new StandardClassLoaderFactory();
1300            }
1301    
1302            /**
1303             * Get the repository source where the configuration content may be found
1304             * 
1305             * @return the source for the configuration repository; never null
1306             */
1307            public RepositorySource getRepositorySource() {
1308                return source;
1309            }
1310    
1311            /**
1312             * Get the path in the configuration repository where the configuration content may be found
1313             * 
1314             * @return the path to the configuration content; never null
1315             */
1316            public Path getPath() {
1317                return path;
1318            }
1319    
1320            /**
1321             * Get the name of the workspace used for the configuration repository.
1322             * 
1323             * @return the name of the workspace, or null if the default workspace should be used
1324             */
1325            public String getWorkspace() {
1326                return workspace;
1327            }
1328    
1329            /**
1330             * @return context
1331             */
1332            public ExecutionContext getContext() {
1333                return context;
1334            }
1335    
1336            /**
1337             * @return classLoaderFactory
1338             */
1339            public ClassLoaderFactory getClassLoaderFactory() {
1340                return classLoaderFactory;
1341            }
1342    
1343            /**
1344             * Return a copy of this configuration that uses the supplied path instead of this object's {@link #getPath() path}.
1345             * 
1346             * @param path the desired path for the new configuration; if null, then "/" is used
1347             * @return the new configuration
1348             */
1349            public ConfigurationDefinition with( Path path ) {
1350                return new ConfigurationDefinition(source, workspace, path, context, classLoaderFactory);
1351            }
1352    
1353            /**
1354             * Return a copy of this configuration that uses the supplied workspace name instead of this object's
1355             * {@link #getWorkspace() workspace}.
1356             * 
1357             * @param workspace the desired workspace name for the new configuration; if null, then the default workspace will be used
1358             * @return the new configuration
1359             */
1360            public ConfigurationDefinition withWorkspace( String workspace ) {
1361                return new ConfigurationDefinition(source, workspace, path, context, classLoaderFactory);
1362            }
1363    
1364            /**
1365             * Return a copy of this configuration that uses the supplied class loader factory instead of this object's
1366             * {@link #getClassLoaderFactory() class loader factory}.
1367             * 
1368             * @param classLoaderFactory the classloader factory, or null if the default factory should be used
1369             * @return the new configuration
1370             */
1371            public ConfigurationDefinition with( ClassLoaderFactory classLoaderFactory ) {
1372                return new ConfigurationDefinition(source, workspace, path, context, classLoaderFactory);
1373            }
1374    
1375            /**
1376             * Obtain a graph to this configuration repository. This method will always return the same graph instance.
1377             * 
1378             * @return the graph; never null
1379             */
1380            public Graph graph() {
1381                if (graph == null) {
1382                    graph = Graph.create(source, context);
1383                    if (workspace != null) graph.useWorkspace(workspace);
1384                }
1385                return graph;
1386            }
1387        }
1388    }