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 }