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.example.dna.sequencer; 025 026 import java.io.File; 027 import java.net.URL; 028 import java.util.ArrayList; 029 import java.util.Calendar; 030 import java.util.List; 031 import java.util.Map; 032 import java.util.Properties; 033 import java.util.TreeMap; 034 import java.util.concurrent.TimeUnit; 035 import javax.jcr.Credentials; 036 import javax.jcr.Node; 037 import javax.jcr.NodeIterator; 038 import javax.jcr.PathNotFoundException; 039 import javax.jcr.Property; 040 import javax.jcr.PropertyIterator; 041 import javax.jcr.Repository; 042 import javax.jcr.RepositoryException; 043 import javax.jcr.Session; 044 import javax.jcr.SimpleCredentials; 045 import javax.jcr.Value; 046 import javax.jcr.ValueFormatException; 047 import javax.jcr.observation.Event; 048 import org.apache.jackrabbit.api.JackrabbitNodeTypeManager; 049 import org.apache.jackrabbit.core.TransientRepository; 050 import org.jboss.dna.common.SystemFailureException; 051 import org.jboss.dna.repository.observation.ObservationService; 052 import org.jboss.dna.repository.sequencer.SequencerConfig; 053 import org.jboss.dna.repository.sequencer.SequencingService; 054 import org.jboss.dna.repository.util.JcrExecutionContext; 055 import org.jboss.dna.repository.util.JcrTools; 056 import org.jboss.dna.repository.util.SessionFactory; 057 import org.jboss.dna.repository.util.SimpleSessionFactory; 058 059 /** 060 * @author Randall Hauch 061 */ 062 public class SequencingClient { 063 064 public static final String DEFAULT_JACKRABBIT_CONFIG_PATH = "jackrabbitConfig.xml"; 065 public static final String DEFAULT_WORKING_DIRECTORY = "repositoryData"; 066 public static final String DEFAULT_REPOSITORY_NAME = "repo"; 067 public static final String DEFAULT_WORKSPACE_NAME = "default"; 068 public static final String DEFAULT_USERNAME = "jsmith"; 069 public static final char[] DEFAULT_PASSWORD = "secret".toCharArray(); 070 071 public static void main( String[] args ) { 072 SequencingClient client = new SequencingClient(); 073 client.setRepositoryInformation(DEFAULT_REPOSITORY_NAME, DEFAULT_WORKSPACE_NAME, DEFAULT_USERNAME, DEFAULT_PASSWORD); 074 client.setUserInterface(new ConsoleInput(client)); 075 } 076 077 private String repositoryName; 078 private String workspaceName; 079 private String username; 080 private char[] password; 081 private String jackrabbitConfigPath; 082 private String workingDirectory; 083 private Session keepAliveSession; 084 private Repository repository; 085 private SequencingService sequencingService; 086 private ObservationService observationService; 087 private UserInterface userInterface; 088 private JcrExecutionContext executionContext; 089 090 public SequencingClient() { 091 setJackrabbitConfigPath(DEFAULT_JACKRABBIT_CONFIG_PATH); 092 setWorkingDirectory(DEFAULT_WORKING_DIRECTORY); 093 setRepositoryInformation(DEFAULT_REPOSITORY_NAME, DEFAULT_WORKSPACE_NAME, DEFAULT_USERNAME, DEFAULT_PASSWORD); 094 } 095 096 protected void setWorkingDirectory( String workingDirectoryPath ) { 097 this.workingDirectory = workingDirectoryPath != null ? workingDirectoryPath : DEFAULT_WORKING_DIRECTORY; 098 } 099 100 protected void setJackrabbitConfigPath( String jackrabbitConfigPath ) { 101 this.jackrabbitConfigPath = jackrabbitConfigPath != null ? jackrabbitConfigPath : DEFAULT_JACKRABBIT_CONFIG_PATH; 102 } 103 104 protected void setRepositoryInformation( String repositoryName, 105 String workspaceName, 106 String username, 107 char[] password ) { 108 if (this.repository != null) { 109 throw new IllegalArgumentException("Unable to set repository information when repository is already running"); 110 } 111 this.repositoryName = repositoryName != null ? repositoryName : DEFAULT_REPOSITORY_NAME; 112 this.workspaceName = workspaceName != null ? workspaceName : DEFAULT_WORKSPACE_NAME; 113 this.username = username; 114 this.password = password; 115 } 116 117 /** 118 * Set the user interface that this client should use. 119 * 120 * @param userInterface 121 */ 122 public void setUserInterface( UserInterface userInterface ) { 123 this.userInterface = userInterface; 124 } 125 126 /** 127 * Start up the JCR repository. This method only operates using the JCR API and Jackrabbit-specific API. 128 * 129 * @throws Exception 130 */ 131 public void startRepository() throws Exception { 132 if (this.repository == null) { 133 try { 134 135 // Load the Jackrabbit configuration ... 136 File configFile = new File(this.jackrabbitConfigPath); 137 if (!configFile.exists()) { 138 throw new SystemFailureException("The Jackrabbit configuration file cannot be found at " 139 + configFile.getAbsoluteFile()); 140 } 141 if (!configFile.canRead()) { 142 throw new SystemFailureException("Unable to read the Jackrabbit configuration file at " 143 + configFile.getAbsoluteFile()); 144 } 145 String pathToConfig = configFile.getAbsolutePath(); 146 147 // Find the directory where the Jackrabbit repository data will be stored ... 148 File workingDirectory = new File(this.workingDirectory); 149 if (workingDirectory.exists()) { 150 if (!workingDirectory.isDirectory()) { 151 throw new SystemFailureException("Unable to create working directory at " 152 + workingDirectory.getAbsolutePath()); 153 } 154 } 155 String workingDirectoryPath = workingDirectory.getAbsolutePath(); 156 157 // Get the Jackrabbit custom node definition (CND) file ... 158 URL cndFile = Thread.currentThread().getContextClassLoader().getResource("jackrabbitNodeTypes.cnd"); 159 160 // Create the Jackrabbit repository instance and establish a session to keep the repository alive ... 161 this.repository = new TransientRepository(pathToConfig, workingDirectoryPath); 162 if (this.username != null) { 163 Credentials credentials = new SimpleCredentials(this.username, this.password); 164 this.keepAliveSession = this.repository.login(credentials, this.workspaceName); 165 } else { 166 this.keepAliveSession = this.repository.login(); 167 } 168 169 try { 170 // Register the node types (only valid the first time) ... 171 JackrabbitNodeTypeManager mgr = (JackrabbitNodeTypeManager)this.keepAliveSession.getWorkspace() 172 .getNodeTypeManager(); 173 mgr.registerNodeTypes(cndFile.openStream(), JackrabbitNodeTypeManager.TEXT_X_JCR_CND); 174 } catch (RepositoryException e) { 175 if (!e.getMessage().contains("already exists")) throw e; 176 } 177 178 } catch (Exception e) { 179 this.repository = null; 180 this.keepAliveSession = null; 181 throw e; 182 } 183 } 184 } 185 186 /** 187 * Shutdown the repository. This method only uses the JCR API. 188 * 189 * @throws Exception 190 */ 191 public void shutdownRepository() throws Exception { 192 if (this.repository != null) { 193 try { 194 this.keepAliveSession.logout(); 195 } finally { 196 this.repository = null; 197 this.keepAliveSession = null; 198 } 199 } 200 } 201 202 /** 203 * Start the DNA services. 204 * 205 * @throws Exception 206 */ 207 public void startDnaServices() throws Exception { 208 if (this.repository == null) { 209 this.startRepository(); 210 } 211 if (this.sequencingService == null) { 212 213 // Create an execution context for the sequencing service. This execution context provides an environment 214 // for the DNA services which knows about the JCR repositories, workspaces, and credentials used to 215 // establish sessions to these workspaces. 216 final String repositoryWorkspaceName = this.repositoryName + "/" + this.workspaceName; 217 SimpleSessionFactory sessionFactory = new SimpleSessionFactory(); 218 sessionFactory.registerRepository(this.repositoryName, this.repository); 219 if (this.username != null) { 220 Credentials credentials = new SimpleCredentials(this.username, this.password); 221 sessionFactory.registerCredentials(repositoryWorkspaceName, credentials); 222 } 223 this.executionContext = new JcrExecutionContext(sessionFactory, repositoryWorkspaceName); 224 225 // Create the sequencing service, passing in the execution context ... 226 this.sequencingService = new SequencingService(); 227 this.sequencingService.setExecutionContext(executionContext); 228 229 // Configure the sequencers. In this example, we only two sequencers that processes image and mp3 files. 230 // So create a configurations. Note that the sequencing service expects the class to be on the thread's current 231 // context 232 // classloader, or if that's null the classloader that loaded the SequencingService class. 233 // 234 // Part of the configuration includes telling DNA which JCR paths should be processed by the sequencer. 235 // These path expressions tell the service that this sequencer should be invoked on the "jcr:data" property 236 // on the "jcr:content" child node of any node uploaded to the repository whose name ends with one of the 237 // supported extensions, and the sequencer should place the generated output metadata in a node with the same name as 238 // the file but immediately below the "/images" node. Path expressions can be fairly complex, and can even 239 // specify that the generated information be placed in a different repository. 240 // 241 // Sequencer configurations can be added before or after the service is started, but here we do it before the service 242 // is running. 243 String name = "Image Sequencer"; 244 String desc = "Sequences image files to extract the characteristics of the image"; 245 String classname = "org.jboss.dna.sequencer.image.ImageMetadataSequencer"; 246 String[] classpath = null; // Use the current classpath 247 String[] pathExpressions = {"//(*.(jpg|jpeg|gif|bmp|pcx|png|iff|ras|pbm|pgm|ppm|psd)[*])/jcr:content[@jcr:data] => /images/$1"}; 248 SequencerConfig imageSequencerConfig = new SequencerConfig(name, desc, classname, classpath, pathExpressions); 249 this.sequencingService.addSequencer(imageSequencerConfig); 250 251 // Set up the MP3 sequencer ... 252 name = "Mp3 Sequencer"; 253 desc = "Sequences mp3 files to extract the id3 tags of the audio file"; 254 classname = "org.jboss.dna.sequencer.mp3.Mp3MetadataSequencer"; 255 String[] mp3PathExpressions = {"//(*.mp3[*])/jcr:content[@jcr:data] => /mp3s/$1"}; 256 SequencerConfig mp3SequencerConfig = new SequencerConfig(name, desc, classname, classpath, mp3PathExpressions); 257 this.sequencingService.addSequencer(mp3SequencerConfig); 258 259 // Set up the MP3 sequencer ... 260 name = "Java Sequencer"; 261 desc = "Sequences java files to extract the characteristics of the java sources"; 262 classname = "org.jboss.dna.sequencer.java.JavaMetadataSequencer"; 263 String[] javaPathExpressions = {"//(*.java[*])/jcr:content[@jcr:data] => /java/$1"}; 264 SequencerConfig javaSequencerConfig = new SequencerConfig(name, desc, classname, classpath, javaPathExpressions); 265 this.sequencingService.addSequencer(javaSequencerConfig); 266 267 // Use the DNA observation service to listen to the JCR repository (or multiple ones), and 268 // then register the sequencing service as a listener to this observation service... 269 this.observationService = new ObservationService(this.executionContext.getSessionFactory()); 270 this.observationService.getAdministrator().start(); 271 this.observationService.addListener(this.sequencingService); 272 this.observationService.monitor(repositoryWorkspaceName, Event.NODE_ADDED | Event.PROPERTY_ADDED 273 | Event.PROPERTY_CHANGED); 274 } 275 // Start up the sequencing service ... 276 this.sequencingService.getAdministrator().start(); 277 } 278 279 /** 280 * Shut down the DNA services. 281 * 282 * @throws Exception 283 */ 284 public void shutdownDnaServices() throws Exception { 285 if (this.sequencingService == null) return; 286 287 // Shut down the service and wait until it's all shut down ... 288 this.sequencingService.getAdministrator().shutdown(); 289 this.sequencingService.getAdministrator().awaitTermination(5, TimeUnit.SECONDS); 290 291 if (this.observationService != null) { 292 // Shut down the observation service ... 293 this.observationService.getAdministrator().shutdown(); 294 this.observationService.getAdministrator().awaitTermination(5, TimeUnit.SECONDS); 295 } 296 } 297 298 /** 299 * Get the sequencing statistics. 300 * 301 * @return the statistics; never null 302 */ 303 public SequencingService.Statistics getStatistics() { 304 return this.sequencingService.getStatistics(); 305 } 306 307 /** 308 * Prompt the user interface for the file to upload into the JCR repository, then upload it using the JCR API. 309 * 310 * @throws Exception 311 */ 312 public void uploadFile() throws Exception { 313 URL url = this.userInterface.getFileToUpload(); 314 // Grab the last segment of the URL path, using it as the filename 315 String filename = url.getPath().replaceAll("([^/]*/)*", ""); 316 String nodePath = this.userInterface.getRepositoryPath("/a/b/" + filename); 317 String mimeType = getMimeType(url); 318 319 // Now use the JCR API to upload the file ... 320 Session session = createSession(); 321 JcrTools tools = this.executionContext.getTools(); 322 try { 323 // Create the node at the supplied path ... 324 Node node = tools.findOrCreateNode(session, nodePath, "nt:folder", "nt:file"); 325 326 // Upload the file to that node ... 327 Node contentNode = tools.findOrCreateChild(session, node, "jcr:content", "nt:resource"); 328 contentNode.setProperty("jcr:mimeType", mimeType); 329 contentNode.setProperty("jcr:lastModified", Calendar.getInstance()); 330 contentNode.setProperty("jcr:data", url.openStream()); 331 332 // Save the session ... 333 session.save(); 334 } finally { 335 session.logout(); 336 } 337 } 338 339 /** 340 * Perform a search of the repository for all image metadata automatically created by the image sequencer. 341 * 342 * @throws Exception 343 */ 344 public void search() throws Exception { 345 // Use JCR to search the repository for image metadata ... 346 List<ContentInfo> infos = new ArrayList<ContentInfo>(); 347 Session session = createSession(); 348 try { 349 // Find the node ... 350 Node root = session.getRootNode(); 351 352 if (root.hasNode("images") || root.hasNode("mp3s")) { 353 Node mediasNode; 354 if (root.hasNode("images")) { 355 mediasNode = root.getNode("images"); 356 357 for (NodeIterator iter = mediasNode.getNodes(); iter.hasNext();) { 358 Node mediaNode = iter.nextNode(); 359 if (mediaNode.hasNode("image:metadata")) { 360 infos.add(extractMediaInfo("image:metadata", "image", mediaNode)); 361 } 362 } 363 } 364 if (root.hasNode("mp3s")) { 365 mediasNode = root.getNode("mp3s"); 366 367 for (NodeIterator iter = mediasNode.getNodes(); iter.hasNext();) { 368 Node mediaNode = iter.nextNode(); 369 if (mediaNode.hasNode("mp3:metadata")) { 370 infos.add(extractMediaInfo("mp3:metadata", "mp3", mediaNode)); 371 } 372 } 373 } 374 375 } 376 if (root.hasNode("java")) { 377 Map<String, List<Properties>> tree = new TreeMap<String, List<Properties>>(); 378 // Find the compilation unit node ... 379 List<Properties> javaElements; 380 if (root.hasNode("java")) { 381 Node javaSourcesNode = root.getNode("java"); 382 for (NodeIterator i = javaSourcesNode.getNodes(); i.hasNext();) { 383 384 Node javaSourceNode = i.nextNode(); 385 386 if (javaSourceNode.hasNodes()) { 387 Node javaCompilationUnit = javaSourceNode.getNodes().nextNode(); 388 // package informations 389 390 javaElements = new ArrayList<Properties>(); 391 try { 392 Node javaPackageDeclarationNode = javaCompilationUnit.getNode("java:package/java:packageDeclaration"); 393 javaElements.add(extractJavaInfo(javaPackageDeclarationNode)); 394 tree.put("Class package", javaElements); 395 } catch (PathNotFoundException e) { 396 // do nothing 397 } 398 399 // import informations 400 javaElements = new ArrayList<Properties>(); 401 try { 402 for (NodeIterator singleImportIterator = javaCompilationUnit.getNode("java:import/java:importDeclaration/java:singleImport") 403 .getNodes(); singleImportIterator.hasNext();) { 404 Node javasingleTypeImportDeclarationNode = singleImportIterator.nextNode(); 405 javaElements.add(extractJavaInfo(javasingleTypeImportDeclarationNode)); 406 } 407 tree.put("Class single Imports", javaElements); 408 } catch (PathNotFoundException e) { 409 // do nothing 410 } 411 412 javaElements = new ArrayList<Properties>(); 413 try { 414 for (NodeIterator javaImportOnDemandIterator = javaCompilationUnit.getNode("java:import/java:importDeclaration/java:importOnDemand") 415 .getNodes(); javaImportOnDemandIterator.hasNext();) { 416 Node javaImportOnDemandtDeclarationNode = javaImportOnDemandIterator.nextNode(); 417 javaElements.add(extractJavaInfo(javaImportOnDemandtDeclarationNode)); 418 } 419 tree.put("Class on demand imports", javaElements); 420 421 } catch (PathNotFoundException e) { 422 // do nothing 423 } 424 // class head informations 425 javaElements = new ArrayList<Properties>(); 426 Node javaNormalDeclarationClassNode = javaCompilationUnit.getNode("java:unitType/java:classDeclaration/java:normalClass/java:normalClassDeclaration"); 427 javaElements.add(extractJavaInfo(javaNormalDeclarationClassNode)); 428 tree.put("Class head information", javaElements); 429 430 // field member informations 431 javaElements = new ArrayList<Properties>(); 432 for (NodeIterator javaFieldTypeIterator = javaCompilationUnit.getNode("java:unitType/java:classDeclaration/java:normalClass/java:normalClassDeclaration/java:field/java:fieldType") 433 .getNodes(); javaFieldTypeIterator.hasNext();) { 434 Node rootFieldTypeNode = javaFieldTypeIterator.nextNode(); 435 if (rootFieldTypeNode.hasNode("java:primitiveType")) { 436 Node javaPrimitiveTypeNode = rootFieldTypeNode.getNode("java:primitiveType"); 437 javaElements.add(extractJavaInfo(javaPrimitiveTypeNode)); 438 // more informations 439 } 440 441 if (rootFieldTypeNode.hasNode("java:simpleType")) { 442 Node javaSimpleTypeNode = rootFieldTypeNode.getNode("java:simpleType"); 443 javaElements.add(extractJavaInfo(javaSimpleTypeNode)); 444 } 445 if (rootFieldTypeNode.hasNode("java:parameterizedType")) { 446 Node javaParameterizedType = rootFieldTypeNode.getNode("java:parameterizedType"); 447 javaElements.add(extractJavaInfo(javaParameterizedType)); 448 } 449 if (rootFieldTypeNode.hasNode("java:arrayType")) { 450 Node javaArrayType = rootFieldTypeNode.getNode("java:arrayType[2]"); 451 javaElements.add(extractJavaInfo(javaArrayType)); 452 } 453 } 454 tree.put("Class field members", javaElements); 455 456 // constructor informations 457 javaElements = new ArrayList<Properties>(); 458 for (NodeIterator javaConstructorIterator = javaCompilationUnit.getNode("java:unitType/java:classDeclaration/java:normalClass/java:normalClassDeclaration/java:constructor") 459 .getNodes(); javaConstructorIterator.hasNext();) { 460 Node javaConstructor = javaConstructorIterator.nextNode(); 461 javaElements.add(extractJavaInfo(javaConstructor)); 462 } 463 tree.put("Class constructors", javaElements); 464 465 // method informations 466 javaElements = new ArrayList<Properties>(); 467 for (NodeIterator javaMethodIterator = javaCompilationUnit.getNode("java:unitType/java:classDeclaration/java:normalClass/java:normalClassDeclaration/java:method") 468 .getNodes(); javaMethodIterator.hasNext();) { 469 Node javaMethod = javaMethodIterator.nextNode(); 470 javaElements.add(extractJavaInfo(javaMethod)); 471 } 472 tree.put("Class member functions", javaElements); 473 474 JavaInfo javaInfo = new JavaInfo(javaCompilationUnit.getPath(), javaCompilationUnit.getName(), 475 "java source", tree); 476 infos.add(javaInfo); 477 } 478 } 479 } 480 481 } 482 } finally { 483 session.logout(); 484 } 485 486 // Display the search results ... 487 this.userInterface.displaySearchResults(infos); 488 } 489 490 private MediaInfo extractMediaInfo( String metadataNodeName, 491 String mediaType, 492 Node mediaNode ) throws RepositoryException, PathNotFoundException, ValueFormatException { 493 String nodePath = mediaNode.getPath(); 494 String nodeName = mediaNode.getName(); 495 mediaNode = mediaNode.getNode(metadataNodeName); 496 497 // Create a Properties object containing the properties for this node; ignore any children ... 498 Properties props = new Properties(); 499 for (PropertyIterator propertyIter = mediaNode.getProperties(); propertyIter.hasNext();) { 500 Property property = propertyIter.nextProperty(); 501 String name = property.getName(); 502 String stringValue = null; 503 if (property.getDefinition().isMultiple()) { 504 StringBuilder sb = new StringBuilder(); 505 boolean first = true; 506 for (Value value : property.getValues()) { 507 if (!first) { 508 sb.append(", "); 509 first = false; 510 } 511 sb.append(value.getString()); 512 } 513 stringValue = sb.toString(); 514 } else { 515 stringValue = property.getValue().getString(); 516 } 517 props.put(name, stringValue); 518 } 519 // Create the image information object, and add it to the collection ... 520 return new MediaInfo(nodePath, nodeName, mediaType, props); 521 } 522 523 /** 524 * Extract informations from a specific node. 525 * 526 * @param node - node, that contains informations. 527 * @return a properties of keys/values. 528 * @throws RepositoryException 529 * @throws IllegalStateException 530 * @throws ValueFormatException 531 */ 532 private Properties extractJavaInfo( Node node ) throws ValueFormatException, IllegalStateException, RepositoryException { 533 if (node.hasProperties()) { 534 Properties properties = new Properties(); 535 for (PropertyIterator propertyIter = node.getProperties(); propertyIter.hasNext();) { 536 Property property = propertyIter.nextProperty(); 537 String name = property.getName(); 538 String stringValue = property.getValue().getString(); 539 properties.put(name, stringValue); 540 } 541 return properties; 542 } 543 return null; 544 } 545 546 /** 547 * Utility method to create a new JCR session from the execution context's {@link SessionFactory}. 548 * 549 * @return the session 550 * @throws RepositoryException 551 */ 552 protected Session createSession() throws RepositoryException { 553 return this.executionContext.getSessionFactory().createSession(this.repositoryName + "/" + this.workspaceName); 554 } 555 556 protected String getMimeType( URL file ) { 557 String filename = file.getPath().toLowerCase(); 558 if (filename.endsWith(".gif")) return "image/gif"; 559 if (filename.endsWith(".png")) return "image/png"; 560 if (filename.endsWith(".pict")) return "image/x-pict"; 561 if (filename.endsWith(".bmp")) return "image/bmp"; 562 if (filename.endsWith(".jpg")) return "image/jpeg"; 563 if (filename.endsWith(".jpe")) return "image/jpeg"; 564 if (filename.endsWith(".jpeg")) return "image/jpeg"; 565 if (filename.endsWith(".ras")) return "image/x-cmu-raster"; 566 if (filename.endsWith(".mp3")) return "audio/mpeg"; 567 if (filename.endsWith(".java")) return "text/x-java-source"; 568 throw new SystemFailureException("Unknown mime type for " + file); 569 } 570 571 }