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.jcr; 025 026 import java.io.File; 027 import java.io.IOException; 028 import java.io.InputStream; 029 import java.net.URL; 030 import java.util.Collections; 031 import java.util.EnumMap; 032 import java.util.HashMap; 033 import java.util.HashSet; 034 import java.util.Map; 035 import java.util.Set; 036 import net.jcip.annotations.NotThreadSafe; 037 import org.jboss.dna.cnd.CndImporter; 038 import org.jboss.dna.common.component.ClassLoaderFactory; 039 import org.jboss.dna.common.util.CheckArg; 040 import org.jboss.dna.graph.ExecutionContext; 041 import org.jboss.dna.graph.Graph; 042 import org.jboss.dna.graph.Location; 043 import org.jboss.dna.graph.Node; 044 import org.jboss.dna.graph.Subgraph; 045 import org.jboss.dna.graph.connector.RepositorySource; 046 import org.jboss.dna.graph.io.Destination; 047 import org.jboss.dna.graph.io.GraphBatchDestination; 048 import org.jboss.dna.graph.property.Name; 049 import org.jboss.dna.graph.property.Path; 050 import org.jboss.dna.graph.property.PathNotFoundException; 051 import org.jboss.dna.graph.property.Property; 052 import org.jboss.dna.graph.property.NamespaceRegistry.Namespace; 053 import org.jboss.dna.jcr.JcrRepository.Option; 054 import org.jboss.dna.repository.DnaConfiguration; 055 import org.jboss.dna.repository.DnaConfigurationException; 056 import org.xml.sax.SAXException; 057 058 /** 059 * A configuration builder for a {@link JcrEngine}. This class is an internal domain-specific language (DSL), and is designed to 060 * be used in a traditional way or in a method-chained manner: 061 * 062 * <pre> 063 * configuration.repositorySource("Source1").setClass(InMemoryRepositorySource.class).setDescription("description"); 064 * configuration.mimeTypeDetector("detector").setClass(ExtensionBasedMimeTypeDetector.class).setDescription("default detector"); 065 * configuration.sequencer("MicrosoftDocs") 066 * .setClass("org.jboss.dna.sequencer.msoffice.MSOfficeMetadataSequencer") 067 * .setDescription("Our primary sequencer for all .doc files") 068 * .sequencingFrom("/public//(*.(doc|xml|ppt)[*]/jcr:content[@jcr:data]") 069 * .andOutputtingTo("/documents/$1"); 070 * configuration.repository("MyRepository").setSource("Source1"); 071 * configuration.save(); 072 * </pre> 073 */ 074 @NotThreadSafe 075 public class JcrConfiguration extends DnaConfiguration { 076 077 /** 078 * Interface used to define a JCR Repository that's accessible from the JcrEngine. 079 * 080 * @param <ReturnType> 081 */ 082 public interface RepositoryDefinition<ReturnType> extends Returnable<ReturnType>, Removable<ReturnType> { 083 084 /** 085 * Specify the name of the repository source that is to be used by this JCR repository. 086 * 087 * @param sourceName the name of the repository source that should be exposed by this JCR repository 088 * @return the interface used to set the value for the property; never null 089 * @throws IllegalArgumentException if the source name parameter is null 090 */ 091 RepositoryDefinition<ReturnType> setSource( String sourceName ); 092 093 /** 094 * Get the name of the repository source that is to be used by this JCR repository. 095 * 096 * @return the source name, or null if it has not yet been set 097 */ 098 String getSource(); 099 100 /** 101 * Specify the repository option that is to be set. 102 * 103 * @param option the option to be set 104 * @param value the new value for the option 105 * @return the interface used to set the value for the property; never null 106 * @throws IllegalArgumentException if either parameter is null 107 */ 108 RepositoryDefinition<ReturnType> setOption( JcrRepository.Option option, 109 String value ); 110 111 /** 112 * Get the value for the repository option. 113 * 114 * @param option the option 115 * @return the current option value, which may be null if the option has not been set (and its default would be used) 116 * @throws IllegalArgumentException if the option parameter is null 117 */ 118 String getOption( JcrRepository.Option option ); 119 120 /** 121 * Specify that the CND file located at the supplied path should be loaded into the repository. 122 * 123 * @param pathToCndFile the path to the CND file 124 * @return this object for chained method invocation 125 * @throws IllegalArgumentException if the string is null or empty 126 * @throws DnaConfigurationException if there is an error reading the CND file 127 */ 128 RepositoryDefinition<ReturnType> addNodeTypes( String pathToCndFile ); 129 130 /** 131 * Specify that the CND file is to be loaded into the repository. 132 * 133 * @param cndFile the CND file 134 * @return this object for chained method invocation 135 * @throws IllegalArgumentException if the file is null 136 * @throws DnaConfigurationException if there is an error reading the file 137 */ 138 RepositoryDefinition<ReturnType> addNodeTypes( File cndFile ); 139 140 /** 141 * Specify that the CND file is to be loaded into the repository. 142 * 143 * @param urlOfCndFile the URL of the CND file 144 * @return this object for chained method invocation 145 * @throws IllegalArgumentException if the URL is null 146 * @throws DnaConfigurationException if there is an error reading the content at the URL 147 */ 148 RepositoryDefinition<ReturnType> addNodeTypes( URL urlOfCndFile ); 149 150 /** 151 * Specify that the CND file is to be loaded into the repository. 152 * 153 * @param cndContent the stream containing the CND content 154 * @return this object for chained method invocation 155 * @throws IllegalArgumentException if the URL is null 156 * @throws DnaConfigurationException if there is an error reading the stream at the URL 157 */ 158 RepositoryDefinition<ReturnType> addNodeTypes( InputStream cndContent ); 159 160 /** 161 * Specify the namespace binding that should be made available in this repository. 162 * 163 * @param prefix the namespace prefix; may not be null or empty, and must be a valid prefix 164 * @param uri the uri for the namespace; may not be null or empty 165 * @return the interface used to set the value for the property; never null 166 */ 167 RepositoryDefinition<ReturnType> registerNamespace( String prefix, 168 String uri ); 169 } 170 171 private final Map<String, RepositoryDefinition<? extends JcrConfiguration>> repositoryDefinitions = new HashMap<String, RepositoryDefinition<? extends JcrConfiguration>>(); 172 173 /** 174 * Create a new configuration, using a default-constructed {@link ExecutionContext}. 175 */ 176 public JcrConfiguration() { 177 super(); 178 } 179 180 /** 181 * Create a new configuration using the supplied {@link ExecutionContext}. 182 * 183 * @param context the execution context 184 * @throws IllegalArgumentException if the path is null or empty 185 */ 186 public JcrConfiguration( ExecutionContext context ) { 187 super(context); 188 } 189 190 /** 191 * {@inheritDoc} 192 * 193 * @throws IOException 194 * @throws SAXException 195 * @see org.jboss.dna.repository.DnaConfiguration#loadFrom(java.lang.String) 196 */ 197 @Override 198 public JcrConfiguration loadFrom( String pathToFile ) throws IOException, SAXException { 199 super.loadFrom(pathToFile); 200 return this; 201 } 202 203 /** 204 * {@inheritDoc} 205 * 206 * @see org.jboss.dna.repository.DnaConfiguration#loadFrom(java.lang.String, java.lang.String) 207 */ 208 @Override 209 public JcrConfiguration loadFrom( String pathToConfigurationFile, 210 String path ) throws IOException, SAXException { 211 super.loadFrom(pathToConfigurationFile, path); 212 return this; 213 } 214 215 /** 216 * {@inheritDoc} 217 * 218 * @see org.jboss.dna.repository.DnaConfiguration#loadFrom(java.io.File) 219 */ 220 @Override 221 public JcrConfiguration loadFrom( File configurationFile ) throws IOException, SAXException { 222 super.loadFrom(configurationFile); 223 return this; 224 } 225 226 /** 227 * {@inheritDoc} 228 * 229 * @see org.jboss.dna.repository.DnaConfiguration#loadFrom(java.io.File, java.lang.String) 230 */ 231 @Override 232 public JcrConfiguration loadFrom( File configurationFile, 233 String path ) throws IOException, SAXException { 234 super.loadFrom(configurationFile, path); 235 return this; 236 } 237 238 /** 239 * {@inheritDoc} 240 * 241 * @see org.jboss.dna.repository.DnaConfiguration#loadFrom(java.net.URL) 242 */ 243 @Override 244 public JcrConfiguration loadFrom( URL urlToConfigurationFile ) throws IOException, SAXException { 245 super.loadFrom(urlToConfigurationFile); 246 return this; 247 } 248 249 /** 250 * {@inheritDoc} 251 * 252 * @see org.jboss.dna.repository.DnaConfiguration#loadFrom(java.net.URL, java.lang.String) 253 */ 254 @Override 255 public JcrConfiguration loadFrom( URL urlToConfigurationFile, 256 String path ) throws IOException, SAXException { 257 super.loadFrom(urlToConfigurationFile, path); 258 return this; 259 } 260 261 /** 262 * {@inheritDoc} 263 * 264 * @see org.jboss.dna.repository.DnaConfiguration#loadFrom(java.io.InputStream) 265 */ 266 @Override 267 public JcrConfiguration loadFrom( InputStream configurationFileInputStream ) throws IOException, SAXException { 268 super.loadFrom(configurationFileInputStream); 269 return this; 270 } 271 272 /** 273 * {@inheritDoc} 274 * 275 * @see org.jboss.dna.repository.DnaConfiguration#loadFrom(java.io.InputStream, java.lang.String) 276 */ 277 @Override 278 public JcrConfiguration loadFrom( InputStream configurationFileInputStream, 279 String path ) throws IOException, SAXException { 280 super.loadFrom(configurationFileInputStream, path); 281 return this; 282 } 283 284 /** 285 * {@inheritDoc} 286 * 287 * @see org.jboss.dna.repository.DnaConfiguration#loadFrom(org.jboss.dna.graph.connector.RepositorySource) 288 */ 289 @Override 290 public JcrConfiguration loadFrom( RepositorySource source ) { 291 super.loadFrom(source); 292 return this; 293 } 294 295 /** 296 * {@inheritDoc} 297 * 298 * @see org.jboss.dna.repository.DnaConfiguration#loadFrom(org.jboss.dna.graph.connector.RepositorySource, java.lang.String) 299 */ 300 @Override 301 public JcrConfiguration loadFrom( RepositorySource source, 302 String workspaceName ) { 303 super.loadFrom(source, workspaceName); 304 return this; 305 } 306 307 /** 308 * {@inheritDoc} 309 * 310 * @see org.jboss.dna.repository.DnaConfiguration#loadFrom(org.jboss.dna.graph.connector.RepositorySource, java.lang.String, 311 * java.lang.String) 312 */ 313 @Override 314 public JcrConfiguration loadFrom( RepositorySource source, 315 String workspaceName, 316 String pathInWorkspace ) { 317 super.loadFrom(source, workspaceName, pathInWorkspace); 318 return this; 319 } 320 321 /** 322 * {@inheritDoc} 323 * 324 * @see org.jboss.dna.repository.DnaConfiguration#and() 325 */ 326 @Override 327 public JcrConfiguration and() { 328 return this; 329 } 330 331 /** 332 * {@inheritDoc} 333 * 334 * @see org.jboss.dna.repository.DnaConfiguration#withClassLoaderFactory(org.jboss.dna.common.component.ClassLoaderFactory) 335 */ 336 @Override 337 public JcrConfiguration withClassLoaderFactory( ClassLoaderFactory classLoaderFactory ) { 338 super.withClassLoaderFactory(classLoaderFactory); 339 return this; 340 } 341 342 /** 343 * {@inheritDoc} 344 * 345 * @see org.jboss.dna.repository.DnaConfiguration#mimeTypeDetector(java.lang.String) 346 */ 347 @Override 348 public MimeTypeDetectorDefinition<JcrConfiguration> mimeTypeDetector( String name ) { 349 return mimeTypeDetectorDefinition(this, name); 350 } 351 352 /** 353 * {@inheritDoc} 354 * 355 * @see org.jboss.dna.repository.DnaConfiguration#repositorySource(java.lang.String) 356 */ 357 @Override 358 public RepositorySourceDefinition<JcrConfiguration> repositorySource( String name ) { 359 return repositorySourceDefinition(this, name); 360 } 361 362 /** 363 * {@inheritDoc} 364 * 365 * @see org.jboss.dna.repository.DnaConfiguration#sequencer(java.lang.String) 366 */ 367 @Override 368 public SequencerDefinition<JcrConfiguration> sequencer( String name ) { 369 return sequencerDefinition(this, name); 370 } 371 372 /** 373 * Obtain or create a definition for the {@link javax.jcr.Repository JCR Repository} with the supplied name or identifier. A 374 * new definition will be created if there currently is no sequencer defined with the supplied name. 375 * 376 * @param name the name or identifier of the sequencer 377 * @return the details of the sequencer definition; never null 378 */ 379 public RepositoryDefinition<JcrConfiguration> repository( String name ) { 380 return repositoryDefinition(this, name); 381 } 382 383 /** 384 * Get the list of sequencer definitions. 385 * 386 * @return the unmodifiable set of definitions; never null but possibly empty if there are no definitions 387 */ 388 public Set<RepositoryDefinition<JcrConfiguration>> repositories() { 389 // Get the children under the 'dna:mimeTypeDetectors' node ... 390 Set<String> names = getNamesOfComponentsUnder(DnaLexicon.REPOSITORIES); 391 names.addAll(this.repositoryDefinitions.keySet()); 392 Set<RepositoryDefinition<JcrConfiguration>> results = new HashSet<RepositoryDefinition<JcrConfiguration>>(); 393 for (String name : names) { 394 results.add(repository(name)); 395 } 396 return Collections.unmodifiableSet(results); 397 } 398 399 /** 400 * {@inheritDoc} 401 * 402 * @see org.jboss.dna.repository.DnaConfiguration#save() 403 */ 404 @Override 405 public JcrConfiguration save() { 406 super.save(); 407 this.repositoryDefinitions.clear(); 408 return this; 409 } 410 411 /** 412 * {@inheritDoc} 413 * 414 * @see org.jboss.dna.repository.DnaConfiguration#build() 415 */ 416 @Override 417 public JcrEngine build() { 418 save(); 419 return new JcrEngine(getExecutionContext(), getConfigurationDefinition()); 420 } 421 422 /** 423 * Utility method to construct a definition object for the repository with the supplied name and return type. 424 * 425 * @param <ReturnType> the type of the return object 426 * @param returnObject the return object 427 * @param name the name of the repository 428 * @return the definition for the repository 429 */ 430 @SuppressWarnings( "unchecked" ) 431 protected <ReturnType extends JcrConfiguration> RepositoryDefinition<ReturnType> repositoryDefinition( ReturnType returnObject, 432 String name ) { 433 RepositoryDefinition<ReturnType> definition = (RepositoryDefinition<ReturnType>)repositoryDefinitions.get(name); 434 if (definition == null) { 435 definition = new RepositoryBuilder<ReturnType>(returnObject, changes(), path(), DnaLexicon.REPOSITORIES, name(name)); 436 repositoryDefinitions.put(name, definition); 437 } 438 return definition; 439 } 440 441 protected class RepositoryBuilder<ReturnType> extends GraphReturnable<ReturnType, RepositoryDefinition<ReturnType>> 442 implements RepositoryDefinition<ReturnType> { 443 private final EnumMap<JcrRepository.Option, String> optionValues = new EnumMap<Option, String>(Option.class); 444 445 protected RepositoryBuilder( ReturnType returnObject, 446 Graph.Batch batch, 447 Path path, 448 Name... names ) { 449 super(returnObject, batch, path, names); 450 // Load the current options ... 451 try { 452 Path optionsPath = context.getValueFactories().getPathFactory().create(path, DnaLexicon.OPTIONS); 453 Subgraph options = batch.getGraph().getSubgraphOfDepth(2).at(optionsPath); 454 for (Location optionChild : options.getRoot().getChildren()) { 455 Node option = options.getNode(optionChild); 456 Property property = option.getProperty(DnaLexicon.VALUE); 457 if (property != null && property.isEmpty()) { 458 try { 459 Option key = Option.findOption(optionChild.getPath() 460 .getLastSegment() 461 .getString(context.getNamespaceRegistry())); 462 String value = context.getValueFactories().getStringFactory().create(property.getFirstValue()); 463 optionValues.put(key, value); 464 } catch (IllegalArgumentException e) { 465 // the key is not valid, so skip it ... 466 } 467 } 468 } 469 } catch (PathNotFoundException e) { 470 // No current options 471 } 472 } 473 474 @Override 475 protected RepositoryDefinition<ReturnType> thisType() { 476 return this; 477 } 478 479 public RepositoryDefinition<ReturnType> setSource( String sourceName ) { 480 setProperty(DnaLexicon.SOURCE_NAME, sourceName); 481 return this; 482 } 483 484 public String getSource() { 485 Property property = getProperty(DnaLexicon.SOURCE_NAME); 486 if (property != null && !property.isEmpty()) { 487 return context.getValueFactories().getStringFactory().create(property.getFirstValue()); 488 } 489 return null; 490 } 491 492 public RepositoryDefinition<ReturnType> setOption( JcrRepository.Option option, 493 String value ) { 494 CheckArg.isNotNull(option, "option"); 495 CheckArg.isNotNull(value, "value"); 496 createIfMissing(DnaLexicon.OPTIONS, option.name()).with(DnaLexicon.VALUE, value.trim()).and(); 497 optionValues.put(option, value); 498 return this; 499 } 500 501 public String getOption( Option option ) { 502 CheckArg.isNotNull(option, "option"); 503 return optionValues.get(option); 504 } 505 506 public RepositoryDefinition<ReturnType> registerNamespace( String prefix, 507 String uri ) { 508 CheckArg.isNotEmpty(prefix, "prefix"); 509 CheckArg.isNotEmpty(uri, "uri"); 510 prefix = prefix.trim(); 511 uri = uri.trim(); 512 createIfMissing(DnaLexicon.NAMESPACES, prefix).with(DnaLexicon.URI, uri).and(); 513 return this; 514 } 515 516 public RepositoryDefinition<ReturnType> addNodeTypes( String pathToCndFile ) { 517 CheckArg.isNotEmpty(pathToCndFile, "pathToCndFile"); 518 return addNodeTypes(new File(pathToCndFile)); 519 } 520 521 public RepositoryDefinition<ReturnType> addNodeTypes( File file ) { 522 CheckArg.isNotNull(file, "file"); 523 if (file.exists() && file.canRead()) { 524 CndImporter importer = createCndImporter(); 525 try { 526 Set<Namespace> namespacesBefore = batch.getGraph().getContext().getNamespaceRegistry().getNamespaces(); 527 importer.importFrom(file, getProblems()); 528 529 // Record any new namespaces added by this import ... 530 registerNewNamespaces(namespacesBefore); 531 } catch (IOException e) { 532 throw new DnaConfigurationException(e); 533 } 534 return this; 535 } 536 throw new DnaConfigurationException(JcrI18n.fileDoesNotExist.text(file.getPath())); 537 } 538 539 public RepositoryDefinition<ReturnType> addNodeTypes( URL url ) { 540 CheckArg.isNotNull(url, "url"); 541 // Obtain the stream ... 542 InputStream stream = null; 543 boolean foundError = false; 544 try { 545 Set<Namespace> namespacesBefore = batch.getGraph().getContext().getNamespaceRegistry().getNamespaces(); 546 stream = url.openStream(); 547 CndImporter importer = createCndImporter(); 548 importer.importFrom(stream, getProblems(), url.toString()); 549 550 // Record any new namespaces added by this import ... 551 registerNewNamespaces(namespacesBefore); 552 } catch (IOException e) { 553 foundError = true; 554 throw new DnaConfigurationException(e); 555 } finally { 556 if (stream != null) { 557 try { 558 stream.close(); 559 } catch (IOException e) { 560 if (!foundError) { 561 throw new DnaConfigurationException(e); 562 } 563 } 564 } 565 } 566 return this; 567 } 568 569 public RepositoryDefinition<ReturnType> addNodeTypes( InputStream cndContent ) { 570 CndImporter importer = createCndImporter(); 571 try { 572 Set<Namespace> namespacesBefore = batch.getGraph().getContext().getNamespaceRegistry().getNamespaces(); 573 importer.importFrom(cndContent, getProblems(), "stream"); 574 575 // Record any new namespaces added by this import ... 576 registerNewNamespaces(namespacesBefore); 577 } catch (IOException e) { 578 throw new DnaConfigurationException(e); 579 } 580 return this; 581 } 582 583 protected void registerNewNamespaces( Set<Namespace> namespacesBefore ) { 584 Set<Namespace> namespacesAfter = batch.getGraph().getContext().getNamespaceRegistry().getNamespaces(); 585 Set<Namespace> newNamespaces = new HashSet<Namespace>(namespacesAfter); 586 newNamespaces.removeAll(namespacesBefore); 587 for (Namespace namespace : newNamespaces) { 588 registerNamespace(namespace.getPrefix(), namespace.getNamespaceUri()); 589 } 590 } 591 592 protected CndImporter createCndImporter() { 593 // The node types will be loaded into 'dna:repositories/{repositoryName}/dna:nodeTypes/' ... 594 Path nodeTypesPath = subpath(DnaLexicon.NODE_TYPES); 595 createIfMissing(DnaLexicon.NODE_TYPES).and(); 596 597 // Now set up the destination, but make it so that ... 598 Destination destination = new GraphBatchDestination(batch, true); // will NOT be executed 599 600 // And create the importer that will load the CND content into the repository ... 601 return new CndImporter(destination, nodeTypesPath); 602 } 603 } 604 605 }