001 /*
002 * JBoss, Home of Professional Open Source.
003 * Copyright 2008, Red Hat Middleware LLC, and individual contributors
004 * as indicated by the @author tags. See the copyright.txt file in the
005 * distribution for a full listing of individual contributors.
006 *
007 * This is free software; you can redistribute it and/or modify it
008 * under the terms of the GNU Lesser General Public License as
009 * published by the Free Software Foundation; either version 2.1 of
010 * the License, or (at your option) any later version.
011 *
012 * This software is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * You should have received a copy of the GNU Lesser General Public
018 * License along with this software; if not, write to the Free
019 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
021 */
022 package org.jboss.dna.graph.util;
023
024 import java.io.File;
025 import java.io.IOException;
026 import java.io.InputStream;
027 import java.net.MalformedURLException;
028 import java.net.URI;
029 import java.util.ArrayList;
030 import java.util.Collection;
031 import java.util.HashMap;
032 import java.util.HashSet;
033 import java.util.Iterator;
034 import java.util.List;
035 import java.util.Map;
036 import java.util.Set;
037 import net.jcip.annotations.Immutable;
038 import org.jboss.dna.common.i18n.I18n;
039 import org.jboss.dna.common.monitor.ProgressMonitor;
040 import org.jboss.dna.common.monitor.SimpleProgressMonitor;
041 import org.jboss.dna.common.util.CheckArg;
042 import org.jboss.dna.common.util.Logger;
043 import org.jboss.dna.graph.ExecutionContext;
044 import org.jboss.dna.graph.GraphI18n;
045 import org.jboss.dna.graph.commands.CompositeCommand;
046 import org.jboss.dna.graph.commands.GraphCommand;
047 import org.jboss.dna.graph.commands.NodeConflictBehavior;
048 import org.jboss.dna.graph.commands.basic.BasicCreateNodeCommand;
049 import org.jboss.dna.graph.commands.basic.BasicGraphCommand;
050 import org.jboss.dna.graph.connectors.RepositoryConnection;
051 import org.jboss.dna.graph.connectors.RepositoryConnectionFactory;
052 import org.jboss.dna.graph.connectors.RepositorySource;
053 import org.jboss.dna.graph.connectors.RepositorySourceException;
054 import org.jboss.dna.graph.properties.Name;
055 import org.jboss.dna.graph.properties.NameFactory;
056 import org.jboss.dna.graph.properties.NamespaceRegistry;
057 import org.jboss.dna.graph.properties.Path;
058 import org.jboss.dna.graph.properties.PathFactory;
059 import org.jboss.dna.graph.properties.Property;
060 import org.jboss.dna.graph.properties.PropertyFactory;
061 import org.jboss.dna.graph.properties.Reference;
062 import org.jboss.dna.graph.properties.ValueFactories;
063 import org.jboss.dna.graph.properties.ValueFactory;
064 import org.jboss.dna.graph.properties.ValueFormatException;
065 import org.jboss.dna.graph.sequencers.SequencerContext;
066 import org.jboss.dna.graph.sequencers.SequencerOutput;
067 import org.jboss.dna.graph.sequencers.StreamSequencer;
068 import org.jboss.dna.graph.xml.DnaXmlLexicon;
069 import org.jboss.dna.graph.xml.XmlSequencer;
070
071 /**
072 * @author Randall Hauch
073 */
074 public class GraphImporter {
075
076 public interface ImportSpecification {
077 /**
078 * Specify the location where the content is to be imported, and then perform the import. This is equivalent to calling
079 * <code>{@link #into(String, Path) into(sourceName,rootPath)}</code>.
080 *
081 * @param sourceName the name of the source into which the content is to be imported
082 * @throws IllegalArgumentException if the <code>uri</code> or path are null
083 * @throws IOException if there is a problem reading the content
084 * @throws RepositorySourceException if there is a problem while writing the content to the {@link RepositorySource
085 * repository source}
086 */
087 void into( String sourceName ) throws IOException, RepositorySourceException;
088
089 /**
090 * Specify the location where the content is to be imported, and then perform the import.
091 *
092 * @param sourceName the name of the source into which the content is to be imported
093 * @param pathInSource the path in the {@link RepositorySource repository source} named <code>sourceName</code> where the
094 * content is to be written; may not be null
095 * @throws IllegalArgumentException if the <code>uri</code> or path are null
096 * @throws IOException if there is a problem reading the content
097 * @throws RepositorySourceException if there is a problem while writing the content to the {@link RepositorySource
098 * repository source}
099 */
100 void into( String sourceName,
101 Path pathInSource ) throws IOException, RepositorySourceException;
102 }
103
104 @Immutable
105 protected abstract class ImportedContentUsingSequencer implements ImportSpecification {
106 private final StreamSequencer sequencer;
107
108 protected ImportedContentUsingSequencer( StreamSequencer sequencer ) {
109 this.sequencer = sequencer;
110 }
111
112 protected StreamSequencer getSequencer() {
113 return this.sequencer;
114 }
115
116 protected NodeConflictBehavior getConflictBehavior() {
117 return NodeConflictBehavior.UPDATE;
118 }
119
120 /**
121 * {@inheritDoc}
122 *
123 * @see org.jboss.dna.graph.util.GraphImporter.ImportSpecification#into(java.lang.String)
124 */
125 public void into( String sourceName ) throws IOException, RepositorySourceException {
126 Path root = getContext().getValueFactories().getPathFactory().createRootPath();
127 into(sourceName, root);
128 }
129 }
130
131 @Immutable
132 protected class UriImportedContent extends ImportedContentUsingSequencer {
133 private final URI uri;
134 private final String mimeType;
135
136 protected UriImportedContent( StreamSequencer sequencer,
137 URI uri,
138 String mimeType ) {
139 super(sequencer);
140 this.uri = uri;
141 this.mimeType = mimeType;
142 }
143
144 /**
145 * @return mimeType
146 */
147 public String getMimeType() {
148 return mimeType;
149 }
150
151 /**
152 * @return uri
153 */
154 public URI getUri() {
155 return uri;
156 }
157
158 /**
159 * {@inheritDoc}
160 *
161 * @see org.jboss.dna.graph.util.GraphImporter.ImportSpecification#into(java.lang.String,
162 * org.jboss.dna.graph.properties.Path)
163 */
164 public void into( String sourceName,
165 Path pathInSource ) throws IOException, RepositorySourceException {
166 CheckArg.isNotNull(sourceName, "sourceName");
167 CheckArg.isNotNull(pathInSource, "pathInSource");
168 importWithSequencer(getSequencer(), uri, mimeType, sourceName, pathInSource, getConflictBehavior());
169 }
170 }
171
172 private final RepositoryConnectionFactory sources;
173 private final ExecutionContext context;
174
175 public GraphImporter( RepositoryConnectionFactory sources,
176 ExecutionContext context ) {
177 CheckArg.isNotNull(sources, "sources");
178 CheckArg.isNotNull(context, "context");
179 this.sources = sources;
180 this.context = context;
181 }
182
183 /**
184 * Get the context in which the importer will be executed.
185 *
186 * @return the execution context; never null
187 */
188 public ExecutionContext getContext() {
189 return this.context;
190 }
191
192 /**
193 * Import the content from the XML file at the supplied URI, specifying on the returned {@link ImportSpecification} where the
194 * content is to be imported.
195 *
196 * @param uri the URI where the importer can read the content that is to be imported
197 * @return the object that should be used to specify into which the content is to be imported
198 * @throws IllegalArgumentException if the <code>uri</code> or destination path are null
199 */
200 public ImportSpecification importXml( URI uri ) {
201 CheckArg.isNotNull(uri, "uri");
202
203 // Create the sequencer ...
204 StreamSequencer sequencer = new XmlSequencer();
205 return new UriImportedContent(sequencer, uri, "text/xml");
206 }
207
208 /**
209 * Import the content from the XML file at the supplied file location, specifying on the returned {@link ImportSpecification}
210 * where the content is to be imported.
211 *
212 * @param pathToFile the path to the XML file that should be imported.
213 * @return the object that should be used to specify into which the content is to be imported
214 * @throws IllegalArgumentException if the <code>uri</code> or destination path are null
215 */
216 public ImportSpecification importXml( String pathToFile ) {
217 CheckArg.isNotNull(pathToFile, "pathToFile");
218 return importXml(new File(pathToFile).toURI());
219 }
220
221 /**
222 * Import the content from the supplied XML file, specifying on the returned {@link ImportSpecification} where the content is
223 * to be imported.
224 *
225 * @param file the XML file that should be imported.
226 * @return the object that should be used to specify into which the content is to be imported
227 * @throws IllegalArgumentException if the <code>uri</code> or destination path are null
228 */
229 public ImportSpecification importXml( File file ) {
230 CheckArg.isNotNull(file, "file");
231 return importXml(file.toURI());
232 }
233
234 /**
235 * Read the content from the supplied URI and import into the repository at the supplied location.
236 *
237 * @param uri the URI where the importer can read the content that is to be imported
238 * @param sourceName the name of the source into which the content is to be imported
239 * @param destinationPathInSource the path in the {@link RepositorySource repository source} where the content is to be
240 * written; may not be null
241 * @throws IllegalArgumentException if the <code>uri</code> or destination path are null
242 * @throws IOException if there is a problem reading the content
243 * @throws RepositorySourceException if there is a problem while writing the content to the {@link RepositorySource repository
244 * source}
245 */
246 public void importXml( URI uri,
247 String sourceName,
248 Path destinationPathInSource ) throws IOException, RepositorySourceException {
249 CheckArg.isNotNull(uri, "uri");
250 CheckArg.isNotNull(destinationPathInSource, "destinationPathInSource");
251
252 // Create the sequencer ...
253 StreamSequencer sequencer = new XmlSequencer();
254 importWithSequencer(sequencer, uri, "text/xml", sourceName, destinationPathInSource, NodeConflictBehavior.UPDATE);
255 }
256
257 /**
258 * Use the supplied sequencer to read the content at the given URI (with the specified MIME type) and write that content to
259 * the {@link RepositorySource repository source} into the specified location.
260 *
261 * @param sequencer the sequencer that should be used; may not be null
262 * @param contentUri the URI where the content can be found; may not be null
263 * @param mimeType the MIME type for the content; may not be null
264 * @param sourceName the name of the source into which the content is to be imported
265 * @param destinationPathInSource the path in the {@link RepositorySource repository source} where the content is to be
266 * written; may not be null
267 * @param conflictBehavior the behavior when a node is to be created when an existing node already exists; defaults to
268 * {@link NodeConflictBehavior#UPDATE} if null
269 * @throws IOException if there is a problem reading the content
270 * @throws RepositorySourceException if there is a problem while writing the content to the {@link RepositorySource repository
271 * source}
272 */
273 protected void importWithSequencer( StreamSequencer sequencer,
274 URI contentUri,
275 String mimeType,
276 String sourceName,
277 Path destinationPathInSource,
278 NodeConflictBehavior conflictBehavior ) throws IOException, RepositorySourceException {
279 assert sequencer != null;
280 assert contentUri != null;
281 assert mimeType != null;
282 assert sourceName != null;
283 assert destinationPathInSource != null;
284 conflictBehavior = conflictBehavior != null ? conflictBehavior : NodeConflictBehavior.UPDATE;
285
286 // Get the input path by creating from the URI, in case the URI is a valid path ...
287 Path inputPath = extractInputPathFrom(contentUri);
288 assert inputPath != null;
289
290 // Now create the importer context ...
291 PropertyFactory propertyFactory = getContext().getPropertyFactory();
292 NameFactory nameFactory = getContext().getValueFactories().getNameFactory();
293 Set<Property> inputProperties = new HashSet<Property>();
294 inputProperties.add(propertyFactory.create(nameFactory.create("jcr:mimeType"), mimeType));
295 ImporterContext importerContext = new ImporterContext(inputPath, inputProperties, "text/xml");
296
297 // Now run the sequencer ...
298 String activity = GraphI18n.errorImportingContent.text(destinationPathInSource, contentUri);
299 ProgressMonitor progressMonitor = new SimpleProgressMonitor(activity);
300 ImporterCommands commands = new ImporterCommands(destinationPathInSource, conflictBehavior);
301 InputStream stream = null;
302 try {
303 stream = contentUri.toURL().openStream();
304 sequencer.sequence(stream, commands, importerContext, progressMonitor);
305 } catch (MalformedURLException err) {
306 throw new IOException(err.getMessage());
307 } finally {
308 if (stream != null) {
309 try {
310 stream.close();
311 } catch (IOException e) {
312 I18n msg = GraphI18n.errorImportingContent;
313 context.getLogger(getClass()).error(e, msg, mimeType, contentUri);
314 }
315 }
316 }
317
318 // Now execute the commands against the repository ...
319 RepositoryConnection connection = null;
320 try {
321 connection = sources.createConnection(sourceName);
322 if (connection == null) {
323 I18n msg = GraphI18n.unableToFindRepositorySourceWithName;
324 throw new RepositorySourceException(msg.text(sourceName));
325 }
326 connection.execute(context, commands);
327 } finally {
328 if (connection != null) {
329 try {
330 connection.close();
331 } catch (RepositorySourceException e) {
332 I18n msg = GraphI18n.errorImportingContent;
333 context.getLogger(getClass()).error(e, msg, mimeType, contentUri);
334 }
335 }
336 }
337 }
338
339 /**
340 * @param contentUri
341 * @return the input path
342 */
343 protected Path extractInputPathFrom( URI contentUri ) {
344 try {
345 return getContext().getValueFactories().getPathFactory().create(contentUri);
346 } catch (ValueFormatException e) {
347 // Get the last component of the URI, and use it to create the input path ...
348 String path = contentUri.getPath();
349 return getContext().getValueFactories().getPathFactory().create(path);
350 }
351 }
352
353 protected class SingleRepositorySourceConnectionFactory implements RepositoryConnectionFactory {
354 private final RepositorySource source;
355
356 protected SingleRepositorySourceConnectionFactory( RepositorySource source ) {
357 CheckArg.isNotNull(source, "source");
358 this.source = source;
359 }
360
361 /**
362 * {@inheritDoc}
363 *
364 * @see org.jboss.dna.graph.connectors.RepositoryConnectionFactory#createConnection(java.lang.String)
365 */
366 public RepositoryConnection createConnection( String sourceName ) throws RepositorySourceException {
367 if (source.getName().equals(sourceName)) {
368 return source.getConnection();
369 }
370 return null;
371 }
372 }
373
374 protected class ImporterCommands extends BasicGraphCommand implements SequencerOutput, CompositeCommand {
375 private final List<GraphCommand> commands = new ArrayList<GraphCommand>();
376 private final Map<Path, BasicCreateNodeCommand> createNodeCommands = new HashMap<Path, BasicCreateNodeCommand>();
377 private final NodeConflictBehavior conflictBehavior;
378 private final Path destinationPath;
379 private final NameFactory nameFactory;
380 private final Name primaryTypeName;
381
382 protected ImporterCommands( Path destinationPath,
383 NodeConflictBehavior conflictBehavior ) {
384 CheckArg.isNotNull(destinationPath, "destinationPath");
385 CheckArg.isNotNull(conflictBehavior, "conflictBehavior");
386 this.conflictBehavior = conflictBehavior;
387 this.destinationPath = destinationPath;
388 this.nameFactory = getContext().getValueFactories().getNameFactory();
389 this.primaryTypeName = this.nameFactory.create("jcr:primaryType");
390 }
391
392 /**
393 * {@inheritDoc}
394 *
395 * @see org.jboss.dna.graph.sequencers.SequencerOutput#getFactories()
396 */
397 public ValueFactories getFactories() {
398 return getContext().getValueFactories();
399 }
400
401 /**
402 * {@inheritDoc}
403 *
404 * @see org.jboss.dna.graph.sequencers.SequencerOutput#getNamespaceRegistry()
405 */
406 public NamespaceRegistry getNamespaceRegistry() {
407 return getContext().getNamespaceRegistry();
408 }
409
410 /**
411 * {@inheritDoc}
412 *
413 * @see org.jboss.dna.graph.sequencers.SequencerOutput#setProperty(java.lang.String, java.lang.String, java.lang.Object[])
414 */
415 public void setProperty( String nodePath,
416 String propertyName,
417 Object... values ) {
418 // Create a command that sets the property ...
419 Path path = getFactories().getPathFactory().create(nodePath);
420 Name name = getFactories().getNameFactory().create(propertyName);
421 setProperty(path, name, values);
422 }
423
424 /**
425 * {@inheritDoc}
426 *
427 * @see org.jboss.dna.graph.sequencers.SequencerOutput#setProperty(org.jboss.dna.graph.properties.Path,
428 * org.jboss.dna.graph.properties.Name, java.lang.Object[])
429 */
430 public void setProperty( Path nodePath,
431 Name propertyName,
432 Object... values ) {
433 // Ignore the property value if the "jcr:primaryType" is "dnaxml:document" ...
434 if (this.primaryTypeName.equals(propertyName) && values.length == 1) {
435 Name typeName = this.nameFactory.create(values[0]);
436 if (DnaXmlLexicon.DOCUMENT.equals(typeName)) return;
437 }
438 PathFactory pathFactory = getFactories().getPathFactory();
439 if (nodePath.isAbsolute()) nodePath.relativeTo(pathFactory.createRootPath());
440 nodePath = pathFactory.create(destinationPath, nodePath).getNormalizedPath();
441 Property property = getContext().getPropertyFactory().create(propertyName, values);
442 BasicCreateNodeCommand command = createNodeCommands.get(nodePath);
443 if (command != null) {
444 // We've already created the node, so find that command and add to it.
445 Collection<Property> properties = command.getProperties();
446 // See if the property was already added and remove it if so
447 Iterator<Property> iter = properties.iterator();
448 while (iter.hasNext()) {
449 Property existingProperty = iter.next();
450 if (existingProperty.getName().equals(propertyName)) {
451 iter.remove();
452 break;
453 }
454 }
455 command.getProperties().add(property);
456 } else {
457 // We haven't created the node yet (and we're assuming that we need to), so create the node
458 List<Property> properties = new ArrayList<Property>();
459 properties.add(property);
460 command = new BasicCreateNodeCommand(nodePath, properties, conflictBehavior);
461 createNodeCommands.put(nodePath, command);
462 commands.add(command);
463 }
464 }
465
466 /**
467 * {@inheritDoc}
468 *
469 * @see org.jboss.dna.graph.sequencers.SequencerOutput#setReference(java.lang.String, java.lang.String,
470 * java.lang.String[])
471 */
472 public void setReference( String nodePath,
473 String propertyName,
474 String... paths ) {
475 Path path = getFactories().getPathFactory().create(nodePath);
476 Name name = getFactories().getNameFactory().create(propertyName);
477 // Create an array of reference values ...
478 ValueFactory<Reference> factory = getFactories().getReferenceFactory();
479 Object[] values = new Object[paths.length];
480 int i = 0;
481 for (String referencedPath : paths) {
482 values[i++] = factory.create(referencedPath);
483 }
484 setProperty(path, name, values);
485 }
486
487 /**
488 * {@inheritDoc}
489 *
490 * @see java.lang.Iterable#iterator()
491 */
492 public Iterator<GraphCommand> iterator() {
493 return this.commands.iterator();
494 }
495
496 }
497
498 protected class ImporterContext implements SequencerContext {
499
500 private final Path inputPath;
501 private final Set<Property> inputProperties;
502 private final String mimeType;
503
504 protected ImporterContext( Path inputPath,
505 Set<Property> inputProperties,
506 String mimeType ) {
507 this.inputPath = inputPath;
508 this.inputProperties = inputProperties;
509 this.mimeType = mimeType;
510 }
511
512 /**
513 * {@inheritDoc}
514 *
515 * @see org.jboss.dna.graph.sequencers.SequencerContext#getFactories()
516 */
517 public ValueFactories getFactories() {
518 return getContext().getValueFactories();
519 }
520
521 /**
522 * {@inheritDoc}
523 *
524 * @see org.jboss.dna.graph.sequencers.SequencerContext#getInputPath()
525 */
526 public Path getInputPath() {
527 return inputPath;
528 }
529
530 /**
531 * {@inheritDoc}
532 *
533 * @see org.jboss.dna.graph.sequencers.SequencerContext#getInputProperties()
534 */
535 public Set<Property> getInputProperties() {
536 return inputProperties;
537 }
538
539 /**
540 * {@inheritDoc}
541 *
542 * @see org.jboss.dna.graph.sequencers.SequencerContext#getInputProperty(org.jboss.dna.graph.properties.Name)
543 */
544 public Property getInputProperty( Name name ) {
545 for (Property property : inputProperties) {
546 if (property.getName().equals(name)) return property;
547 }
548 return null;
549 }
550
551 /**
552 * {@inheritDoc}
553 *
554 * @see org.jboss.dna.graph.sequencers.SequencerContext#getLogger(java.lang.Class)
555 */
556 public Logger getLogger( Class<?> clazz ) {
557 return getContext().getLogger(clazz);
558 }
559
560 /**
561 * {@inheritDoc}
562 *
563 * @see org.jboss.dna.graph.sequencers.SequencerContext#getLogger(java.lang.String)
564 */
565 public Logger getLogger( String name ) {
566 return getContext().getLogger(name);
567 }
568
569 /**
570 * {@inheritDoc}
571 *
572 * @see org.jboss.dna.graph.sequencers.SequencerContext#getMimeType()
573 */
574 public String getMimeType() {
575 return mimeType;
576 }
577
578 /**
579 * {@inheritDoc}
580 *
581 * @see org.jboss.dna.graph.sequencers.SequencerContext#getNamespaceRegistry()
582 */
583 public NamespaceRegistry getNamespaceRegistry() {
584 return getContext().getNamespaceRegistry();
585 }
586
587 }
588
589 }