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