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 } else if (root.hasNode("java")) { 373 Map<String, List<Properties>> tree = new TreeMap<String, List<Properties>>(); 374 // Find the compilation unit node ... 375 List<Properties> javaElements; 376 if (root.hasNode("java")) { 377 Node javaSourcesNode = root.getNode("java"); 378 for (NodeIterator i = javaSourcesNode.getNodes(); i.hasNext();) { 379 380 Node javaSourceNode = i.nextNode(); 381 382 if (javaSourceNode.hasNodes()) { 383 Node javaCompilationUnit = javaSourceNode.getNodes().nextNode(); 384 // package informations 385 386 javaElements = new ArrayList<Properties>(); 387 try { 388 Node javaPackageDeclarationNode = javaCompilationUnit.getNode("java:package/java:packageDeclaration"); 389 javaElements.add(extractJavaInfo(javaPackageDeclarationNode)); 390 tree.put("Class package", javaElements); 391 } catch (PathNotFoundException e) { 392 // do nothing 393 } 394 395 // import informations 396 javaElements = new ArrayList<Properties>(); 397 try { 398 for (NodeIterator singleImportIterator = javaCompilationUnit.getNode("java:import/java:importDeclaration/java:singleImport").getNodes(); singleImportIterator.hasNext();) { 399 Node javasingleTypeImportDeclarationNode = singleImportIterator.nextNode(); 400 javaElements.add(extractJavaInfo(javasingleTypeImportDeclarationNode)); 401 } 402 tree.put("Class single Imports", javaElements); 403 } catch (PathNotFoundException e) { 404 // do nothing 405 } 406 407 javaElements = new ArrayList<Properties>(); 408 try { 409 for (NodeIterator javaImportOnDemandIterator = javaCompilationUnit.getNode("java:import/java:importDeclaration/java:importOnDemand").getNodes(); javaImportOnDemandIterator.hasNext();) { 410 Node javaImportOnDemandtDeclarationNode = javaImportOnDemandIterator.nextNode(); 411 javaElements.add(extractJavaInfo(javaImportOnDemandtDeclarationNode)); 412 } 413 tree.put("Class on demand imports", javaElements); 414 415 } catch (PathNotFoundException e) { 416 // do nothing 417 } 418 // class head informations 419 javaElements = new ArrayList<Properties>(); 420 Node javaNormalDeclarationClassNode = javaCompilationUnit.getNode("java:unitType/java:classDeclaration/java:normalClass/java:normalClassDeclaration"); 421 javaElements.add(extractJavaInfo(javaNormalDeclarationClassNode)); 422 tree.put("Class head information", javaElements); 423 424 // field member informations 425 javaElements = new ArrayList<Properties>(); 426 for (NodeIterator javaFieldTypeIterator = javaCompilationUnit.getNode("java:unitType/java:classDeclaration/java:normalClass/java:normalClassDeclaration/java:field/java:fieldType").getNodes(); javaFieldTypeIterator.hasNext();) { 427 Node rootFieldTypeNode = javaFieldTypeIterator.nextNode(); 428 if (rootFieldTypeNode.hasNode("java:primitiveType")) { 429 Node javaPrimitiveTypeNode = rootFieldTypeNode.getNode("java:primitiveType"); 430 javaElements.add(extractJavaInfo(javaPrimitiveTypeNode)); 431 // more informations 432 } 433 434 if (rootFieldTypeNode.hasNode("java:simpleType")) { 435 Node javaSimpleTypeNode = rootFieldTypeNode.getNode("java:simpleType"); 436 javaElements.add(extractJavaInfo(javaSimpleTypeNode)); 437 } 438 if (rootFieldTypeNode.hasNode("java:parameterizedType")) { 439 Node javaParameterizedType = rootFieldTypeNode.getNode("java:parameterizedType"); 440 javaElements.add(extractJavaInfo(javaParameterizedType)); 441 } 442 if (rootFieldTypeNode.hasNode("java:arrayType")) { 443 Node javaArrayType = rootFieldTypeNode.getNode("java:arrayType[2]"); 444 javaElements.add(extractJavaInfo(javaArrayType)); 445 } 446 } 447 tree.put("Class field members", javaElements); 448 449 // constructor informations 450 javaElements = new ArrayList<Properties>(); 451 for (NodeIterator javaConstructorIterator = javaCompilationUnit.getNode("java:unitType/java:classDeclaration/java:normalClass/java:normalClassDeclaration/java:constructor").getNodes(); javaConstructorIterator.hasNext();) { 452 Node javaConstructor = javaConstructorIterator.nextNode(); 453 javaElements.add(extractJavaInfo(javaConstructor)); 454 } 455 tree.put("Class constructors", javaElements); 456 457 // method informations 458 javaElements = new ArrayList<Properties>(); 459 for (NodeIterator javaMethodIterator = javaCompilationUnit.getNode("java:unitType/java:classDeclaration/java:normalClass/java:normalClassDeclaration/java:method").getNodes(); javaMethodIterator.hasNext();) { 460 Node javaMethod = javaMethodIterator.nextNode(); 461 javaElements.add(extractJavaInfo(javaMethod)); 462 } 463 tree.put("Class member functions", javaElements); 464 465 JavaInfo javaInfo = new JavaInfo(javaCompilationUnit.getPath(), javaCompilationUnit.getName(), 466 "java source", tree); 467 infos.add(javaInfo); 468 } 469 } 470 } 471 472 } 473 } finally { 474 session.logout(); 475 } 476 477 // Display the search results ... 478 this.userInterface.displaySearchResults(infos); 479 } 480 481 private MediaInfo extractMediaInfo( String metadataNodeName, 482 String mediaType, 483 Node mediaNode ) throws RepositoryException, PathNotFoundException, ValueFormatException { 484 String nodePath = mediaNode.getPath(); 485 String nodeName = mediaNode.getName(); 486 mediaNode = mediaNode.getNode(metadataNodeName); 487 488 // Create a Properties object containing the properties for this node; ignore any children ... 489 Properties props = new Properties(); 490 for (PropertyIterator propertyIter = mediaNode.getProperties(); propertyIter.hasNext();) { 491 Property property = propertyIter.nextProperty(); 492 String name = property.getName(); 493 String stringValue = null; 494 if (property.getDefinition().isMultiple()) { 495 StringBuilder sb = new StringBuilder(); 496 boolean first = true; 497 for (Value value : property.getValues()) { 498 if (!first) { 499 sb.append(", "); 500 first = false; 501 } 502 sb.append(value.getString()); 503 } 504 stringValue = sb.toString(); 505 } else { 506 stringValue = property.getValue().getString(); 507 } 508 props.put(name, stringValue); 509 } 510 // Create the image information object, and add it to the collection ... 511 return new MediaInfo(nodePath, nodeName, mediaType, props); 512 } 513 514 /** 515 * Extract informations from a specific node. 516 * 517 * @param node - node, that contains informations. 518 * @return a properties of keys/values. 519 * @throws RepositoryException 520 * @throws IllegalStateException 521 * @throws ValueFormatException 522 */ 523 private Properties extractJavaInfo( Node node ) throws ValueFormatException, IllegalStateException, RepositoryException { 524 if (node.hasProperties()) { 525 Properties properties = new Properties(); 526 for (PropertyIterator propertyIter = node.getProperties(); propertyIter.hasNext();) { 527 Property property = propertyIter.nextProperty(); 528 String name = property.getName(); 529 String stringValue = property.getValue().getString(); 530 properties.put(name, stringValue); 531 } 532 return properties; 533 } 534 return null; 535 } 536 537 /** 538 * Utility method to create a new JCR session from the execution context's {@link SessionFactory}. 539 * 540 * @return the session 541 * @throws RepositoryException 542 */ 543 protected Session createSession() throws RepositoryException { 544 return this.executionContext.getSessionFactory().createSession(this.repositoryName + "/" + this.workspaceName); 545 } 546 547 protected String getMimeType( URL file ) { 548 String filename = file.getPath().toLowerCase(); 549 if (filename.endsWith(".gif")) return "image/gif"; 550 if (filename.endsWith(".png")) return "image/png"; 551 if (filename.endsWith(".pict")) return "image/x-pict"; 552 if (filename.endsWith(".bmp")) return "image/bmp"; 553 if (filename.endsWith(".jpg")) return "image/jpeg"; 554 if (filename.endsWith(".jpe")) return "image/jpeg"; 555 if (filename.endsWith(".jpeg")) return "image/jpeg"; 556 if (filename.endsWith(".ras")) return "image/x-cmu-raster"; 557 if (filename.endsWith(".mp3")) return "audio/mpeg"; 558 if (filename.endsWith(".java")) return "text/x-java-source"; 559 throw new SystemFailureException("Unknown mime type for " + file); 560 } 561 562 }