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 }