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 }