001    /*
002     * JBoss, Home of Professional Open Source.
003     * Copyright 2008, Red Hat Middleware LLC, and individual contributors
004     * as indicated by the @author tags. See the copyright.txt file in the
005     * distribution for a full listing of individual contributors. 
006     *
007     * This is free software; you can redistribute it and/or modify it
008     * under the terms of the GNU Lesser General Public License as
009     * published by the Free Software Foundation; either version 2.1 of
010     * the License, or (at your option) any later version.
011     *
012     * This software is distributed in the hope that it will be useful,
013     * but WITHOUT ANY WARRANTY; without even the implied warranty of
014     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015     * Lesser General Public License for more details.
016     *
017     * You should have received a copy of the GNU Lesser General Public
018     * License along with this software; if not, write to the Free
019     * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020     * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
021     */
022    package org.jboss.dna.repository;
023    
024    import java.util.ArrayList;
025    import java.util.List;
026    import net.jcip.annotations.Immutable;
027    import org.jboss.dna.common.component.ClassLoaderFactory;
028    import org.jboss.dna.common.i18n.I18n;
029    import org.jboss.dna.common.text.Inflector;
030    import org.jboss.dna.common.util.CheckArg;
031    import org.jboss.dna.common.util.Reflection;
032    import org.jboss.dna.graph.ExecutionContext;
033    import org.jboss.dna.graph.Graph;
034    import org.jboss.dna.graph.connector.RepositorySource;
035    import org.jboss.dna.graph.connector.inmemory.InMemoryRepositorySource;
036    import org.jboss.dna.graph.mimetype.MimeTypeDetector;
037    import org.jboss.dna.graph.property.Name;
038    import org.jboss.dna.graph.property.Path;
039    import org.jboss.dna.graph.property.PathExpression;
040    import org.jboss.dna.graph.property.PathFactory;
041    import org.jboss.dna.graph.property.ValueFormatException;
042    import org.jboss.dna.graph.property.basic.RootPath;
043    import org.jboss.dna.repository.sequencer.Sequencer;
044    
045    /**
046     * @param <BuilderType>
047     */
048    public abstract class Configurator<BuilderType> {
049    
050        /**
051         * Interface used to configure a sequencer.
052         * 
053         * @param <ReturnType> the type of interface to return after the sequencer's configuration is completed
054         */
055        public interface SequencerConfigurator<ReturnType> {
056    
057            /**
058             * Add a new {@link Sequencer sequencer} to this configuration. The new sequencer will have the supplied name, and if the
059             * name of an existing sequencer is used, this will replace the existing sequencer configuration.
060             * 
061             * @param id the identifier of the new sequencer
062             * @return the interface for choosing the class, which returns the interface used to configure the sequencer; never null
063             * @throws IllegalArgumentException if the sequencer name is null, empty, or otherwise invalid
064             */
065            public ChooseClass<Sequencer, SequencerDetails<ReturnType>> addSequencer( final String id );
066        }
067    
068        /**
069         * Interface used to initialize the configurator to use a specific repository containing configuration information.
070         * 
071         * @param <ReturnType> the configurator type returned after the configuration repository is defined
072         */
073        public interface Initializer<ReturnType> {
074            /**
075             * Specify that this configuration should use a particular {@link RepositorySource} for its configuration repository. By
076             * default each configuration uses an internal transient repository for its configuration, but using this method will make
077             * the configuration use a different repository (that is perhaps shared with other processes).
078             * 
079             * @return the interface for choosing the class, which returns the interface used to configure the repository source that
080             *         will be used for the configuration repository; never null
081             */
082            public ChooseClass<RepositorySource, ConfigRepositoryDetails<ReturnType>> withConfigurationRepository();
083        }
084    
085        /**
086         * Interface used to configure a repository source.
087         * 
088         * @param <ReturnType> the type of interface to return after the repository source's configuration is completed
089         */
090        public interface RepositoryConfigurator<ReturnType> {
091            /**
092             * Add a new {@link RepositorySource repository} for this configuration. The new repository will have the supplied name,
093             * and if the name of an existing repository is used, this will replace the existing repository configuration.
094             * 
095             * @param id the id of the new repository that is to be added
096             * @return the interface for choosing the class, which returns the interface used to configure the repository source;
097             *         never null
098             * @throws IllegalArgumentException if the repository name is null, empty, or otherwise invalid
099             * @see #addRepository(RepositorySource)
100             */
101            public ChooseClass<RepositorySource, RepositoryDetails<ReturnType>> addRepository( final String id );
102    
103            /**
104             * Add a new {@link RepositorySource repository} for this configuration. The new repository will have the supplied name,
105             * and if the name of an existing repository is used, this will replace the existing repository configuration.
106             * 
107             * @param source the {@link RepositorySource} instance that should be used
108             * @return this configuration object, for method-chaining purposes
109             * @throws IllegalArgumentException if the repository source reference is null
110             * @see #addRepository(String)
111             */
112            public ReturnType addRepository( RepositorySource source );
113        }
114    
115        /**
116         * Interface used to configure a MIME type detector.
117         * 
118         * @param <ReturnType> the type of interface to return after the detector's configuration is completed
119         */
120        public interface MimeDetectorConfigurator<ReturnType> {
121            /**
122             * Add a new {@link MimeTypeDetector MIME type detector} to this configuration. The new detector will have the supplied
123             * name, and if the name of an existing detector is used, this will replace the existing detector configuration.
124             * 
125             * @param id the id of the new detector
126             * @return the interface for choosing the class, which returns the interface used to configure the detector; never null
127             * @throws IllegalArgumentException if the detector name is null, empty, or otherwise invalid
128             */
129            public ChooseClass<MimeTypeDetector, MimeTypeDetectorDetails<ReturnType>> addMimeTypeDetector( final String id );
130        }
131    
132        /**
133         * Interface used to build the configured component.
134         * 
135         * @param <ReturnType> the type of component that this configuration builds
136         */
137        public interface Builder<ReturnType> {
138            /**
139             * Complete this configuration and create the corresponding engine.
140             * 
141             * @return the new engine configured by this instance
142             * @throws DnaConfigurationException if the engine cannot be created from this configuration.
143             */
144            public ReturnType build() throws DnaConfigurationException;
145        }
146    
147        /**
148         * Interface used to configure a {@link RepositorySource repository}.
149         * 
150         * @param <ReturnType>
151         */
152        public interface RepositoryDetails<ReturnType>
153            extends SetName<RepositoryDetails<ReturnType>>, SetDescription<RepositoryDetails<ReturnType>>,
154            SetProperties<RepositoryDetails<ReturnType>>, And<ReturnType> {
155        }
156    
157        /**
158         * Interface used to define the configuration repository.
159         * 
160         * @param <ReturnType>
161         */
162        public interface ConfigRepositoryDetails<ReturnType>
163            extends SetDescription<ConfigRepositoryDetails<ReturnType>>, SetProperties<ConfigRepositoryDetails<ReturnType>>,
164            And<ReturnType> {
165            /**
166             * Specify the path under which the configuration content is to be found. This path is assumed to be "/" by default.
167             * 
168             * @param path the path to the configuration content in the configuration source; may not be null
169             * @return this instance for method chaining purposes; never null
170             */
171            public ConfigRepositoryDetails<ReturnType> under( String path );
172        }
173    
174        /**
175         * Interface used to configure a {@link Sequencer sequencer}.
176         * 
177         * @param <ReturnType>
178         */
179        public interface SequencerDetails<ReturnType>
180            extends SetName<SequencerDetails<ReturnType>>, SetDescription<SequencerDetails<ReturnType>>, And<ReturnType> {
181    
182            /**
183             * Specify the input {@link PathExpression path expression} represented as a string, which determines when this sequencer
184             * will be executed.
185             * 
186             * @param inputPathExpression the path expression for nodes that, when they change, will be passed as an input to the
187             *        sequencer
188             * @return the interface used to specify the output path expression; never null
189             */
190            PathExpressionOutput<ReturnType> sequencingFrom( String inputPathExpression );
191    
192            /**
193             * Specify the input {@link PathExpression path expression}, which determines when this sequencer will be executed.
194             * 
195             * @param inputPathExpression the path expression for nodes that, when they change, will be passed as an input to the
196             *        sequencer
197             * @return the interface used to continue specifying the configuration of the sequencer
198             */
199            SequencerDetails<ReturnType> sequencingFrom( PathExpression inputPathExpression );
200        }
201    
202        /**
203         * Interface used to specify the output path expression for a
204         * {@link Configurator.SequencerDetails#sequencingFrom(PathExpression) sequencer configuration}.
205         * 
206         * @param <ReturnType>
207         */
208        public interface PathExpressionOutput<ReturnType> {
209            /**
210             * Specify the output {@link PathExpression path expression}, which determines where this sequencer's output will be
211             * placed.
212             * 
213             * @param outputExpression the path expression for the location(s) where output generated by the sequencer is to be placed
214             * @return the interface used to continue specifying the configuration of the sequencer
215             */
216            SequencerDetails<ReturnType> andOutputtingTo( String outputExpression );
217        }
218    
219        /**
220         * Interface used to configure a {@link MimeTypeDetector MIME type detector}.
221         * 
222         * @param <ReturnType>
223         */
224        public interface MimeTypeDetectorDetails<ReturnType>
225            extends SetName<MimeTypeDetectorDetails<ReturnType>>, SetDescription<MimeTypeDetectorDetails<ReturnType>>,
226            SetProperties<MimeTypeDetectorDetails<ReturnType>>, And<ReturnType> {
227        }
228    
229        /**
230         * Interface for configuring the JavaBean-style properties of an object.
231         * 
232         * @param <ReturnType> the interface returned after the property has been set.
233         * @author Randall Hauch
234         */
235        public interface SetProperties<ReturnType> {
236            /**
237             * Specify the name of the JavaBean-style property that is to be set. The value may be set using the interface returned by
238             * this method.
239             * 
240             * @param beanPropertyName the name of the JavaBean-style property (e.g., "retryLimit")
241             * @return the interface used to set the value for the property; never null
242             */
243            PropertySetter<ReturnType> with( String beanPropertyName );
244        }
245    
246        /**
247         * The interface used to set the value for a JavaBean-style property.
248         * 
249         * @param <ReturnType> the interface returned from these methods
250         * @author Randall Hauch
251         * @see Configurator.SetProperties#with(String)
252         */
253        public interface PropertySetter<ReturnType> {
254            /**
255             * Set the property value to an integer.
256             * 
257             * @param value the new value for the property
258             * @return the next component to continue configuration; never null
259             */
260            ReturnType setTo( int value );
261    
262            /**
263             * Set the property value to a long number.
264             * 
265             * @param value the new value for the property
266             * @return the next component to continue configuration; never null
267             */
268            ReturnType setTo( long value );
269    
270            /**
271             * Set the property value to a short.
272             * 
273             * @param value the new value for the property
274             * @return the next component to continue configuration; never null
275             */
276            ReturnType setTo( short value );
277    
278            /**
279             * Set the property value to a boolean.
280             * 
281             * @param value the new value for the property
282             * @return the next component to continue configuration; never null
283             */
284            ReturnType setTo( boolean value );
285    
286            /**
287             * Set the property value to a float.
288             * 
289             * @param value the new value for the property
290             * @return the next component to continue configuration; never null
291             */
292            ReturnType setTo( float value );
293    
294            /**
295             * Set the property value to a double.
296             * 
297             * @param value the new value for the property
298             * @return the next component to continue configuration; never null
299             */
300            ReturnType setTo( double value );
301    
302            /**
303             * Set the property value to a string.
304             * 
305             * @param value the new value for the property
306             * @return the next component to continue configuration; never null
307             */
308            ReturnType setTo( String value );
309    
310            /**
311             * Set the property value to an object.
312             * 
313             * @param value the new value for the property
314             * @return the next component to continue configuration; never null
315             */
316            ReturnType setTo( Object value );
317        }
318    
319        /**
320         * The interface used to configure the class used for a component.
321         * 
322         * @param <ComponentClassType> the class or interface that the component is to implement
323         * @param <ReturnType> the interface returned from these methods
324         */
325        public interface ChooseClass<ComponentClassType, ReturnType> {
326    
327            /**
328             * Specify the name of the class that should be instantiated for the instance. The classpath information will need to be
329             * defined using the returned interface.
330             * 
331             * @param classname the name of the class that should be instantiated
332             * @return the interface used to define the classpath information; never null
333             * @throws IllegalArgumentException if the class name is null, empty, blank, or not a valid class name
334             */
335            LoadedFrom<ReturnType> usingClass( String classname );
336    
337            /**
338             * Specify the class that should be instantiated for the instance. Because the class is already available to this class
339             * loader, there is no need to specify the classloader information.
340             * 
341             * @param clazz the class that should be instantiated
342             * @return the next component to continue configuration; never null
343             * @throws DnaConfigurationException if the class could not be accessed and instantiated (if needed)
344             * @throws IllegalArgumentException if the class reference is null
345             */
346            ReturnType usingClass( Class<? extends ComponentClassType> clazz );
347        }
348    
349        /**
350         * The interface used to set a description on a component.
351         * 
352         * @param <ReturnType> the interface returned from these methods
353         */
354        public interface SetDescription<ReturnType> {
355            /**
356             * Specify the description of this component.
357             * 
358             * @param description the description; may be null or empty
359             * @return the next component to continue configuration; never null
360             */
361            ReturnType describedAs( String description );
362        }
363    
364        /**
365         * The interface used to set a human readable name on a component.
366         * 
367         * @param <ReturnType> the interface returned from these methods
368         */
369        public interface SetName<ReturnType> {
370            /**
371             * Specify the human-readable name for this component.
372             * 
373             * @param description the description; may be null or empty
374             * @return the next component to continue configuration; never null
375             */
376            ReturnType named( String description );
377        }
378    
379        /**
380         * Interface for specifying from where the component's class is to be loaded.
381         * 
382         * @param <ReturnType> the interface returned from these methods
383         */
384        public interface LoadedFrom<ReturnType> {
385            /**
386             * Specify the names of the classloaders that form the classpath for the component, from which the component's class (and
387             * its dependencies) can be loaded. The names correspond to the names supplied to the
388             * {@link ExecutionContext#getClassLoader(String...)} methods.
389             * 
390             * @param classPathNames the names for the classloaders, as passed to the {@link ClassLoaderFactory} implementation (e.g.,
391             *        the {@link ExecutionContext}).
392             * @return the next component to continue configuration; never null
393             * @see #loadedFromClasspath()
394             * @see ExecutionContext#getClassLoader(String...)
395             */
396            ReturnType loadedFrom( String... classPathNames );
397    
398            /**
399             * Specify that the component (and its dependencies) will be found on the current (or
400             * {@link Thread#getContextClassLoader() current context}) classloader.
401             * 
402             * @return the next component to continue configuration; never null
403             * @see #loadedFrom(String...)
404             * @see ExecutionContext#getClassLoader(String...)
405             */
406            ReturnType loadedFromClasspath();
407        }
408    
409        /**
410         * Continue with another aspect of configuration.
411         * 
412         * @param <ReturnType>
413         */
414        public interface And<ReturnType> {
415    
416            /**
417             * Return a reference to the next configuration interface for additional operations.
418             * 
419             * @return a reference to the next configuration interface
420             */
421            ReturnType and();
422        }
423    
424        protected final BuilderType builder;
425        protected final ExecutionContext context;
426        protected ConfigurationRepository configurationSource;
427        private Graph graph;
428        private Graph.Batch batch;
429    
430        /**
431         * Specify a new {@link ExecutionContext} that should be used for this DNA instance.
432         * 
433         * @param context the new context, or null if a default-constructed execution context should be used
434         * @param builder the builder
435         * @throws IllegalArgumentException if the supplied context reference is null
436         */
437        protected Configurator( ExecutionContext context,
438                                BuilderType builder ) {
439            CheckArg.isNotNull(context, "context");
440            CheckArg.isNotNull(builder, "builder");
441            this.context = context;
442            this.builder = builder;
443    
444            // Set up the default configuration repository ...
445            this.configurationSource = createDefaultConfigurationSource();
446        }
447    
448        /**
449         * Method that is used to set up the default configuration repository source. By default, this method sets up the
450         * {@link InMemoryRepositorySource} loaded from the classpath.
451         * 
452         * @return the default repository source
453         */
454        protected ConfigurationRepository createDefaultConfigurationSource() {
455            InMemoryRepositorySource defaultSource = new InMemoryRepositorySource();
456            defaultSource.setName("Configuration");
457            ConfigurationRepository result = new ConfigurationRepository(defaultSource, "Configuration Repository", null);
458            return result;
459        }
460    
461        /**
462         * Get the execution context used by this configurator.
463         * 
464         * @return the execution context; never null
465         */
466        public final ExecutionContext getExecutionContext() {
467            return this.context;
468        }
469    
470        protected final PathFactory pathFactory() {
471            return getExecutionContext().getValueFactories().getPathFactory();
472        }
473    
474        /**
475         * Get the graph containing the configuration information.
476         * 
477         * @return the configuration repository graph; never null
478         * @see #graph()
479         */
480        protected final Graph graph() {
481            if (this.graph == null) {
482                this.graph = Graph.create(configurationSource.getRepositorySource(), context);
483            }
484            return this.graph;
485        }
486    
487        /**
488         * Get the graph batch that can be used to change the configuration, where the changes are enqueued until {@link #save()
489         * saved}.
490         * 
491         * @return the latest batch for changes to the configuration repository; never null
492         * @see #graph()
493         */
494        protected final Graph.Batch configuration() {
495            if (this.batch == null) {
496                this.batch = graph().batch();
497            }
498            return this.batch;
499        }
500    
501        /**
502         * Save any changes that have been made so far to the configuration. This method does nothing if no changes have been made.
503         * 
504         * @return this configuration object for method chaining purposes; never null
505         */
506        public BuilderType save() {
507            if (this.batch != null) {
508                this.batch.execute();
509                this.batch = this.graph.batch();
510            }
511            return this.builder;
512        }
513    
514        protected abstract Name nameFor( String name );
515    
516        protected Path createOrReplaceNode( Path parentPath,
517                                            String id ) {
518            Path path = pathFactory().create(parentPath, id);
519            configuration().create(path).with(DnaLexicon.READABLE_NAME, id).and();
520            return path;
521        }
522    
523        protected void recordBeanPropertiesInGraph( Path path,
524                                                    Object javaBean ) {
525            Reflection reflector = new Reflection(javaBean.getClass());
526            for (String propertyName : reflector.findGetterPropertyNames()) {
527                Object value;
528                try {
529                    value = reflector.invokeGetterMethodOnTarget(propertyName, javaBean);
530                    if (value == null) continue;
531                    propertyName = Inflector.getInstance().lowerCamelCase(propertyName);
532                    configuration().set(nameFor(propertyName)).to(value).on(path);
533                } catch (ValueFormatException err) {
534                    throw err;
535                } catch (Throwable err) {
536                    // Unable to call getter and set property
537                }
538            }
539        }
540    
541        protected class ConfigurationRepositoryClassChooser<ReturnType>
542            implements ChooseClass<RepositorySource, ConfigRepositoryDetails<ReturnType>> {
543    
544            private final ReturnType returnObject;
545    
546            protected ConfigurationRepositoryClassChooser( ReturnType returnObject ) {
547                assert returnObject != null;
548                this.returnObject = returnObject;
549            }
550    
551            public LoadedFrom<ConfigRepositoryDetails<ReturnType>> usingClass( final String className ) {
552                return new LoadedFrom<ConfigRepositoryDetails<ReturnType>>() {
553                    @SuppressWarnings( "unchecked" )
554                    public ConfigRepositoryDetails loadedFrom( String... classpath ) {
555                        ClassLoader classLoader = getExecutionContext().getClassLoader(classpath);
556                        Class<? extends RepositorySource> clazz = null;
557                        try {
558                            clazz = (Class<? extends RepositorySource>)classLoader.loadClass(className);
559                        } catch (ClassNotFoundException err) {
560                            throw new DnaConfigurationException(RepositoryI18n.unableToLoadClassUsingClasspath.text(className,
561                                                                                                                    classpath));
562                        }
563                        return usingClass(clazz);
564                    }
565    
566                    @SuppressWarnings( "unchecked" )
567                    public ConfigRepositoryDetails loadedFromClasspath() {
568                        Class<? extends RepositorySource> clazz = null;
569                        try {
570                            clazz = (Class<? extends RepositorySource>)Class.forName(className);
571                        } catch (ClassNotFoundException err) {
572                            throw new DnaConfigurationException(RepositoryI18n.unableToLoadClass.text(className));
573                        }
574                        return usingClass(clazz);
575                    }
576                };
577            }
578    
579            public ConfigRepositoryDetails<ReturnType> usingClass( Class<? extends RepositorySource> repositorySource ) {
580                try {
581                    Configurator.this.configurationSource = new ConfigurationRepository(repositorySource.newInstance());
582                } catch (InstantiationException err) {
583                    I18n msg = RepositoryI18n.errorCreatingInstanceOfClass;
584                    throw new DnaConfigurationException(msg.text(repositorySource.getName(), err.getLocalizedMessage()), err);
585                } catch (IllegalAccessException err) {
586                    I18n msg = RepositoryI18n.errorCreatingInstanceOfClass;
587                    throw new DnaConfigurationException(msg.text(repositorySource.getName(), err.getLocalizedMessage()), err);
588                }
589                return new ConfigurationSourceDetails<ReturnType>(returnObject);
590            }
591        }
592    
593        protected class ConfigurationSourceDetails<ReturnType> implements ConfigRepositoryDetails<ReturnType> {
594            private final ReturnType returnObject;
595    
596            protected ConfigurationSourceDetails( ReturnType returnObject ) {
597                assert returnObject != null;
598                this.returnObject = returnObject;
599            }
600    
601            /**
602             * {@inheritDoc}
603             * 
604             * @see org.jboss.dna.repository.Configurator.SetDescription#describedAs(java.lang.String)
605             */
606            public ConfigRepositoryDetails<ReturnType> describedAs( String description ) {
607                Configurator.this.configurationSource = Configurator.this.configurationSource.with(description);
608                return this;
609            }
610    
611            /**
612             * {@inheritDoc}
613             * 
614             * @see org.jboss.dna.repository.Configurator.SetProperties#with(java.lang.String)
615             */
616            public PropertySetter<ConfigRepositoryDetails<ReturnType>> with( String propertyName ) {
617                return new BeanPropertySetter<ConfigRepositoryDetails<ReturnType>>(
618                                                                                   Configurator.this.configurationSource.getRepositorySource(),
619                                                                                   propertyName, this);
620            }
621    
622            /**
623             * {@inheritDoc}
624             * 
625             * @see org.jboss.dna.repository.Configurator.ConfigRepositoryDetails#under(java.lang.String)
626             */
627            public ConfigRepositoryDetails<ReturnType> under( String path ) {
628                CheckArg.isNotNull(path, "path");
629                Path newPath = getExecutionContext().getValueFactories().getPathFactory().create(path);
630                Configurator.this.configurationSource = Configurator.this.configurationSource.with(newPath);
631                return null;
632            }
633    
634            /**
635             * {@inheritDoc}
636             * 
637             * @see org.jboss.dna.repository.Configurator.And#and()
638             */
639            public ReturnType and() {
640                return returnObject;
641            }
642        }
643    
644        /**
645         * Reusable implementation of {@link Configurator.ChooseClass} that can be used to obtain the name of a class and how its
646         * class loader is defined.
647         * 
648         * @param <ComponentClass> the type of the component that is being chosen
649         * @param <ReturnType> the interface that should be returned when the class name and classpath have been chosen.
650         */
651        protected class ClassChooser<ComponentClass, ReturnType> implements Configurator.ChooseClass<ComponentClass, ReturnType> {
652            protected final Path pathOfComponentNode;
653            protected final ReturnType returnObject;
654    
655            protected ClassChooser( Path pathOfComponentNode,
656                                    ReturnType returnObject ) {
657                assert pathOfComponentNode != null;
658                assert returnObject != null;
659                this.pathOfComponentNode = pathOfComponentNode;
660                this.returnObject = returnObject;
661            }
662    
663            /**
664             * {@inheritDoc}
665             * 
666             * @see Configurator.ChooseClass#usingClass(java.lang.String)
667             */
668            public Configurator.LoadedFrom<ReturnType> usingClass( final String classname ) {
669                CheckArg.isNotEmpty(classname, "classname");
670                configuration().set(DnaLexicon.CLASSNAME).to(classname).on(pathOfComponentNode);
671                return new Configurator.LoadedFrom<ReturnType>() {
672                    public ReturnType loadedFromClasspath() {
673                        return returnObject;
674                    }
675    
676                    public ReturnType loadedFrom( String... classpath ) {
677                        CheckArg.isNotEmpty(classpath, "classpath");
678                        if (classpath.length == 1 && classpath[0] != null) {
679                            configuration().set(DnaLexicon.CLASSPATH).to(classpath[0]).on(pathOfComponentNode);
680                        } else {
681                            Object[] remaining = new String[classpath.length - 1];
682                            System.arraycopy(classpath, 1, remaining, 0, remaining.length);
683                            configuration().set(DnaLexicon.CLASSPATH).to(classpath[0], remaining).on(pathOfComponentNode);
684                        }
685                        return returnObject;
686                    }
687                };
688            }
689    
690            /**
691             * {@inheritDoc}
692             * 
693             * @see Configurator.ChooseClass#usingClass(java.lang.Class)
694             */
695            public ReturnType usingClass( Class<? extends ComponentClass> clazz ) {
696                CheckArg.isNotNull(clazz, "clazz");
697                return usingClass(clazz.getName()).loadedFromClasspath();
698            }
699        }
700    
701        /**
702         * Reusable implementation of {@link Configurator.PropertySetter} that sets the JavaBean-style property using reflection.
703         * 
704         * @param <ReturnType>
705         */
706        protected class BeanPropertySetter<ReturnType> implements Configurator.PropertySetter<ReturnType> {
707            private final Object javaBean;
708            private final String beanPropertyName;
709            private final ReturnType returnObject;
710    
711            protected BeanPropertySetter( Object javaBean,
712                                          String beanPropertyName,
713                                          ReturnType returnObject ) {
714                assert javaBean != null;
715                assert beanPropertyName != null;
716                assert returnObject != null;
717                this.javaBean = javaBean;
718                this.beanPropertyName = beanPropertyName;
719                this.returnObject = returnObject;
720            }
721    
722            public ReturnType setTo( boolean value ) {
723                return setTo((Object)value);
724            }
725    
726            public ReturnType setTo( int value ) {
727                return setTo((Object)value);
728            }
729    
730            public ReturnType setTo( long value ) {
731                return setTo((Object)value);
732            }
733    
734            public ReturnType setTo( short value ) {
735                return setTo((Object)value);
736            }
737    
738            public ReturnType setTo( float value ) {
739                return setTo((Object)value);
740            }
741    
742            public ReturnType setTo( double value ) {
743                return setTo((Object)value);
744            }
745    
746            public ReturnType setTo( String value ) {
747                return setTo((Object)value);
748            }
749    
750            public ReturnType setTo( Object value ) {
751                // Set the JavaBean-style property on the RepositorySource instance ...
752                Reflection reflection = new Reflection(javaBean.getClass());
753                try {
754                    reflection.invokeSetterMethodOnTarget(beanPropertyName, javaBean, value);
755                } catch (Throwable err) {
756                    I18n msg = RepositoryI18n.errorSettingJavaBeanPropertyOnInstanceOfClass;
757                    throw new DnaConfigurationException(msg.text(beanPropertyName, javaBean.getClass(), err.getMessage()), err);
758                }
759                return returnObject;
760            }
761        }
762    
763        /**
764         * Reusable implementation of {@link Configurator.PropertySetter} that sets the property on the specified node in the
765         * configuration graph.
766         * 
767         * @param <ReturnType>
768         */
769        protected class GraphPropertySetter<ReturnType> implements Configurator.PropertySetter<ReturnType> {
770            private final Path path;
771            private final String beanPropertyName;
772            private final ReturnType returnObject;
773    
774            protected GraphPropertySetter( Path path,
775                                           String beanPropertyName,
776                                           ReturnType returnObject ) {
777                assert path != null;
778                assert beanPropertyName != null;
779                assert returnObject != null;
780                this.path = path;
781                this.beanPropertyName = Inflector.getInstance().lowerCamelCase(beanPropertyName);
782                this.returnObject = returnObject;
783            }
784    
785            public ReturnType setTo( boolean value ) {
786                configuration().set(nameFor(beanPropertyName)).to(value).on(path);
787                return returnObject;
788            }
789    
790            public ReturnType setTo( int value ) {
791                configuration().set(nameFor(beanPropertyName)).to(value).on(path);
792                return returnObject;
793            }
794    
795            public ReturnType setTo( long value ) {
796                configuration().set(nameFor(beanPropertyName)).to(value).on(path);
797                return returnObject;
798            }
799    
800            public ReturnType setTo( short value ) {
801                configuration().set(nameFor(beanPropertyName)).to(value).on(path);
802                return returnObject;
803            }
804    
805            public ReturnType setTo( float value ) {
806                configuration().set(nameFor(beanPropertyName)).to(value).on(path);
807                return returnObject;
808            }
809    
810            public ReturnType setTo( double value ) {
811                configuration().set(nameFor(beanPropertyName)).to(value).on(path);
812                return returnObject;
813            }
814    
815            public ReturnType setTo( String value ) {
816                configuration().set(nameFor(beanPropertyName)).to(value).on(path);
817                return returnObject;
818            }
819    
820            public ReturnType setTo( Object value ) {
821                configuration().set(nameFor(beanPropertyName)).to(value).on(path);
822                return returnObject;
823            }
824        }
825    
826        protected class GraphRepositoryDetails<ReturnType> implements RepositoryDetails<ReturnType> {
827            private final Path path;
828            private final ReturnType returnObject;
829    
830            protected GraphRepositoryDetails( Path path,
831                                              ReturnType returnObject ) {
832                assert path != null;
833                assert returnObject != null;
834                this.path = path;
835                this.returnObject = returnObject;
836            }
837    
838            /**
839             * {@inheritDoc}
840             * 
841             * @see org.jboss.dna.repository.Configurator.SetName#named(java.lang.String)
842             */
843            public RepositoryDetails<ReturnType> named( String name ) {
844                configuration().set(DnaLexicon.READABLE_NAME).to(name).on(path);
845                return this;
846            }
847    
848            /**
849             * {@inheritDoc}
850             * 
851             * @see org.jboss.dna.repository.Configurator.SetDescription#describedAs(java.lang.String)
852             */
853            public RepositoryDetails<ReturnType> describedAs( String description ) {
854                configuration().set(DnaLexicon.DESCRIPTION).to(description).on(path);
855                return this;
856            }
857    
858            /**
859             * {@inheritDoc}
860             * 
861             * @see org.jboss.dna.repository.Configurator.SetProperties#with(java.lang.String)
862             */
863            public PropertySetter<RepositoryDetails<ReturnType>> with( String propertyName ) {
864                return new GraphPropertySetter<RepositoryDetails<ReturnType>>(path, propertyName, this);
865            }
866    
867            /**
868             * {@inheritDoc}
869             * 
870             * @see org.jboss.dna.repository.Configurator.And#and()
871             */
872            public ReturnType and() {
873                return returnObject;
874            }
875        }
876    
877        protected class GraphSequencerDetails<ReturnType> implements SequencerDetails<ReturnType> {
878            private final Path path;
879            private final List<String> compiledExpressions = new ArrayList<String>();
880            private final ReturnType returnObject;
881    
882            protected GraphSequencerDetails( Path path,
883                                             ReturnType returnObject ) {
884                assert path != null;
885                assert returnObject != null;
886                this.path = path;
887                this.returnObject = returnObject;
888            }
889    
890            /**
891             * {@inheritDoc}
892             * 
893             * @see org.jboss.dna.repository.Configurator.SequencerDetails#sequencingFrom(java.lang.String)
894             */
895            public PathExpressionOutput<ReturnType> sequencingFrom( final String from ) {
896                CheckArg.isNotEmpty(from, "from");
897                return new PathExpressionOutput<ReturnType>() {
898                    /**
899                     * {@inheritDoc}
900                     * 
901                     * @see org.jboss.dna.repository.Configurator.PathExpressionOutput#andOutputtingTo(java.lang.String)
902                     */
903                    public SequencerDetails<ReturnType> andOutputtingTo( String into ) {
904                        CheckArg.isNotEmpty(into, "into");
905                        return sequencingFrom(PathExpression.compile(from + " => " + into));
906                    }
907                };
908            }
909    
910            /**
911             * {@inheritDoc}
912             * 
913             * @see org.jboss.dna.repository.Configurator.SetName#named(java.lang.String)
914             */
915            public SequencerDetails<ReturnType> named( String name ) {
916                configuration().set(DnaLexicon.READABLE_NAME).to(name).on(path);
917                return this;
918            }
919    
920            /**
921             * {@inheritDoc}
922             * 
923             * @see org.jboss.dna.repository.Configurator.SequencerDetails#sequencingFrom(org.jboss.dna.graph.property.PathExpression)
924             */
925            public SequencerDetails<ReturnType> sequencingFrom( PathExpression expression ) {
926                CheckArg.isNotNull(expression, "expression");
927                String compiledExpression = expression.getExpression();
928                if (!compiledExpressions.contains(compiledExpression)) compiledExpressions.add(compiledExpression);
929                configuration().set(DnaLexicon.PATH_EXPRESSIONS).on(path).to(compiledExpressions);
930                return this;
931            }
932    
933            /**
934             * {@inheritDoc}
935             * 
936             * @see org.jboss.dna.repository.Configurator.SetDescription#describedAs(java.lang.String)
937             */
938            public SequencerDetails<ReturnType> describedAs( String description ) {
939                configuration().set(DnaLexicon.DESCRIPTION).to(description).on(path);
940                return this;
941            }
942    
943            /**
944             * {@inheritDoc}
945             * 
946             * @see org.jboss.dna.repository.Configurator.And#and()
947             */
948            public ReturnType and() {
949                return returnObject;
950            }
951        }
952    
953        protected class GraphMimeTypeDetectorDetails<ReturnType> implements MimeTypeDetectorDetails<ReturnType> {
954            private final Path path;
955            private final ReturnType returnObject;
956    
957            protected GraphMimeTypeDetectorDetails( Path path,
958                                                    ReturnType returnObject ) {
959                assert path != null;
960                assert returnObject != null;
961                this.path = path;
962                this.returnObject = returnObject;
963            }
964    
965            /**
966             * {@inheritDoc}
967             * 
968             * @see org.jboss.dna.repository.Configurator.SetName#named(java.lang.String)
969             */
970            public MimeTypeDetectorDetails<ReturnType> named( String name ) {
971                configuration().set(DnaLexicon.READABLE_NAME).to(name).on(path);
972                return this;
973            }
974    
975            /**
976             * {@inheritDoc}
977             * 
978             * @see org.jboss.dna.repository.Configurator.SetProperties#with(java.lang.String)
979             */
980            public PropertySetter<MimeTypeDetectorDetails<ReturnType>> with( String propertyName ) {
981                return new GraphPropertySetter<MimeTypeDetectorDetails<ReturnType>>(path, propertyName, this);
982            }
983    
984            /**
985             * {@inheritDoc}
986             * 
987             * @see org.jboss.dna.repository.Configurator.SetDescription#describedAs(java.lang.String)
988             */
989            public MimeTypeDetectorDetails<ReturnType> describedAs( String description ) {
990                configuration().set(DnaLexicon.DESCRIPTION).to(description).on(path);
991                return this;
992            }
993    
994            /**
995             * {@inheritDoc}
996             * 
997             * @see org.jboss.dna.repository.Configurator.And#and()
998             */
999            public ReturnType and() {
1000                return returnObject;
1001            }
1002        }
1003    
1004        @Immutable
1005        public static class ConfigurationRepository {
1006            private final RepositorySource source;
1007            private final String description;
1008            private final Path path;
1009    
1010            protected ConfigurationRepository( RepositorySource source ) {
1011                this(source, null, null);
1012            }
1013    
1014            protected ConfigurationRepository( RepositorySource source,
1015                                               String description,
1016                                               Path path ) {
1017                this.source = source;
1018                this.description = description != null ? description : "";
1019                this.path = path != null ? path : RootPath.INSTANCE;
1020            }
1021    
1022            /**
1023             * @return source
1024             */
1025            public RepositorySource getRepositorySource() {
1026                return source;
1027            }
1028    
1029            /**
1030             * @return description
1031             */
1032            public String getDescription() {
1033                return description;
1034            }
1035    
1036            /**
1037             * @return path
1038             */
1039            public Path getPath() {
1040                return path;
1041            }
1042    
1043            public ConfigurationRepository with( String description ) {
1044                return new ConfigurationRepository(source, description, path);
1045            }
1046    
1047            public ConfigurationRepository with( Path path ) {
1048                return new ConfigurationRepository(source, description, path);
1049            }
1050        }
1051    }