JBoss.org Community Documentation

4.4. Reviewing the example application

Recall that the example application consists of a client application that sets up an in-memory JCR repository and that allows a user to upload files into that repository. The client also sets up the DNA services with an image sequencer so that if any of the uploaded files are PNG, JPEG, GIF, BMP or other images, DNA will automatically extract the image's metadata (e.g., image format, physical size, pixel density, etc.) and store that in the repository. Or, if the client uploads MP3 audio files, the title, author, album, year, and comment are extract from the audio file and stored in the repository.

The example is comprised of 3 classes and 1 interface, located in the src/main/java directory:

  org/jboss/example/dna/sequencers/ConsoleInput.java
                                  /MediaInfo.java
                                  /SequencingClient.java
                                  /UserInterface.java

SequencingClient is the class that contains the main application. MediaInfo is a simple Java object that encapsulates metadata about a media file (as generated by the sequencer), and used by the client to pass information to the UserInterface , which is an interface with methods that will be called at runtime to request data from the user. ConsoleInput is an implementation of this that creates a text user interface, allowing the user to operate the client from the command line. We can easily create a graphical implementation of UserInterface at a later date. We can also create a mock implementation for testing purposes that simulates a user entering data. This allows us to check the behaviour of the client automatically using conventional JUnit test cases, as demonstrated by the code in the src/test/java directory:

  org/jboss/example/dna/sequencers/SequencingClientTest.java
                                  /MockUserInterface.java

If we look at the SequencingClient code, there are a handful of methods that encapsulate the various activities.

Note

To keep the code shown in this book as readable as possible, some of the comments and error handling have been removed.

The startRepository() method starts up an in-memory Jackrabbit JCR repository. The bulk of this method is simply gathering and passing the information required by Jackrabbit. Because Jackrabbit's TransientRepository implementation shuts down after the last session is closed, the application maintains a session to ensure that the repository remains open throughout the application's lifetime. And finally, the node type needed by the image sequencer is registered with Jackrabbit.

public void startRepository() throws Exception {
    if (this.repository == null) {
        try {

            // Load the Jackrabbit configuration ...
            File configFile = new File(this.jackrabbitConfigPath);
            String pathToConfig = configFile.getAbsolutePath();

            // Find the directory where the Jackrabbit repository data will be stored ...
            File workingDirectory = new File(this.workingDirectory);
            String workingDirectoryPath = workingDirectory.getAbsolutePath();

            // Get the Jackrabbit custom node definition (CND) file ...
            URL cndFile = Thread.currentThread().getContextClassLoader().getResource("jackrabbitNodeTypes.cnd");

            // Create the Jackrabbit repository instance and establish a session to keep the repository alive ...
            this.repository = new TransientRepository(pathToConfig, workingDirectoryPath);
            if (this.username != null) {
                Credentials credentials = new SimpleCredentials(this.username, this.password);
                this.keepAliveSession = this.repository.login(credentials, this.workspaceName);
            } else {
                this.keepAliveSession = this.repository.login();
            }

            try {
                // Register the node types (only valid the first time) ...
                JackrabbitNodeTypeManager mgr = (JackrabbitNodeTypeManager)this.keepAliveSession.getWorkspace().getNodeTypeManager();
                mgr.registerNodeTypes(cndFile.openStream(), JackrabbitNodeTypeManager.TEXT_X_JCR_CND);
            } catch (RepositoryException e) {
                if (!e.getMessage().contains("already exists")) throw e;
            }

        } catch (Exception e) {
            this.repository = null;
            this.keepAliveSession = null;
            throw e;
        }
    }
}

As you can see, this method really has nothing to do with JBoss DNA, other than setting up a JCR repository that JBoss DNA will use.

The shutdownRepository() method shuts down the Jackrabbit transient repository by closing the "keep alive session". Again, this method really does nothing specifically with JBoss DNA, but is needed to manage the JCR repository that JBoss DNA uses.

public void shutdownRepository() throws Exception {
    if (this.repository != null) {
        try {
            this.keepAliveSession.logout();
        } finally {
            this.repository = null;
            this.keepAliveSession = null;
        }
    }
}

The startDnaServices() method first starts the JCR repository (if it were not already started), and proceeds to create and configure the SequencingService as described earlier . This involes setting up the SessionFactory , ExecutionContext , creating the SequencingService instance, and configuring the image sequencer. The method then continues by setting up the ObservationService as described earlier and starting the service.

public void startDnaServices() throws Exception {
    if (this.repository == null) this.startRepository();
    if (this.sequencingService == null) {

        SimpleSessionFactory sessionFactory = new SimpleSessionFactory();
        sessionFactory.registerRepository(this.repositoryName, this.repository);
        if (this.username != null) {
            Credentials credentials = new SimpleCredentials(this.username, this.password);
            sessionFactory.registerCredentials(this.repositoryName + "/" + this.workspaceName, credentials);
        }
        this.executionContext = new SimpleExecutionContext(sessionFactory);

        // Create the sequencing service, passing in the execution context ...
        this.sequencingService = new SequencingService();
        this.sequencingService.setExecutionContext(executionContext);

        // Configure the sequencers.
        String name = "Image Sequencer";
        String desc = "Sequences image files to extract the characteristics of the image";
        String classname = "org.jboss.dna.sequencer.images.ImageMetadataSequencer";
        String[] classpath = null; // Use the current classpath
        String[] pathExpressions = {"//(*.(jpg|jpeg|gif|bmp|pcx|png|iff|ras|pbm|pgm|ppm|psd))[*]/jcr:content[@jcr:data] => /images/$1"};
        SequencerConfig imageSequencerConfig = new SequencerConfig(name, desc, classname, classpath, pathExpressions);
        this.sequencingService.addSequencer(imageSequencerConfig);

        // Set up the MP3 sequencer ...
        name = "Mp3 Sequencer";
        desc = "Sequences mp3 files to extract the id3 tags of the audio file";
        classname = "org.jboss.dna.sequencer.mp3.Mp3MetadataSequencer";
        String[] mp3PathExpressions = {"//(*.mp3)[*]/jcr:content[@jcr:data] => /mp3s/$1"};
        SequencerConfig mp3SequencerConfig = new SequencerConfig(name, desc, classname, classpath, mp3PathExpressions);
        this.sequencingService.addSequencer(mp3SequencerConfig);

        // Use the DNA observation service to listen to the JCR repository (or multiple ones), and
        // then register the sequencing service as a listener to this observation service...
        this.observationService = new ObservationService(this.executionContext.getSessionFactory());
        this.observationService.getAdministrator().start();
        this.observationService.addListener(this.sequencingService);
        this.observationService.monitor(this.repositoryName + "/" + this.workspaceName, Event.NODE_ADDED | Event.PROPERTY_ADDED | Event.PROPERTY_CHANGED);
    }
    // Start up the sequencing service ...
    this.sequencingService.getAdministrator().start();
}

The shutdownDnaServices() method is pretty straightforward: it just calls shutdown on each of the services and waits until they terminate.

public void shutdownDnaServices() throws Exception {
    if (this.sequencingService == null) return;

    // Shut down the service and wait until it's all shut down ...
    this.sequencingService.getAdministrator().shutdown();
    this.sequencingService.getAdministrator().awaitTermination(5, TimeUnit.SECONDS);

    // Shut down the observation service ...
    this.observationService.getAdministrator().shutdown();
    this.observationService.getAdministrator().awaitTermination(5, TimeUnit.SECONDS);
}

None of the other methods really do anything with JBoss DNA per se. Instead, they merely work with the repository using the JCR API.

The main method of the SequencingClient class creates a SequencingClient instance, and passes a new ConsoleInput instance:

public static void main( String[] args ) throws Exception {
    SequencingClient client = new SequencingClient();
    client.setRepositoryInformation("repo", "default", "jsmith", "secret".toCharArray());
    client.setUserInterface(new ConsoleInput(client));
}

If we look at the ConsoleInput constructor, it starts the repository, the DNA services, and a thread for the user interface. At this point, the constructor returns, but the main application continues under the user interface thread. When the user requests to quit, the user interface thread also shuts down the DNA services and JCR repository.

public ConsoleInput( SequencerClient client ) {
  try {
      client.startRepository();
      client.startDnaServices();
  
      System.out.println(getMenu());
      Thread eventThread = new Thread(new Runnable() {
          private boolean quit = false;
          public void run() {
              try {
                  while (!quit) {
                      // Display the prompt and process the requested operation ...
                  }
              } finally {
                  try {
                      // Terminate ...
                      client.shutdownDnaServices();
                      client.shutdownRepository();
                  } catch (Exception err) {
                      System.out.println("Error shutting down sequencing service and repository: " + err.getLocalizedMessage());
                      err.printStackTrace(System.err);
                  }
              }
          }
      });
      eventThread.start();
  } catch (Exception err) {
      System.out.println("Error: " + err.getLocalizedMessage());
      err.printStackTrace(System.err);
  }
}

At this point, we've reviewed all of the interesting code in the example application. However, feel free to play with the application, trying different things.