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("Source1").setClass(InMemoryRepositorySource.class).setDescription("description");
069 * configuration.mimeTypeDetector("detector").setClass(ExtensionBasedMimeTypeDetector.class).setDescription("default detector");
070 * configuration.sequencer("MicrosoftDocs")
071 * .setClass("org.jboss.dna.sequencer.msoffice.MSOfficeMetadataSequencer")
072 * .setDescription("Our primary sequencer for all .doc files")
073 * .sequencingFrom("/public//(*.(doc|xml|ppt)[*]/jcr:content[@jcr:data]")
074 * .andOutputtingTo("/documents/$1");
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 }