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