for use with JBoss Enterprise Application Platform 5

MarkNewton

AlesJustin

Edited by

EvaKopalova

Edited by

MistyStanley-Jones

Edited by

PetrPenicka

Edited by

RussellDickenson

Edited by

ScottMumford

Legal Notice

This document is licensed by Red Hat under the Creative Commons Attribution-ShareAlike 3.0 Unported License. If you distribute this document, or a modified version of it, you must provide attribution to Red Hat, Inc. and provide a link to the original. If the document is modified, all Red Hat trademarks must be removed.

Red Hat, as the licensor of this document, waives the right to enforce, and agrees not to assert, Section 4d of CC-BY-SA to the fullest extent permitted by applicable law.

Red Hat, Red Hat Enterprise Linux, the Shadowman logo, JBoss, MetaMatrix, Fedora, the Infinity Logo, and RHCE are trademarks of Red Hat, Inc., registered in the United States and other countries.

Linux® is the registered trademark of Linus Torvalds in the United States and other countries.

Java® is a registered trademark of Oracle and/or its affiliates.

XFS® is a trademark of Silicon Graphics International Corp. or its subsidiaries in the United States and/or other countries.

MySQL® is a registered trademark of MySQL AB in the United States, the European Union and other countries.

Node.js® is an official trademark of Joyent. Red Hat Software Collections is not formally related to or endorsed by the official Joyent Node.js open source or commercial project.

The OpenStack® Word Mark and OpenStack Logo are either registered trademarks/service marks or trademarks/service marks of the OpenStack Foundation, in the United States and other countries and are used with the OpenStack Foundation's permission. We are not affiliated with, endorsed or sponsored by the OpenStack Foundation, or the OpenStack community.

All other trademarks are the property of their respective owners.

Abstract

This guide is intended for Java developers who wish to use the JBoss Microcontainer to deploy customized, modular Java environments for their applications.

Preface

1. Document Conventions

This manual uses several conventions to highlight certain words and phrases and draw attention to specific pieces of information.

In PDF and paper editions, this manual uses typefaces drawn from the Liberation Fonts set. The Liberation Fonts set is also used in HTML editions if the set is installed on your system. If not, alternative but equivalent typefaces are displayed. Note: Red Hat Enterprise Linux 5 and later include the Liberation Fonts set by default.

1.1. Typographic Conventions

Four typographic conventions are used to call attention to specific words and phrases. These conventions, and the circumstances they apply to, are as follows.

Mono-spaced Bold

Used to highlight system input, including shell commands, file names and paths. Also used to highlight keys and key combinations. For example:

To see the contents of the file my_next_bestselling_novel in your current working directory, enter the cat my_next_bestselling_novel command at the shell prompt and press Enter to execute the command.

The above includes a file name, a shell command and a key, all presented in mono-spaced bold and all distinguishable thanks to context.

Key combinations can be distinguished from an individual key by the plus sign that connects each part of a key combination. For example:

Press Enter to execute the command.

Press Ctrl+Alt+F2 to switch to a virtual terminal.

The first example highlights a particular key to press. The second example highlights a key combination: a set of three keys pressed simultaneously.

If source code is discussed, class names, methods, functions, variable names and returned values mentioned within a paragraph will be presented as above, in mono-spaced bold. For example:

File-related classes include filesystem for file systems, file for files, and dir for directories. Each class has its own associated set of permissions.

Proportional Bold

This denotes words or phrases encountered on a system, including application names; dialog box text; labeled buttons; check-box and radio button labels; menu titles and sub-menu titles. For example:

Choose System → Preferences → Mouse from the main menu bar to launch Mouse Preferences. In the Buttons tab, select the Left-handed mouse check box and click Close to switch the primary mouse button from the left to the right (making the mouse suitable for use in the left hand).

To insert a special character into a gedit file, choose Applications → Accessories → Character Map from the main menu bar. Next, choose Search → Find… from the Character Map menu bar, type the name of the character in the Search field and click Next. The character you sought will be highlighted in the Character Table. Double-click this highlighted character to place it in the Text to copy field and then click the Copy button. Now switch back to your document and choose Edit → Paste from the gedit menu bar.

The above text includes application names; system-wide menu names and items; application-specific menu names; and buttons and text found within a GUI interface, all presented in proportional bold and all distinguishable by context.

Mono-spaced Bold Italic or Proportional Bold Italic

Whether mono-spaced bold or proportional bold, the addition of italics indicates replaceable or variable text. Italics denotes text you do not input literally or displayed text that changes depending on circumstance. For example:

To connect to a remote machine using ssh, type ssh username@domain.name at a shell prompt. If the remote machine is example.com and your username on that machine is john, type ssh john@example.com.

The mount -o remount file-system command remounts the named file system. For example, to remount the /home file system, the command is mount -o remount /home.

To see the version of a currently installed package, use the rpm -q package command. It will return a result as follows: package-version-release.

Note the words in bold italics above — username, domain.name, file-system, package, version and release. Each word is a placeholder, either for text you enter when issuing a command or for text displayed by the system.

Aside from standard usage for presenting the title of a work, italics denotes the first use of a new and important term. For example:

Publican is a DocBook publishing system.

1.2. Pull-quote Conventions

Terminal output and source code listings are set off visually from the surrounding text.

Output sent to a terminal is set in mono-spaced roman and presented thus:

1.3. Notes and Warnings

Finally, we use three visual styles to draw attention to information that might otherwise be overlooked.

Note

Notes are tips, shortcuts or alternative approaches to the task at hand. Ignoring a note should have no negative consequences, but you might miss out on a trick that makes your life easier.

Important

Important boxes detail things that are easily missed: configuration changes that only apply to the current session, or services that need restarting before an update will apply. Ignoring a box labeled 'Important' will not cause data loss but may cause irritation and frustration.

Warning

Warnings should not be ignored. Ignoring warnings will most likely cause data loss.

2. Getting Help and Giving Feedback

2.1. Do You Need Help?

If you experience difficulty with a procedure described in this documentation, visit the Red Hat Customer Portal at http://access.redhat.com. Through the customer portal, you can:

search or browse through a knowledgebase of technical support articles about Red Hat products.

submit a support case to Red Hat Global Support Services (GSS).

access other product documentation.

Red Hat also hosts a large number of electronic mailing lists for discussion of Red Hat software and technology. You can find a list of publicly available mailing lists at https://www.redhat.com/mailman/listinfo. Click on the name of any mailing list to subscribe to that list or to access the list archives.

2.2. Give us Feedback

If you find a typographical error, or know how this guide can be improved, we would love to hear from you. Submit a report in Bugzilla against the product JBoss Enterprise Application Platform 5 and the component doc-JBoss_Microcontainer_User_Guide. The following link will take you to a pre-filled bug report for this product: http://bugzilla.redhat.com/.

Fill out the following template in Bugzilla's Description field. Be as specific as possible when describing the issue; this will help ensure that we can fix it quickly.

Chapter 1. Prerequisites to Using This Guide

To use the examples in this guide, you need to install and configure some supporting software, and download the code for the examples.

1.1. Install Maven

The examples used in this project require Maven v2.2.0 or later. Download Maven directly from the Apache Maven homepage, and install and configure your system as described in Procedure 1.1, “Install Maven”.

Procedure 1.1. Install Maven

Verify Java Developer Kit 1.6 or above is installed. This is also a requirement for the Enterprise Platform.

Ensure you have Java installed on your system, and have set the JAVA_HOME environment variable in your ~/.bash_profile for Linux, or in the System Properties for Windows. For more information regarding setting environment variables, refer to the Step 4 step in this procedure.

Download Maven

Note

This step and future steps assume that you have saved Maven to the suggested location for your operating system. Maven, as any other Java application, is able to be installed in any reasonable location on your system.

Save the zip archive to your C:\Documents and Settings\user_name directory.

Install Maven

For Linux Users

Extract the zip file to your home directory. If you selected the zip archive in Step 2, and do not rename the directory, the extracted directory is named apache-maven-version.

For Windows Users

Extract the zip archive to C:\Program Files\Apache Software Foundation. If you selected the zip archive in Step 2, and do not rename the directory, the extracted directory is named apache-maven-version.

Configure Environment Variables

For Linux Users

Add the following lines to your ~/.bash_profile. Ensure you change the [username] to your actual username, and that the Maven directory is the actual directory name. The version number may be different than the one listed below.

By including M2 at the beginning of your path, the Maven version you just installed will be the default version used. You may also want to set the path of your JAVA_HOME environment variable to the location of the JDK on your system.

For Windows Users

Add the M2_HOME, M2, and JAVA_HOME environment variables.

Press Start+Pause|Break. The System Properties dialog box is displayed.

Click the Advanced tab, then click the Environment Variables button.

Under System Variables, select Path.

Click Edit, and append the two Maven paths using a semi-colon to separate each entry. Quotation marks are not required around paths.

Add the variable M2_HOME and set the path to C:\Program Files\Apache Software Foundation\apache-maven-2.2.1.

Add the variable M2 and set the value to %M2_HOME%\bin.

In the same dialog, create the JAVA_HOME environment variable:

Add the variable %JAVA_HOME% and set the value to the location of your JDK. For example C:\Program Files\Java\jdk1.6.0_02.

In the same dialog, update or create the Path environment variable:

Add the variable %M2% to allow Maven to be executed from the command-line.

Add the variable %JAVA_HOME%\bin to set the path to the correct Java installation.

Click OK until the System Properties dialog box closes.

Implement changes to .bash_profile

For Linux Users Only

To update the changes made to the .bash_profile in the current terminal session, source your .bash_profile.

[localhost]$ source ~/.bash_profile

Update gnome-terminal profile

For Linux Users Only

Update the terminal profile to ensure that subsequent iterations of gnome-terminal (or Konsole terminal) read the new environment variables.

Click Edit → Profiles

Select Default, and click the Edit button.

In the Editing Profile dialog, click the Title and Command tab.

Select the Run command as login shell check box.

Close all open Terminal dialog boxes.

Verify the environment variable changes and Maven install

For Linux Users

To verify that the changes have been implemented correctly, open a terminal and execute the following commands:

Execute echo $M2_HOME, which should return the following result.

[localhost]$ echo $M2_HOME /home/username/apache-maven-2.2.1

Execute echo $M2, which should return the following result.

[localhost]$ echo $M2 /home/username/apache-maven-2.2.1/bin

Execute echo $PATH, and verify the Maven/bin directory is included.

[localhost]$ echo $PATH /home/username/apache-maven-2.2.1/bin

Execute which mvn, which should display the path to the Maven executable.

[localhost]$ which mvn ~/apache-maven-2.2.1/bin/mvn

Execute mvn -version, which should display the Maven version, related Java version, and operating system information.

You have now successfully configured Maven for use with the examples in this guide.

1.2. Special Maven Settings for the Microcontainer Examples

Maven is a modular build system which pulls in dependencies as needed. The examples in this guide assume that you have included the block of XML in Example 1.1, “Example settings.xml File” in your ~/.m2/settings.xml (Linux) or C:\Documents and Settings\username\.m2\settings.xml (Windows). If the file does not exist, you can create it first.

1.3. Downloading the Examples

The examples in this guide show you how to create a maven project that depends on the JBoss Microcontainer, using Maven. You can download them from images/examples.zip . This location is subject to change, but is included for expediency.

After you have downloaded the ZIP file containing the examples, extract it to a convenient location and look over the examples to familiarize yourself with their structure.

Chapter 2. Introduction to the Microcontainer

The JBoss Microcontainer is a refactoring of the JBoss JMX Microkernel to support direct POJO deployment and standalone use outside the JBoss application server.

The Microcontainer is designed to meet specific needs of Java developers who want to use object-oriented programming techniques to rapidly deploy software. In addition, it allows software to be deployed on a wide range of devices, from mobile computing platforms, large-scale grid-computing environments, and everything in between.

2.1. Features

All the features of the JMX Microkernel

Direct POJO deployment (no need for Standard/XMBean or MBeanProxy)

Direct IOC style dependency injection

Improved life cycle management

Additional control over dependencies

Transparent AOP integration

Virtual File System

Virtual Deployment Framework

OSGi classloading

2.2. Definitions

The JBoss JMX Microkernel is a modular Java environment. It differs from standard environments like J2EE in that the developer is able to choose exactly which components are part of the environment, and leave out the rest.

POJO

A Plain Old Java Object (POJO) is a modular, reusable Java object. The name is used to emphasize that a given object is an ordinary Java Object, not a special object, and in particular not an Enterprise JavaBean. The term was coined by Martin Fowler, Rebecca Parsons and Josh MacKenzie in September 2000 in a talk where they were pointing out the many benefits of encoding business logic into regular java objects rather than using Entity Beans.

Java Bean

A Java Bean is a reusable software component that can be manipulated visually in a builder tool.

A Java Bean is an independent piece of code. It is not required to inherit from any particular base class or interface. Although Java Beans are primarily created in graphical IDEs, they can also be developed in simple text editors.

AOP

Aspect-Oriented Programming (AOP) is a programming paradigm in which secondary or supporting functions are isolated from the main program's business logic. It is a subset of object-oriented programming.

2.3. Installation

The Microcontainer is an integral part of the Enterprise Platform. More information about installing and configuring the Enterprise Platform can be found in the Administration and configuration Guide.

Services are pieces of code which perform work needed by multiple clients. For our purposes, we will put some additional constraints on the definition of a service. Services should have unique names which can be referenced, or called, by clients. The internals of a service should be invisible and unimportant to clients. This is the "black box" concept of object-oriented programming (OOP). In OOP, each object is independent, and no other object needs to know how it does its job.

In the context of the Microcontainer, services are built from POJOs. A POJO is nearly a service in its own right, but it can not be accessed by a unique name, and it must be created by the client that needs it.

Although a POJO must be created at run-time by the client, it does not need to be implemented by a separate class in order to provide a well-defined interface. As long as fields and methods are not removed, and access to them is not restricted, there is no need to recompile clients to use a newly-created POJO.

Note

Implementing an interface is only necessary in order to allow a client to choose between alternative implementations. If the client is compiled against an interface, many different implementations of the interface can be provided without having to recompile the client. The interface ensures that the method signatures do not change.

The remainder of this guide consists of creating a Human Resources service, using the Microcontainer to capture and modularize the business logic of the application. After the Microcontainer is installed, the example code is located in examples/User_Guide/gettingStarted/humanResourcesService.

3.1. Introduction to the Human Resources Example

The Java source files are located in packages beneath the examples/User_Guide/gettingStarted/humanResourcesService/src/main/java/org/jboss/example/service directory, after you have extracted the ZIP file. Each of these classes represents a simple POJO that does not implement any special interfaces. The most important class is HRManager, which represents the service entry point providing all of the public methods that clients will call.

Methods Provided by the HRManager Class

addEmployee(Employeeemployee)

removeEmployee(Employeeemployee)

getEmployee(StringfirstName, StringlastName)

getEmployees()

getSalary(Employeeemployee)

setSalary(Employeeemployee, IntegernewSalary)

isHiringFreeze()

setHiringFreeze(booleanhiringFreeze)

getSalaryStrategy()

setSalaryStrategy(SalaryStrategystrategy)

The Human Resources Service is composed of a handful of classes which maintain a list of employees and their details (addresses and salaries, in this case). Using the SalaryStrategy interface it is possible to configure the HRManager so that different salary strategy implementations are available to place minimum and maximum limits on the salaries for different employee roles.

3.2. Compiling the HRManager Example Project

To compile the source code, issue mvn compile from the humanResourcesService/ directory. This creates a new directory called target/classes which contains the compiled classes. To clean up the project and remove the target directory, issue the mvn clean command.

3.3. Creating POJOs

Before a POJO can be used, you need to create it. You need a naming mechanism that allows you to register a reference to the POJO instance with a name. Clients need this name to use the POJO.

The Microcontainer provides such a mechanism: a Controller. A Controller allows you to deploy your POJO-based services into a run-time environment.

3.3.1. XML Deployment Descriptors

After compiling the classes, use an XML deployment descriptor to create instances of them. The descriptor contains a list of beans representing individual instances. Each bean has a unique name, so that it can be called by clients at run-time. The following descriptor deploys an instance of the HRManager:

This XML creates an instance of the HRManager class and registers it with the name HRService. This file is passed to an XML deployer associated with the Microcontainer at run-time, which performs the actual deployment, and instantiates the beans.

3.4. Connecting POJOs Together

Individual POJO instances can only provide relatively simple behavior. The true power of POJOs comes from connecting them together to perform complex tasks. How can you wire POJOs together to choose different salary strategy implementations?

This XML creates an instance of the chosen salary strategy implementation by including an additional <bean> element. This time, the AgeBasedSalaryStrategy is chosen. Next the code injects a reference to this bean into the instance of HRManager created using the HRService bean. Injection is possible because the HRManager class contains a setSalaryStrategy(SalaryStrategy strategy) method. Behind the scenes, JBoss Microcontainer calls this method on the newly created HRManager instance and passes a reference to the AgeBasedSalaryStrategy instance.

The XML deployment descriptor causes the same sequence of events to occur as if you had written the following code:

In addition to performing injection via property setter methods, JBoss Microcontainer can also perform injection via constructor parameters if necessary. For more details please see the 'Injection' chapter in Part II 'POJO Development.'

3.4.1. Special Considerations

Although creating instances of classes using the <bean> element in the deployment descriptor is possible, it is not always the best way. For example, creating instances of the Employee and Address classes is unnecessary, because the client creates these in response to input from the user. They remain part of the service but are not referenced in the deployment descriptor.

Comment Your Code

You can define multiple beans within a deployment descriptor as long as each has a unique name, which is used to perform injection as shown above. However all of the beans do not necessarily represent services. While a service can be implemented using a single bean, multiple beans are usually used together. One bean typically represents the service entry point, and contains the public methods called by the clients. In this example the entry point is the HRService bean. The XML deployment descriptor does not indicate whether a bean represents a service or whether a bean is the service entry point. It is a good idea to use comments and an obvious naming scheme to delineate service beans from non-service beans.

3.5. Working with Services

After creating POJOs and connecting them together to form services, you need to configure the services, test them, and package them.

3.5.1. Configuring A Service

Services can be configured by at least two ways:

Injecting references between POJO instances

Injecting values into POJO properties

In this example, the second method is used. The following deployment descriptor configures the HRManager instance in the following ways:

A hiring freeze is implemented.

The AgeBasedSalaryStrategy implements new minimum and maximum salary values.

Injecting references between POJO instances is one way of configuring a service however we can also inject values into POJO properties. The following deployment descriptor shows how we can configure the HRManager instance to have a hiring freeze and the AgeBasedSalaryStrategy to have new minimum and maximum salary values:

The classes must have public setter methods for the relevant properties so that values can be injected. For example, the HRManager class has a setHiringFreeze(boolean hiringFreeze) method and the AgeBasedSalaryStrategy class has setMinSalary(int minSalary) and setMaxSalary(int maxSalary) methods.

The values in the deployment descriptor are converted from strings into the relevant types (boolean, int etc...) by JavaBean PropertyEditors. Many PropertyEditors are provided by default for standard types, but you can create your own if necessary. See the Properties chapter in Part II 'POJO Development' for more details.

3.5.2. Testing A Service

After you have created your POJOs and connected them together to form services, you need to test them. JBoss Microcontainer allows unit testing individual POJOs as well as services, through the use of a MicrocontainerTest class.

The org.jboss.test.kernel.junit.MicrocontainerTest class inherits from junit.framework.TestCase, setting up each test by bootstrapping JBoss Microcontainer and adding a BasicXMLDeployer. It then searches the classpath for an XML deployment descriptor with the same name as the test class, ending in .xml and residing in a directory structure representing the class's package name. Any beans found in this file are deployed and can then be accessed using a convenience method called getBean(String name).

The HRManagerTest class extends MicrocontainerTest in order to set up a number of employees to use as the basis for the tests. Individual test cases then subclass HRManagerTest to perform the actual work. Also included are a couple of TestSuite classes that are used to group individual test cases together for convenience.

To run the tests, enter mvn test from the humanResourcesService/ directory. You should see some DEBUG log output which shows JBoss Microcontainer starting up and deploying beans from the relevant XML file before running each test. At the end of the test the beans are undeployed and the Microcontainer is shut down.

Note

Some of the tests, such as HRManagerTestCase, AgeBasedSalaryTestCase, and LocationBasedSalaryTestCase, unit test individual POJOs. Other tests, such as HRManagerAgeBasedTestCase and HRManagerLocationBasedTestCase unit test entire services. Either way, the tests are run in the same manner. Using the MicrocontainerTest class makes it easy to set up and conduct comprehensive tests for any part of your code.

The Address and Employee classes are not tested here. Writing tests for them is up to you.

3.5.3. Packaging A Service

After testing your service, it is time to package it up so that others can use it. The simplest way to do this is to create a JAR containing all of the classes. You can choose to include the deployment descriptor if there is a sensible default way to configure the service, but this is optional.

Procedure 3.1. Packaging a Service

Place the deployment descriptor in the META-INF directory (optional)

If you do choose to include the deployment descriptor, by convention it should be named jboss-beans.xml and should be placed in a META-INF directory. This is the default layout for the Enterprise Platform, so the JAR deployer recognizes this layout and automatically performs the deployment.

The deployment descriptor is not included in the Human Resources example, because the service is configured by editing the descriptor directly, as a separate file.

Generate the JAR

To generate a JAR containing all of the compiled classes, enter mvn package from the humanResourcesService/ directory.

The client consists of three classes and one interface, located in the org/jboss/example/client directory.

UserInterface describes methods that the client calls at run-time to request user input. ConsoleInput is an implementation of UserInterface that creates a TUI which the user uses to interact with the client. The advantage of this design is that you can easily create a Swing implementation of UserInterface at a later date and replace the TUI with a GUI. You can also simulate the data-entry process with a script. Then you can check the behavior of the client automatically using conventional JUnit test cases found in Example 3.2, “Listing of the src/test/java Directory”.

For the build to work you must first build and install auditAspect.jar from the examples/User_Guide/gettingStarted/auditAspect directory using the mvn install command. A number of different client distributions are created, including one based on AOP which relies on auditAspect.jar being available in the local Maven repository.

If you previously typed mvn install from the examples/User_Guide/gettingStarted directory then humanResourcesService.jar and auditAspect.jar have already been built and packaged, along with the client, so this step will not be necessary.

Each sub-directory represents a different distribution with all of the shell scripts, JARs, and XML deployment descriptors needed to run the client in different configurations. The rest of this chapter uses the client-pojo distribution found in the client-pojo sub-directory, which is listed in Example 4.3, “Listing of the client-pojo Directory”.

To select an option, enter the letter shown on the left-hand side and press RETURN. For example to display the menu options enter m followed by RETURN. Entering more than one letter or entering an invalid option results in an error message.

Important

The run.sh script sets up the run-time environment by adding all of the JARs found in the lib/ directory to the classpath using the java.ext.dirs system property. It also adds the current directory and the client-1.0.0.jar using the -cp flag so that the jboss-beans.xml deployment descriptor can be found at run-time along with the org.jboss.example.client.Client class which is called to start the application.

4.1. Bootstrapping the Microcontainer

Before using the client to deploy and call your service, take a closer look at what happened during its construction:

First of all a URL representing the jboss-beans.xml deployment descriptor is created. This is later required so that the XML deployer can deploy and undeploy beans declared in the file. The getResource() method of the application classloader is used because the jboss-beans.xml file is included on the classpath. This is optional; the name and location of the deployment descriptor are unimportant as long as the URL is valid and reachable.

Next an instance of JBoss Microcontainer is created, along with an XML deployer. This process is called bootstrapping and a convenience class called BasicBootstrap is provided as part of the Microcontainer to allow for programmatic configuration. To add an XML deployer, extend BasicBootstrap to create an EmbeddedBootstrap class and override the protected bootstrap() method as follows:

The shutdown hook ensures that when the JVM exits, all of the beans are undeployed in the correct order. The public deploy/undeploy methods delegate to the BasicXMLDeployer so that beans declared in jboss-beans.xml can be deployed and undeployed.

Finally references to the Microcontainer controller and bus are restored, so you can look up bean references by name and access them directly or indirectly as necessary.

4.2. Deploying the Service

After creating the client, you can deploy the Human Resources service. This is done by entering the d option from the TUI. Output indicates that the BasicXMLDeployer has parsed the jboss-beans.xml file using the URL, and instantiated the beans found within.

Note

The Microcontainer is able to instantiate the beans because their classes are available in the extension classpath inside the lib/humanResourcesService.jar file. You can also place these classes in an exploded directory structure and add it to the application classpath, but packaging them in a JAR is typically more convenient.

The deployment descriptor is entirely separate from the humanResourcesService.jar file. This enables easy editing of it for testing purposes. The jboss-beans.xml file in the example contains some commented-out fragments of XML which show some of the possible configurations.

Depending on how you access the service at run-time, you may need to shut down the application and restart it again to redeploy the service and see your changes. This reduces the flexibility of the application, but results in faster performance at run-time. Alternatively you may be able to simply redeploy the service while the application is running. This increases flexibility but results in slower run-time performance. Keep these trade-offs under consideration when designing your applications.

4.3. Direct Access

If no parameters are given to the run.sh script when the client is started, a reference to the HRService bean is looked up using the Microcontainer controller after the service is deployed:

Rather than immediately looking up a reference to the bean instance, the example first looks up a reference to a ControllerContext, then obtains a reference to the bean instance from the context using the getTarget() method. The bean can exist within the Microcontainer in any of the states listed in States of a Bean Within the Microcontainer.

States of a Bean Within the Microcontainer

NOT_INSTALLED

DESCRIBED

INSTANTIATED

CONFIGURED

INSTALLED

To keep track of which state the bean is in, wrap it in another object called a context that describes the current state. The name of the context is the same as the bean name. Once a context reaches the INSTALLED state, the bean it represents is considered to be deployed.

After creating a reference to the bean instance representing the service entry point, you can call methods on it to preform work:

The client is accessing the service directly since it is using a reference to the actual bean instance. Performance is good, because each method call goes directly to the bean. What happens, however, if you want to reconfigure the service and redeploy it while the application is running?

Reconfiguration is achieved by making changes to the XML deployment descriptor and saving the file. In order to redeploy the service, the current instance must be undeployed. During undeployment the Microcontainer controller releases its reference to the bean instance, along with any dependent beans. These beans will subsequently become available for garbage collection because they are no longer required by the application. Redeploying the service creates new bean instances representing the new configuration. Any subsequent look-ups from clients will retrieve references to these new instances and they will be able to access the reconfigured service.

The problem is that the reference to the bean instance representing our service entry point is cached when you deploy the service for the first time. Undeploying the service has no affect, since the bean instance can still be accessed using the cached reference and it will not be garbage collected until the client releases it. Along the same line, deploying the service again will not cause another look-up because the client already has a cached reference. It will therefore continue to use the bean instance representing the initial service configuration.

You can test this behavior by typing u followed by RETURN to undeploy the current service. You should still be able to access the service from the client even though it is 'undeployed'. Next, make some changes to the configuration using the jboss-beans.xml file, save the file, and deploy it again using the d option. Printing out the status of the service using the p option shows that the client is still accessing the initial instance of the service that was deployed.

Warning

Even if you modify the client to look up a new reference each time the service is redeployed, new developers may hand out copies of this reference to other objects, by mistake. If all of these references are not cleaned up during redeployment, the same caching problem can occur.

To reliably redeploy the reconfigured service, shut down the application completely using the 'q' option and restart it again using the run.sh script. For enterprise services such as Transactions, Messaging and Persistence this is perfectly acceptable behavior, since they are generally always in use. They cannot be redeployed at run-time and also benefit from the high performance given by using direct access. If your service falls into this category, consider using direct access via the Microcontainer controller.

4.4. Indirect Access

The run.sh script can be called with an optional parameter bus, which causes calls to the Human Resources service to use the Microcontainer bus.

Instead of using a direct reference to the bean instance obtained from the Microcontainer controller, the new behavior is to call an invoke() method on the bus, passing in the bean name, method name, method arguments and method types. The bus uses this information to call the bean on the client's behalf.

The bus looks up the reference to the named bean instance and calls the chosen method using reflection. The client never has a direct reference to the bean instance, so it is said to accesses the service indirectly. Since the bus does not cache the reference, you can safely make changes to the service configuration, and it can be redeployed at run-time. Subsequent calls by the client will use the new reference, as expected. The client and service have been decoupled.

Note

This behavior can be tested by deploying the service and using the p option to print out the status. Undeploy the service using the u option and notice that it is inaccessible. Next, make some changes to jboss-beans.xml file, save the changes, and deploy it again using the d option. Print out the status again using the p option. The client is accessing the new service configuration.

Because the bus uses reflection to call bean instances, it is a slower than direct access. The benefit of the approach is that only the bus has references to the bean instances. When a service is redeployed, all of the existing references can be cleaned up and replaced with new ones. This way, a service can be reliably redeployed at run-time. Services that are not used very often or that are specific to certain applications are good candidates for indirect access using the Microcontainer bus. Often, the reduction in performance is outweighed by the flexibility that this provides.

4.5. Dynamic Classloading

So far you have used the extension and application classloaders to load all of the classes in the application. The application classpath is set up by the run.sh script using the -cp flag to include the current directory and the client-1.0.0.jar, as shown here:

For convenience the JARs in the lib directory were added to the extension classloader's classpath using the java.ext.dirs system property, rather than listing the full path to each of the JARs after the -cp flag. Because the classloader extension is the parent of the classloader application, the client classes is able to find all of the Microcontainer classes and the Human Resources service classes at run-time.

Note

With Java versions 6 and higher, you can use a wild-card to include all JARs in a directory with the -cp flag: java -cp `pwd`/lib/*:.:client-1.0.0.jar org.jboss.example.client.Client $1

Here, all of the classes in the application will be added to the application classloader's classpath, and the extension classloader's classpath will retain its default value.

What happens if you need to deploy an additional service at run-time? If the new service is packaged in a JAR file, it must be visible to a classloader before any of its classes can be loaded. Because you have already set up the classpath for the application classloader (and extension classloader) on start-up, it is not easy to add the URL of the JAR. The same situation applies if the service classes are contained in a directory structure. Unless the top-level directory is located in the current directory (which is on the application classpath) then the classes will not be found by the application classloader.

If you wish to redeploy an existing service, changing some of its classes, you need to work around security constraints, which forbid an existing classloader from reloading classes.

The goal is to create a new classloader that knows the location of the new service's classes, or that can load new versions of an existing service's classes, in order to deploy the service's beans. JBoss Microcontainer uses the <classloader> element in the deployment descriptor to accomplish this.

The humanResourcesService.jar file has been moved to a new sub-directory called otherLib. It is no longer available to either the extension or application classloaders whose classpaths are setup in the run.sh script:

To work around this, create a new classloader during the deployment of the service, load it in the service classes, and create instances of the beans. To see how this is done, look at the contents of the jboss-beans.xml file:

First, create an instance of java.net.URL called URL, using parameter injection in the constructor to specify the location of the humanResourcesService.jar file on the local file-system.

Next, create an instance of a URLClassLoader by injecting the URL bean into the constructor as the only element in an array.

Include a <classloader> element in your HRService bean definition and inject the customCL bean. This specifies that the HRManager class needs to be loaded by the customCL classloader.

You need a way to decide which classloader to use for the other beans in the deployment. All beans in the deployment use the current thread's context classloader. In this case the thread that handles deployment is the main thread of the application which has its context classloader set to the application classloader on start-up. If you wish, you can specify a different classloader for the entire deployment using a <classloader> element, as shown in Example 4.6, “Specifying a Different Classloader”.

This would be necessary to allow for reconfiguration of the service by uncommenting the AgeBasedSalary or LocationBasedSalary beans. Classloaders specified at the bean level override the deployment level classloader. To override the deployment level classloader altogether, and use the default classloader for a bean, use the <null/> value as follows:

4.5.1. Problems With Classloaders Created with Deployment Descriptors

If you create a new classloader for your service using the deployment descriptor, you may not be able to access classes loaded by it from the application classloader. In the HRManager example, the client is no longer able to cache a direct reference to the bean instance when using the Microcontainer controller.

To see this behavior, start the client using the run.sh command, then try to deploy the service. A java.lang.NoClassDefFoundError exception is thrown and the application exits.

In this scenario, you must use the bus to access the service indirectly and provide access to any classes shared by the client in the application classpath. In this example, the affected classes are Address, Employee, and SalaryStrategy.

Chapter 5. Adding Behavior with AOP

Object Oriented Programming (OOP) contains many useful techniques for software development including encapsulation, inheritance, and polymorphism. However, it does not solve the problem of addressing logic that is often repeated in many different classes. Examples of this include logging, security, and transactional logic which is traditionally hard-coded into each class. This type of logic is called a cross-cutting concern.

Aspect Oriented Programming (AOP) works to allow cross-cutting concerns to be applied to classes after they have been compiled. This keeps the source code free of logic which is not central to the main purpose of the class and streamlines maintenance. The method depends on the AOP implementation. Typically if a class implements an interface, each method call to an instance of the class first passes through a proxy. This proxy implements the same interface, adding the required behavior. Alternatively, if an interface is not used, then the java bytecode of the compiled class is modified: the original methods are renamed and replaced by methods that implement the cross-cutting logic. These new methods then call the original methods after the cross-cutting logic has been executed. Another method to achieve the same result is modifying the bytecode to create a subclass of the original class that overrides its methods. The overridden methods then execute the cross-cutting logic before calling the corresponding methods of the super class.

JBoss AOP is a framework for AOP. Using it, you can create cross-cutting concerns using conventional java classes and methods. In AOP terminology each concern is represented by an aspect that you implement using a simple POJO. Behavior is provided by methods within the aspect called advices. These advices follow certain rules for their parameter, and return types and any exceptions that they throw. Within this framework, you can use conventional object-oriented notions such as inheritance, encapsulation, and composition to make your cross-cutting concerns easy to maintain. Aspects are applied to code using an expression language that allows you to specify which constructors, methods and even fields to target. You can quickly change the behavior of multiple classes by editing a configuration file.

This chapter contains examples which demonstrate how to use JBoss AOP alongside the Microcontainer to create and apply an auditing aspect to the Human Resources Service. The auditing code could be placed within the HRManager class, but it would clutter the class with code which is not relevant to its central purpose, bloating it and making it harder to maintain. The design of the aspect also provide modularity, making it easy to audit other classes in the future, if the scope of the project changes.

AOP can also be used to apply additional behavior during the deployment phase. This example will create and bind a proxy to a bean instance into a basic JNDI service, allowing it to be accessed using a JNDI look-up instead of the Microcontainer controller.

5.1. Creating An Aspect

The examples/User_Guide/gettingStarted/auditAspect directory contains all the files needed to create the aspect.

The constructor checks for the presence of a log directory in the current working directory, and creates one if not found.

Next, an advice is defined. This advice is called whenever the constructor of the target class is called. This creates a new log file within the log directory to record method calls made on different instances of the target class in separate files.

Finally, another advice is defined. This advice applies to each method call made on the target class.The method name and arguments are stored, along with the return value. This information is used to construct an audit record and write it to the current log file. Each advice calls inv.invokeNext(), which chains the advices together if more than one cross-cutting concern has been applied, or to call the target constructor/method.

Note

Each advice is implemented using a method that takes an invocation object as a parameter, throws Throwable and returns Object. At design time you do not know which constructors or methods these advices will be applied to, so make the types as generic as possible.

To compile the class and create an auditAspect.jar file that can be used by other examples, type mvn install from the auditAspect directory.

5.2. Configuring the Microcontainer for AOP

Before applying the audit aspect to the HR Service, a number of JARs must be added to the extension classpath. They are in the lib sub-directory of the client-aop distribution located in the examples/User_Guide/gettingStarted/commandLineClient/target/client-aop.dir directory:

First, lib/auditAspect-1.0.0.jar is required to create an instance of the aspect at run-time, in order to execute the logic. Next the jar file for JBoss AOP (jboss-aop.jar), along with its dependencies javassist and trove, adds the AOP functionality. Finally, the jboss-aop-mc-int jar is required because it contains an XML schema definition that allows you to define aspects inside an XML deployment descriptor. It also contains integration code to create dependencies between normal beans and aspect beans within the Microcontainer, allowing you to add behavior during the deployment and undeployment phases.

Because you are using Maven2 to assemble the client-aop distribution, you should add these JAR files by declaring the appropriate dependencies in your pom.xml file and creating a valid assembly descriptor. A sample pom.xml snippet is shown in Example 5.3, “Example pom.xml Excerpt for AOP”. To perform your build using Ant, the procedure will be different.

5.3. Applying An Aspect

Now that you have a valid distribution containing everything you need, you can configure jboss-beans.xml to apply the audit aspect. It is in examples/User_Guide/gettingStarted/commandLineClient/target/client-aop.dir.

Before you can apply your aspect to any classes, you need to create an instance of org.jboss.aop.AspectManager using a <bean> element. A factory method is used here instead of calling a conventional constructor, since only one instance of the AspectManager in the JVM is necessary at run-time.

Next an instance of our aspect called AuditAspect is created, using the <aop:aspect> element. This looks similar to the <bean> element because it has name and class attributes that are used in the same way. However it also has method and pointcut attributes that you can use to apply or bind an advice within the aspect to constructors and methods within other classes. These attributes bind the audit advice to all public constructors and methods within the HRManager class. Only the audit method needs to be specified, since it has been overloaded within the AuditAspect class with different parameters. JBoss AOP knows at run-time which to select, depending on whether a constructor or method invocation is being made.

This additional configuration is all that is needed to apply the audit aspect at run-time, adding auditing behavior to the Human Resources service. You can test this by running the client using the run.sh script. A log directory is created on start-up alongside the lib directory when the AuditAspect bean is created by the Microcontainer. Each deployment of the Human Resources service causes a new log file to appear within the log directory. The log file contains a record of any calls made from the client to the service. It is named something similar to auditLog-28112007-163902, and contains output similar to Example 5.4, “Example AOP Log Output”.

To remove the auditing behavior, comment out the relevant fragments of XML in the deployment descriptor and restart the application.

Warning

The order of deployment matters. Specifically each aspect must be declared before the beans that it applies to, so that the Microcontainer deploys them in that order. This is because the Microcontainer may need to alter the bytecode of the normal bean class to add the cross-cutting logic, before it creates an instance and stores a reference to it in the controller. If a normal bean instance has already been created, this is not possible.

5.4. Life cycle callbacks

In addition to applying aspects to beans that we instantiate using the Microcontainer we can also add behavior during the deployment and undeployment process. As mentioned in Section 4.3, “Direct Access”, a bean goes through several different states as it is deployed. These include:

NOT_INSTALLED

the deployment descriptor containing the bean has been parsed, along with any annotations on the bean itself.

DESCRIBED

any dependencies created by AOP have been added to the bean, and custom annotations have been processed.

INSTANTIATED

an instance of the bean has been created.

CONFIGURED

properties have been injected into the bean, along with any references to other beans.

CREATE

the create method, if defined in the bean, has been called.

START

the start method, if defined in the bean, has been called.

INSTALLED

any custom install actions that were defined in the deployment descriptor have been executed and the bean is ready to access.

Important

The CREATE and START states are included for legacy purposes. This allows services that were implemented as MBeans in previous versions of the Enterprise Platform to function correctly when implemented as beans in the Enterprise Platform 5.1. If you do not define any corresponding create/start methods in your bean, it will pass straight through these states.

These states represent the bean's life cycle. You can define a number of callbacks to be applied to any point by using an additional set of <aop> elements:

<aop:lifecycle-describe>

applied when entering/leaving the DESCRIBED state

<aop:lifecycle-instantiate>

applied when entering/leaving the INSTANTIATED state

<aop:lifecycle-configure>

applied when entering/leaving the CONFIGURED state

<aop:lifecycle-create>

applied when entering/leaving the CREATE state

<aop:lifecycle-start>

applied when entering/leaving the START state

<aop:lifecycle-install>

applied when entering/leaving the INSTALLED state

Like the <bean> and <aop:aspect> elements, the <aop:lifecycle-> elements contain name and class attributes. The Microcontainer uses these attributes to create an instance of the callback class, naming it so that it can be used as beans enter or leave the relevant state during deployment and undeployment. You can specify which beans are affected by the callback using the classes attribute, as shown in Example 5.5, “Using the classes Attribute”.

This code specifies that additional logic in the lifecycleCallback class is applied to any bean classes that are annotated with @org.jboss.test.microcontainer.support.Install before they enter and after they leave the INSTALLED state.

The install method is called during the bean's deployment, and the uninstall method during its undeployment.

Note

Although behavior is being added to the deployment and undeployment process using callbacks, AOP is not actually used here. The pointcut expression functionality of JBoss AOP is used to determine which bean classes the behaviors apply to.

5.5. Adding Service Look-ups Through JNDI

Until now, you have used the Microcontainer to look up references to bean instances which represent services. This is not ideal, because it requires a reference to the Microcontainer kernel before the controller can be accessed. This is shown in Example 5.7, “Looking Up References To Beans”.

Handing out kernel references to every client that looks up a service is a security risk, because it provides wide-spread access to the Microcontainer configuration. For better security, apply the ServiceLocator pattern and use a class to performs look-ups on behalf of the clients. Even better, pass the bean references, along with their names, to the ServiceLocator at deployment time, using a life-cycle callback. In that scenario, the ServiceLocator can look them up without knowing about the Microcontainer at all. Undeployment would subsequently remove the bean references from the ServiceLocator to prevent further look-ups.

It would not be difficult to write your own ServiceLocator implementation. Integrating an existing one such as JBoss Naming Service (JBoss NS) is even quicker, and has the additional benefit of complying to the Java Naming and Directory Interface (JNDI) specification. JNDI enables clients to access different, possibly multiple, naming services using a common API.

Procedure 5.3. Writing Your Own ServiceLocator Implementation

First, create an instance of JBoss NS using the Microcontainer.

Next, add a life-cycle callback to perform the binding and unbinding of the bean references during deployment and undeployment.

Mark the bean classes you wish to bind references for, using annotations.

Now, you can locate the beans at run-time using the shorthand pointcut expression as shown earlier.

Part II. Advanced Concepts with the Microcontainer

This section covers advanced concepts, and shows some interesting features of the Microcontainer. Code examples in the rest of the guide are assumed to be incomplete examples, and it is the programmer's responsibility to extrapolate and extend them as necessary.

The JBoss Microcontainer works within several popular POJO component models. Components are reusable software programs that you can develop and assemble easily to create sophisticated applications. Effective integration with these component models was a key goal for the Microcontainer. Some popular component models which can be used with the Microcontainer are JMX, Spring, and Guice.

6.1. Allowable Interactions with Component Models

Before discussing interaction with some of the popular component models, it is important to understand which types of interactions are allowable. JMX MBeans are one example of a component model. Their interactions include executing MBean operations, referencing attributes, setting attributes and declaring explicit dependencies between named MBeans.

The default behaviors and interactions in the Microcontainer are what you also normally get from any other Inversion of Control (IoC) container and are similar to the functionality provided by MBeans, including plain method invocations for operations, setters/getters for attributes and explicit dependencies.

This file's namespace is different from the plain Microcontainer bean’s file. The urn:jboss:spring-beans:2.0 namespace points to your version of the Spring schema port, which describes your bean's Spring style. The Microcontainer, rather than Spring's bean factory notion, deploys the beans.

To deploy MBeans deployment via the Microcontainer, you must write an entirely new handler for the component model. See system-jmx-beans.xml for more details. The code from this file lives in the JBoss Application Server source code: system-jmx sub-project.

You can use any of the injection look-up types, by either looking up a plain POJO or getting a handle to an MBean from the MBean server. One of the injection options is to use type injection, sometimes called autowiring, and shown in Example 6.10, “Autowiring”.

The FromGuice bean injects the Guice bean via type matching, where PlainPojo is injected with a common name injection. Now, you can test if Guice binding works as expected, as shown in Example 6.11, “Testing Guice Functionality”.

This descriptor maps the SpringPojo name to the springPojo alias. The benefit of aliases as true component models is that timing of bean deployment becomes less important. The alias waits in a non-installed state until the real bean triggers it.

Today, Dependency injection (DI), also called Inversion of Control (IoC), lies at the core of many frameworks that embrace the notion of a container or a component model. Component models were discussed in a previous chapter. JBoss JMX kernel, the precursor to the Microcontainer, provided only lightweight DI/IoC support, primarily due to the limitations of accessing MBeans through the MBeans server. However with the new POJO-based component model, several new and interesting features are available.

This chapter shows how you can apply different DI concepts with the help of the JBoss Microcontainer. These concepts will be expressed via XML code, but you can also apply most of these features using annotations.

publicenum BeanAccessMode {
STANDARD(BeanInfoCreator.STANDARD), // Getters and SettersFIELDS(BeanInfoCreator.FIELDS), // Getters/Setters and fields without getters and settersALL(BeanInfoCreator.ALL); // As above but with non public fields included}

7.4. Bean Alias

Each bean can have any number of aliases. Since Microcontainer component names are treated as Objects, the alias type is not limited. By default a system property replacement is not done; you need to set the replace flag explicitly, as shown in Example 7.9, “A Simple Bean Alias”.

7.5. XML (or MetaData) Annotations Support

AOP support is a prime feature in JBoss Microcontainer. You can use AOP aspects and plain beans in any combination. Example 7.10, “Intercepting a Method based on Annotation” attempts to intercept a method invocation based on an annotation. The annotation can come from anywhere. It might be a true class annotation or an annotation added through the xml configuration.

In both cases - ShapeUser and ShapeChecker - only Circle should be used, since Square is excluded in the contextual binding.

7.7. Bean Factory

When you want more than one instance of a particular bean, you need to use the bean factory pattern. The job of the Microcontainer is to configure and install the bean factory as if it were a plain bean. Then you need to invoke the bean factory's createBean method.

By default, the Microcontainer creates a GenericBeanFactory instance, but you can configure your own factory. The only limitation is that its signature and configuration hooks are similar to the one of AbstractBeanFactory.

7.9. Custom ClassLoader

In the Microcontainer you can define a custom ClassLoader per bean. When defining a classloader for the whole deployment, make sure you do not create a cyclic dependency -- for instance, a newly defined classloader that depends on itself.

7.10. Controller Mode

By default, the Microcontainer uses the AUTO controller mode. It pushes beans as far as they go with respect to dependencies. But there are two other modes: MANUAL and ON_DEMAND.

If the bean is marked as ON_DEMAND, it will not be used or installed installed until some other bean explicitly depends on it. In MANUAL mode, the Microcontainer user must push the bean forward and backward along the state ladder.

Using the fromContext attribute of the inject class, you can inject beans, as well as their unmodifiable Microcontainer component representation.

Review the code of OptionalServiceUser and ManualServiceUser for how to use the Microcontainer API for ON_DEMAND and MANUAL bean handling.

7.11. Cycle

Beans may depend on each other in a cycle. For instance, A depends on B at construction, but B depends on A at setter. Because of the Microcontainer’s fine-grained state life cycle separation, this problem can be solved easily.

7.12. Demand and Supply

Sometimes, such as with an injection, a dependency between two beans may not be readily apparent. Such dependencies should be expressed in a clear way, such as shown in Example 7.22, “Static Code Usage”.

7.13. Installs

As a bean moves through different states, you might want to invoke some methods on other beans or the same bean. Example 7.23, “Invoking Methods in Different-States” shows how Entry invokes RepositoryManager's add and removeEntry methods to register and unregister itself.

7.14. Lazy Mock

You might have a dependency on a bean that is rarely used, but takes a long time to configure. You can use a lazy mock of the bean, demonstrated in Example 7.24, “Lazy Mock”, to resolve the dependency. When you actually need the bean, invoke and use the target bean, hoping it has been installed by then.

7.15. Life cycle

By default the Microcontainer uses create, start, and destroy methods when it moves through the various states. However, you may not want the Microcontainer to invoke them. For this reason, an ignore flag is available.

Chapter 8. The Virtual File System

Duplication of resource-handling code is a common problem for developers. In most cases, the code deals with determining information about a particular resource, which might be a file, a directory, or, in the case of a JAR, a remote URL. Another duplication problem is code for the processing of nested archives. Example 8.1, “Resource Duplication Problem” illustrates the problem.

There are also many problems with file locking on Windows systems, forcing developers to copy all hot-deployable archives to another location to prevent locking those in deploy folders (which would prevent their deletion and file-system based undeploy). File locking is a major problem whose only solution used to be centralizing all the resource loading code in one place.

The VFS project was created solve all of these issues. VFS stands for Virtual File System.

8.1. VFS Public API

As mentioned, in plain JDK, handling and navigating resources are complex. You must always check the resource type, and these checks can be cumbersome. VFS abstracts resources into a single resource type, VirtualFile.

Example 8.2. The VirtualFile Resource Type

publicclass VirtualFile implements Serializable {
/** * Get certificates. * * @return the certificates associated with this virtual file */
Certificate[] getCertificates()
/** * Get the simple VF name (X.java) * * @return the simple file name * @throws IllegalStateException if the file is closed */
String getName()
/** * Get the VFS relative path name (org/jboss/X.java) * * @return the VFS relative path name * @throws IllegalStateException if the file is closed */
String getPathName()
/** * Get the VF URL (file://root/org/jboss/X.java) * * @return the full URL to the VF in the VFS. * @throws MalformedURLException if a url cannot be parsed * @throws URISyntaxException if a uri cannot be parsed * @throws IllegalStateException if the file is closed */
URL toURL() throws MalformedURLException, URISyntaxException
/** * Get the VF URI (file://root/org/jboss/X.java) * * @return the full URI to the VF in the VFS. * @throws URISyntaxException if a uri cannot be parsed * @throws IllegalStateException if the file is closed * @throws MalformedURLException for a bad url */
URI toURI() throws MalformedURLException, URISyntaxException
/** * When the file was last modified * * @return the last modified time * @throws IOException for any problem accessing the virtual file system * @throws IllegalStateException if the file is closed */longgetLastModified() throws IOException
/** * Returns true if the file has been modified since this method was last called * Last modified time is initialized at handler instantiation. * * @return true if modifed, false otherwise * @throws IOException for any error */booleanhasBeenModified() throws IOException
/** * Get the size * * @return the size * @throws IOException for any problem accessing the virtual file system * @throws IllegalStateException if the file is closed */longgetSize() throws IOException
/** * Tests whether the underlying implementation file still exists. * @return true if the file exists, false otherwise. * @throws IOException - thrown on failure to detect existence. */booleanexists() throws IOException
/** * Whether it is a simple leaf of the VFS, * i.e. whether it can contain other files * * @return true if a simple file. * @throws IOException for any problem accessing the virtual file system * @throws IllegalStateException if the file is closed */booleanisLeaf() throws IOException
/** * Is the file archive. * * @return true if archive, false otherwise * @throws IOException for any error */booleanisArchive() throws IOException
/** * Whether it is hidden * * @return true when hidden * @throws IOException for any problem accessing the virtual file system * @throws IllegalStateException if the file is closed */booleanisHidden() throws IOException
/** * Access the file contents. * * @return an InputStream for the file contents. * @throws IOException for any error accessing the file system * @throws IllegalStateException if the file is closed */
InputStream openStream() throws IOException
/** * Do file cleanup. * * e.g. delete temp files */voidcleanup()
/** * Close the file resources (stream, etc.) */voidclose()
/** * Delete this virtual file * * @return true if file was deleted * @throws IOException if an error occurs */booleandelete() throws IOException
/** * Delete this virtual file * * @param gracePeriod max time to wait for any locks (in milliseconds) * @return true if file was deleted * @throws IOException if an error occurs */booleandelete(int gracePeriod) throws IOException
/** * Get the VFS instance for this virtual file * * @return the VFS * @throws IllegalStateException if the file is closed */
VFS getVFS()
/** * Get the parent * * @return the parent or null if there is no parent * @throws IOException for any problem accessing the virtual file system * @throws IllegalStateException if the file is closed */
VirtualFile getParent() throws IOException
/** * Get a child * * @param path the path * @return the child or <code>null</code> if not found * @throws IOException for any problem accessing the VFS * @throws IllegalArgumentException if the path is null * @throws IllegalStateException if the file is closed or it is a leaf node */
VirtualFile getChild(String path) throws IOException
/** * Get the children * * @return the children * @throws IOException for any problem accessing the virtual file system * @throws IllegalStateException if the file is closed */
List<VirtualFile> getChildren() throws IOException
/** * Get the children * * @param filter to filter the children * @return the children * @throws IOException for any problem accessing the virtual file system * @throws IllegalStateException if the file is closed or it is a leaf node */
List<VirtualFile> getChildren(VirtualFileFilter filter) throws IOException
/** * Get all the children recursively<p> * * This always uses {@link VisitorAttributes#RECURSE} * * @return the children * @throws IOException for any problem accessing the virtual file system * @throws IllegalStateException if the file is closed */
List<VirtualFile> getChildrenRecursively() throws IOException
/** * Get all the children recursively<p> * * This always uses {@link VisitorAttributes#RECURSE} * * @param filter to filter the children * @return the children * @throws IOException for any problem accessing the virtual file system * @throws IllegalStateException if the file is closed or it is a leaf node */
List<VirtualFile> getChildrenRecursively(VirtualFileFilter filter) throws IOException
/** * Visit the virtual file system * * @param visitor the visitor * @throws IOException for any problem accessing the virtual file system * @throws IllegalArgumentException if the visitor is null * @throws IllegalStateException if the file is closed */voidvisit(VirtualFileVisitor visitor) throws IOException
}

All of the usual read-only File System operations are available, plus a few options to cleanup or delete the resource. Cleanup or deletion handling is needed when dealing with some internal temporary files, such as files created to handle nested jars.

To switch from the JDK's File or URL resource handling to new VirtualFile you need a root VirtualFile, which is provided by the VFS class, with the help of URL or URI parameter.

getVFS returns a VFS instance but does not yet create a VirtualFile instance. This is important because there are methods which help with configuring a VFS instance (see VFS class API javadocs), before instructing it to create a VirtualFile root.

The other two methods, on the other hand, use default settings for root creation. The difference between createNewRoot and getRoot is in caching details, which are covered later.

Another useful thing about VFS API is its implementation of a proper visitor pattern. It is very simple to recursively gather different resources, a task which is difficult to do with plain JDK resource loading.

8.2. VFS Architecture

While the public API is quite intuitive, real implementation details add complexity. Some concepts need to be explained in more detail.

Each time you create a VFS instance, its matching VFSContext instance is created. This creation is done via VFSContextFactory. Different protocols map to different VFSContextFactory instances. For example, file/vfsfile maps to FileSystemContextFactory, while zip/vfszip maps to ZipEntryContextFactory.

Each time a VirtualFile instance is created, its matching VirtualFileHandler is created. This VirtualFileHandler instance knows how to handle different resource types properly; the VirtualFile API only delegates invocations to its VirtualFileHandler reference.

The VFSContext instance knows how to create VirtualFileHandler instances accordingly to a resource type. For example, ZipEntryContextFactory creates ZipEntryContext, which then creates ZipEntryHandler.

8.3. Existing Implementations

Apart from files, directories (FileHandler) and zip archives (ZipEntryHandler) the Microcontainer also supports other more advanced use cases. The first one is Assembled, which is similar to what Eclipse calls Linked Resources. Its purpose is to take existing resources from different trees, and "mock" them into single resource tree.

Another implementation is in-memory files. This implementation arose out of a need to easily handle AOP generated bytes. Instead of using temporary files, you can drop bytes into in-memory VirtualFileHandlers.

8.4. Extension Hooks

It is easy to extend VFS with a new protocol, similar to what we've done with Assembled and Memory. All you need is a combination of VFSContexFactory, VFSContext, VirtualFileHandler, FileHandlerPlugin, and URLStreamHandler implementations. The VFSContextFactory is trivial, while the others depend on the complexity of your task. You could implement rar, tar, gzip, or even remote access.

After implementing a new protocol, register the new VFSContextFactory with VFSContextFactoryLocator.

8.5. Features

One of the first major problems the Microcontainer developers faced was proper usage of nested resources, more specifically nested jar files: For example, normal ear deployments: gema.ear/ui.war/WEB-INF/lib/struts.jar.

In order to read contents of struts.jar we have two options:

handle resources in memory

create top level temporary copies of nested jars, recursively

The first option is easier to implement, but it's very memory-consuming, requiring potentially large applications to reside in memory. The other approach leaves behind a large number of temporary files, which should be invisible to the end user and therefore should disappear after undeployment.

Consider the following scenario: A user accesses a VFS URL instance, which points to some nested resource.

The way plain VFS would handle this is to re-create the entire path from scratch: it would unpack nested resources over and over again. This leads to a great number of temporary files.

The Microcontainer avoids this by using VFSRegistry, VFSCache, and TempInfo.

When you ask for VirtualFile over VFS (getRoot, not createNewRoot), VFS asks the VFSRegistry implementation to provide the file. The existing DefaultVFSRegistry first checks if a matching root VFSContext exists for the provided URI. If it does, DefaultVFSRegistry first tries to navigate to the existing TempInfo (link to a temporary files), falling back to regular navigation if no such temporary file exists. In this way you completely reuse any temporary files which have already been unpacked, saving time and disk space. If no matching VFSContext is found in cache, the code will create a new VFSCache entry and continue with default navigation.

Determining how the VFSCache handles cached VFSContext entries depends on the implementation used. VFSCache is configurable via VFSCacheFactory. By default, nothing is cached, but there are a few useful existing VFSCache implementations, using algorithms such as Least Recently Used (LRU) or timed cache.

Chapter 9. The ClassLoading Layer

JBoss has always had a unique way of dealing with classloading, and the new classloading layer that comes with the Microcontainer is no exception ClassLoading is an optional add-on that you can use when you want non-default classloading. With the rising demand for OSGi-style classloading, and a number of new Java classloading specifications on the horizon, the changes to the ClassLoading layer of EAP 5.1 are useful and timely.

The Microcontainer ClassLoading layer is an abstraction layer. Most of the details are hidden behind private and package-private methods, without compromising the extensibility and functionality available through public classes and methods that make the API. This means that you code against policy and not against classloader details.

The ClassLoader project is split into 3 sub-projects

classloader

classloading

classloading-vfs

classloader contains a custom java.lang.ClassLoader extension without any specific classloading policy. A classloading policy includes knowledge of where to load from and how to load.

Classloading is an extension of Microcontainer’s dependency mechanisms. Its VFS-backed implementation is classloading-vfs. See Chapter 8, The Virtual File System for more information on VFS.

9.1. ClassLoader

The ClassLoader implementation supports pluggable policies and is itself a final class, not meant to be altered. To write your own ClassLoader implementations, write a ClassLoaderPolicy which provides a simpler API for locating classes and resources, and for specifying other rules associated with the classloader.

To customize classloading, instantiate a ClassLoaderPolicy and register it with a ClassLoaderSystem to create a custom ClassLoader. You can also create a ClassLoaderDomain to partition the ClassLoaderSystem.

The ClassLoader layer also includes the implementation of things like DelegateLoader model, classloading, resource filters, and parent-child delegation policies.

The run-time is JMX enabled to expose the policy used for each classloader. It also provides classloading statistics and debugging methods to help determine where things are loaded from.

The regexp service uses the regular expression pattern config/[^.]+\\.[^.]{1,4} to list resources under the config// directory. The suffix length is limited, such that file names such as excluded.properties will be ignored.

This service prints out the contents of two configuration files. It shows that decryption of any encrypted resources is hidden behind the classloading layer.

To properly test this, either encrypt the policy module yourself or use an existing encrypted one. To put this into action, you need to properly tie EncryptedService to ClassLoaderSystem and deployers.

Partitioning ClassLoaderSystem is discussed later in this chapter.

9.2. ClassLoading

Instead of using the ClassLoader abstraction directly, you can create ClassLoading modules which contain declarations of ClassLoader dependencies. Once the dependencies are specified the ClassLoaderPolicys are constructed and wired together accordingly.

To facilitate defining the ClassLoaders before they actually exist, the abstraction includes a ClassLoadingMetaData model.

The ClassLoadingMetaData can be exposed as a Managed Object within the new JBoss EAP profile service. This helps system administrators to deal with more abstract policy details rather than the implementation details.

You can also mix the requirements and capabilities types, using packages and modules.

The classloading sub-project uses a very small resource-visitor-pattern implementation.

In the ClassLoader project, the connection between deployment and classloading is done through the Module class, which holds all of the required information to properly apply restrictions on the visitor pattern, such as filtering.

To use the module, instantiate your ResourceVisitor instance and pass it to Module::visit method. This feature is used in the deployment framework to index annotations usage in deployments.

9.3. ClassLoading VFS

These examples provide an implementation of the ClassLoaderPolicy that uses a JBoss Virtual File System project to load classes and resources. You can use this idea directly or in combination with a classloading framework.

Optionally, you can set up your modules inside the Microcontainer configuration.

The VFSClassLoaderFactory class transforms the XML deployer into a VFSClassLoaderPolicyModule, which then creates the actual ClassLoader instance. You can then directly use this new ClassLoader instance with your beans.

Note

VFSClassLoaderFactory extends ClassLoadingMetaData, so all examples pertaining to ClassLoadingMetaData apply in this case as well.

The new Virtual Deployment Framework (VDF) is an improved way to manage deployers in the Microcontainer. This chapter details some of its useful features.

10.1. Agnostic Handling of Deployment Types

The traditional type of virtual deployment is based on classes which already exist in a shared class-space or domain. In this case, the end product is a new service installed onto the server from your main client. The traditional way to do this is to upload a descriptor file. The new VDF simplifies this process by passing over the bytes and serializing them into a Deployment instance.

The other type of deployment, which extends the first one, is a plain file-system-based deployment, backed up by Microcontainer VFS. This approach is described in more detail in Chapter 8, The Virtual File System.

In order to do any real work on top of a deployment, you must first understand its structure, including its classpaths and metadata locations.

Metadata locations include the configuration files such as my-jboss-beans.xml, web.xml, ejb-jar.xml. Classpaths are classloader roots, such as WEB-INF/classes or myapp.ear/lib.

Bearing the structure in mind, you can proceed to actual deployment handling.

A typical deployment life cycle

The MainDeployer passes the deployment to the set of StructuralDeployers for recognition, and receives back a Deployment context.

Next, the MainDeployer passes the resulting Deployment context to the Deployers for handling by the appropriate Deployer.

In this way, the MainDeployer is a broker with the responsibility of deciding which Deployers to use.

In the case of virtual or programmatic deployment, an existing predetermined StructureMetaData information reads the structure information and handles it in one of the ways explained in Handling StructuredMetaData Information.

Handling StructuredMetaData Information

VFS-based deployments

the structure recognition is forwarded to a set of StructureDeployers.

JEE-specification-defined structures

we have matching StructureDeployer implementations:

EarStructure

WarStructure

JarStructure

DeclarativeStructures

looks for META-INF/jboss-structure.xml file inside your deployment, and parses it to construct a proper StructureMetaData.

FileStructures

only recognizes known configuration files, such as files like -jboss-beans.xml or -service.xml.

Inside Deployers, the deployment is converted into the Microcontainer's component DeploymentControllerContext. The Microcontainer's state machine handles dependencies.

Deployments are handled sequentially by deployment stage. For each deployer, the entire deployed hierarchy order is handled, using the deployer's parent-first property. This property is set to true by default.

You can also specify which hierarchy levels your deployer handles. You can choose all, top level, components only, or no components.

The way the Microcontainer handles component models and dependency handling applies here as well. If there are unresolved dependencies, the deployment will wait in the current state, potentially reporting an error if the current state is not the required state.

Adding a new deployer is accomplished by extending one of the many existing helper deployers.

Some deployers actually need VFS backed deployment, while others can use a general deployment. In most cases the parsing deployers are the ones that need VFS backing.

Warning

Also be aware that deployers run recursively through every deployment, sub-deployment, and component. Your code needs to determine, as early in the process as possible, whether the deployer should handle the current deployment or not.

Example 10.3. Simple Deployer which Outputs Information About Its Deployment

Add this description into one of the -jboss-beans.xml files in deployers/ directory in JBoss Application Server, and MainDeployerImpl bean will pick up this deployer via the Microcontainer's IoC callback handling.

Example 10.4. Simple Deployment Descriptor

<bean name="StdioDeployer" class="org.jboss.acme.StdioDeployer"/>

10.3. Natural Flow Control in the form of Attachments

VDF includes a mechanism called attachments, which facilitates the passing of information from one deployer to the next. Attachments are implemented as a slightly-enhanced java.util.Map, whose entries each represent an attachment.

Some deployers are producers, while others are consumers. The same deployer can also perform both functions. Some deployers create metadata or utility instances, putting them into the attachments map. Other deployers only declare their need for these attachments and pull the data from the attachments map, before doing additional work on that data.

Natural order refers to the way that deployers are ordered. A common natural order uses the relative terms before and after. However, with the attachments mechanism already in place, you can order deployers by the way in which they produce and/or consume the attachments.

Each attachment has a key, and deployers pass keys to the attachments they produce. If the deployer produces an attachment, then that key is called output. If the deployer consumes an attachment, then that key is called input.

Deployers have ordinary inputs and required inputs. Ordinary inputs are only used to help determine the natural order. Required inputs also help determine order, but they have another function too. They help to determine if the deployer is actually relevant for the given deployment, by checking to see if an attachment corresponding to that required input exists in the attachments map.

Warning

While relative ordering is still supported, it is considered bad practice, and could be deprecated in future releases.

10.4. Client, User, and Server Usage and Implementation Details

These features hide the implementation details, making the usage less error-prone, while at the same time streamlining the development process.

The goal is for clients to only see a Deployment API, while developers see a DeploymentUnit, and server implementation details are contained in a DeploymentContext. Only the necessary information is exposed to a particular level of deployment's life cycle.

Components have already been mentioned as part of deployers' hierarchy handling. While top level deployment and sub-deployments are a natural representation of the deployment's structure hierarchy, components are a new VDF concept. The idea of components is that they have a 1:1 mapping with the ControllerContexts inside the Microcontainer. See Why Components Map 1:1 with the ControllerContexts for the reasons behind this assertion.

Why Components Map 1:1 with the ControllerContexts

Naming

The component unit's name is the same as the ControllerContext's name.

get*Scope() and get*MetaData()

return the same MDR context that will be used by the Microcontainer for that instance.

IncompleteDeploymentException (IDE)

In order for the IDE to print out what dependencies are missing for a deployment, it needs to know the ControllerContext names. It discovers the name by collecting the Component DeploymentUnit's names in Component Deployers that specify them, such as BeanMetaDataDeployer or the setUseUnitName() method in AbstractRealDeployer.

10.5. Single State Machine

All Microcontainer components are handled by a single entry point, or single state machine. Deployments are no exception.

You can take advantage of this feature by using the jboss-dependency.xml configuration file in your deployments.

(1) shows how to describe dependency on another service. This example requires TransactionManager to be created before the deployment is in the 'Real' stage.

(2) is a bit more complex, since you are missing additional information. By default, deployment names inside the Microcontainer are URI names, which makes typing them by hand an error prone proposition. So, in order to be able to easily declare dependence on other deployments, you need an aliasing mechanism to avoid URI names. You can add a plain text file named aliases.txt into your deployment. Each line of the file contains an alias, giving a deployment archive one or more simple names used to refer to it.

10.6. Scanning Classes for Annotations

Current JEE specifications reduce the number of configuration files, but the container is now required to do most of the work using @annotations. In order to get @annotation info, containers must scan classes. This scanning creates a performance penalty.

But to reduce the amount of scanning, the Microcontainer provides another descriptor hook, by means of jboss-scanning.xml.