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 }