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