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