cornercorner
FeaturesPluginsDocs & SupportCommunityPartners

MergedDDModelGuide

Revision as of 13:23, 5 November 2009 by Admin (Talk | contribs)
(diff) ← Older revision | Current revision (diff) | Newer revision → (diff)

Java EE metadata model user's guide

This is a guide for users of the Java EE metadata model. It focuses on the generic MetadataModel class, but it also gives examples of concrete models.

See also MergedDDModel for information on implementing concrete metadata models.

Where to get it

See MergedDDModel.

Model architecture

First an example. This is how a client would retrieve the count of entity classes in a persistence unit:

// scope is a PersistenceScope
// puName is a persistence unit name (String)
MetadataModel<EntityMappingsMetadata> emModel = scope.getEntityMappingsModel(puName);
int count = emModel.runReadAction(new MetadataModelAction<EntityMappingsMetadata, int>() {
    public int run(EntityMappingsMetadata metadata) {
        EntityMappings em = metadata.getRoot();
        Entity[] entity = em.getEntity();
        return entity.length;
    }
});

Any model is encapsulated by the MetadataModel class. This is a generic class not tied in any way to any model (web, EJB, JPA, etc.). It has a type parameter whose value is a model-specific class or interface. For the JPA model there is an interface called EntityMappingsMetadata. Thus when a client retrieves a JPA metadata model, he will hold an instance of MetadataModel<EntityMappingsMetadata>.

The model-specific class should be named according to the model's root interface (WebAppMetadata, EjbJarMetadata, EntityMappingsMetadata, etc.). The class should have at least a getRoot() method returning the model's root interface:

public interface EntityMappingsMetadata {

    EntityMappings getRoot();
}

For a web model, the interface will be called WebAppMetadata and thus a MetadataModel<WebAppMetadata> will be given to the client.

Models will be retrieved by methods in the classes representing Java EE modules (WebModule, EjbJar) or JPA persistence scopes (PersistenceScope). For example a web model will be retrieved from the WebModule class:

public class WebModule {

    // ...

    MetadataModel<WebAppMetadata> getMetadataModel();

    // ...
}

The MetadataModel class has methods which clients call to gather read access to the model. The simplest is runReadAction():

public final class MetadataModel<T> {

    public R runReadAction(MetadataModelAction<T, R> action);

    // ...
}

public interface MetadataModelAction<T, R> {

    public R run(T metadata);
}

MetadataModelAction is an interface with a run() method, which contains the code that must be run in read access (similarly to JavaSource.runUserActionTask()). The run() method gets a T value (which is the MetadataModel's model-specific type parameter described above). The assumption is that the result of reading the model is a value, so the run() method can return a value of type R, which will be returned by MetadataModel.runReadAction().

For example, a client which wants to retrieve the number of entity classes in a JPA ORM model would do:

// scope is a PersistenceScope
// puName is a persistence unit name (String)
MetadataModel<EntityMappingsMetadata> emModel = scope.getEntityMappingsModel(puName);
int count = emModel.runReadAction(new MetadataModelAction<EntityMappingsMetadata, Integer>() {
    public Integer run(EntityMappingsMetadata metadata) {
        EntityMappings em = metadata.getRoot();
        Entity[] entity = em.getEntity();
        return entity.length;
    }
});

If the clients wanted the names of all servlets in the web metadata model, the client would do:

// wm is a WebModule
MetadataModel<WebAppMetadata> webModel = wm.getMetadataModel();
List<String> servlets = webModel.runReadAction(new MetadataModelAction<WebAppMetadata, List<String>>() {
    public List<String> run(WebAppMetadata metadata) {
        List<String> result = new ArrayList<String>();
        WebApp webApp = metadata.getRoot();
        for (Servlet servlet : webApp.getServlet()) {
            result.add(servlet.getServletName());
        }
        return result;
    }
});

Clients should not hold objects returned from the model outside read access -- similar to the javac API Element's. In the example above, the instances of Servlet themeselves are not allowed to escape the run() method, thus the following code is incorrect and will cause random bugs at runtime:

DO NOT DO THIS!

// wm is a WebModule
MetadataModel<WebAppMetadata> webModel = wm.getMetadataModel();
List<Servlet> servlets = webModel.runReadAction(new MetadataModelAction<WebAppMetadata, List<Servlet>>() {
    public List<Servlet> run(WebAppMetadata metadata) {
        List<Servlet> result = new ArrayList<Servlet>();
        WebApp webApp = metadata.getRoot();
        for (Servlet servlet : webApp.getServlet()) {
            result.add(servlet);
        }
        return result;
    }
});

Ready and non-ready models

Since most metadata models are based on annotations, the models need to rely on the Java infrastructure. This means that while the Java infrastructure is performing a classpath scan the model might be in an inconsistent state or might contain only a part of data it should contain. For example if the Java infrastructure has parsed just a part of the classes in an EJB project, a client reading the model will see just a half of the EJBs in that project.

For this reason the concept of a "ready" model was introduced. Basically, if a model is ready, the metadata in the model correspond exactly to their source. If a model is not ready there are no guarantees on the metadata in the model. They might correspond to the source partially or not at all. To find if a model is ready the MetadataModel.isReady() can be used.

Additionally, model client will usually need to wait until the model is ready before reading it (e.g., a wizard displaying the entity classes in a project needs to wait until the model is ready to be sure that all entities are displayed). For this purpose the MetadataModel.runReadActionWhenReady() is available. This method gets a MetadataModelAction as its parameter, and will execute the action either synchronously (in the current thread) if the model is ready, or asynchronously if the model is not ready. The method returns a Future which can be used to wait for the action to finish and retrieve its result.

For example, a client wanting to display all entity classes in the UI might do something like:

MetadataModel<EntityMappingsMetadata> emModel = scope.getEntityMappingsModel(puName);
Future<List<String>> entityNamesFuture = emModel.runReadActionWhenReady(new MetadataModelAction<EntityMappingsMetadata, List<String>>() {
    public int run(EntityMappingsMetadata metadata) {
        List<String> result = new ArrayList<String>();
        for (Entity entity : metadata.getRoot().getEntity()) {
            result.add(entity.getName());
        }
        return result;
    }
});
List<String> entityNames = entityNamesFuture.get();
SwingUtilities.invokeLater() {
    displayEntities(entityNames);
}

Alternatively, the code above could have just called SwingUtilities.invokeLater() from within the action's run() method after having gathered the entity names.

Event thread considerations

Since most models delegate to the Java infrastructure, which should not be called in the event processing thread (EDT), the runReadAccess() and runReadAccessWhenReady() methods should not be called from the EDT either. If the model data are used to fill an UI widget, there should be status messages in the UI such as "classpath scan in progress" and so on.

Typically a client will want to schedule a call to runReadAccessWhenReady() to a RequestProcessor thread and maintain the state in which the action is (scanning classpath, reading the model, etc.). This leads to complicated code, which is prone to errors. An alternative is to use the MetadataModelReadHelper class in j2ee/utilities. See the Javadoc for a guide on using this class.