7.5 JAXR Programming

   

7.5 JAXR Programming

So far in this chapter, you have seen the JAXR registry information model, which is mapped to the javax.xml.registry.infomodel package. This section looks at the API that allows you to search or update a registry, which is defined in the package javax.xml.registry . The examples shown in this section assume that you have installed the sample registry data in either a UDDI or ebXML registry, as described in Section 7.3, earlier in this chapter.

7.5.1 Connecting to the Registry

In order to use a registry, you first have to create a connection to it. This process requires two steps:

  1. Obtain a ConnectionFactory object and optionally set properties that determine how it will behave.

  2. Use the ConnectionFactory object to obtain a Connection or FederatedConnection to the target registry.

There are two ways to get a ConnectionFactory object. In a J2EE environment, the preferred way is to use the JNDI API to look up a preconfigured ConnectionFactory :

 InitialContext ctx = new InitialContext(  );
ConnectionFactory factory = (ConnectionFactory)ctx.lookup("pathToFactory"); 

J2SE-based registry clients that do not have a preconfigured JNDI environment can instead use the static newInstance( ) method:

 ConnectionFactory factory = ConnectionFactory.newInstance(  ); 

ConnectionFactory is an abstract class. The actual class of the object returned by the newInstance( ) method is determined by the property javax.xml.registry.ConnectionFactoryClass , which can be set in either of the following locations (in order of priority):

  • As a system property (and therefore accessible by calling System.getProperty( " javax.xml.registry.ConnectionFactoryClass " ) ).

  • In a file called jaxr.properties that is located in the lib directory of the installed JRE.

If a value for this property is not found in either of these locations, then a file called javax.xml.registry.ConnectionFactoryClass is searched for in a directory called META-INF/services within the classpath of the Java VM. [6] If this file exists, its content is expected to be a single line of text that gives the name of the ConnectionFactory class to be loaded. Failing this, a default ConnectionFactory implementation is used. In the case of the JAXR reference implementation, the default factory creates Connection objects that can be used to access a UDDI registry.

[6] This file is typically included in the JAR file containing the implementation of a registry provider. The first such JAR file to be processed , in classpath order, determines which registry implementation is used.

The behavior of Connection objects that are obtained from a ConnectionFactory is determined by a set of properties associated with the ConnectionFactory . These properties are set by calling the setProperties( ) method, which requires as its argument a Properties object containing some or all of the standard property values listed in Table 7-2.

Table 7-2. Standard JAXR ConnectionFactory properties

Property name

Description

javax.xml.registry.queryManagerURL

The URL at which the registry query service can be accessed. This property must always be set.

javax.xml.registry.lifeCycleManagerURL

The URL at which the service used to update the registry can be accessed. If not set, the query URL is used.

javax.xml.registry.semanticEquivalences

Allows a mapping to be established in the JAXR provider between pairs of Concepts that are to be regarded as equivalent. This property is most often used in connection with postal addresses held in UDDI registries, and is described in Section 7.5.7, later in this chapter.

javax.xml.registry.security.authenticationMethod

Specifies the way in which the JAXR provider is to authenticate itself to the registry. The client must select an authentication mechanism that is recognized by the registry that it is attempting to use. Authentication is usually required only when updating the registry. See Section 7.5.10 later in this chapter for further information.

javax.xml.registry.uddi.maxRows

Specifies the maximum number of elements that a registry find query is allowed to return. This value applies only to a UDDI registry and must be a well- formed integer. If this property is not specified, then no limit is placed on the number of rows that will be returned.

javax.xml.registry.PostalAddressScheme

Gives the UUID of the postal address scheme used to map postal address items in the registry to attributes of the JAXR PostalAddress object. This property is currently used only by UDDI registry implementations . See Section 7.5.7 later in this chapter for further information.

JAXR implementations are free to specify additional properties as required to allow customization of their behavior. The UDDI provider in the JAXR reference implementation recognizes the eight additional properties listed in Table 7-3.

Table 7-3. JAXR ConnectionFactory properties recognized by the reference implementation UDDI provider

Property name

Description

com.sun.xml.registry.http.proxyHost

Hostname for an HTTP proxy if a direct connection to the registry is not possible.

com.sun.xml.registry.http.proxyPort

Port number to be used for an HTTP connection to a registry via a proxy host.

com.sun.xml.registry. https .proxyHost

Hostname used when making an HTTPS connection to a proxy host. Some registries require secure connections for registry updates.

com.sun.xml.registry.https.proxyPort

Port name used when making an HTTPS connection to a proxy host.

com.sun.xml.registry.http.proxyUserName

If a proxy is in use and the proxy requires clients to authenticate, this property should be used to set the username expected by the proxy.

com.sun.xml.registry.http.proxyPassword

Sets the password to be used if proxy authentication is required.

com.sun.xml.registry.http.useCache

If this property has the value true, the registry may store objects in a local cache from which it may return them in response to later queries that require the same object. This property is true by default.

com.sun.xml.registry.http.useSOAP

By default, the UDDI JAXR provider uses the SAAJ API to send client requests to the registry server. If this property is set to true, it uses Apache SOAP instead. This property is not likely to be of much practical use in a production environment.

A typical piece of code that obtains a connection to a registry is shown in Example 7-1.

Example 7-1. Connecting to a registry
 Properties props = new Properties(  );
props.put("javax.xml.registry.queryManagerURL", queryURL);
props.put("javax.xml.registry.lifeCycleManagerURL", lifecycleURL);

ConnectionFactory cf = ConnectionFactory.newInstance(  );
cf.setProperties(props);

// Get and initialize the connection
Connection conn = cf.createConnection(  );
conn.setCredentials(credentials);

// Get the RegistryService and the managers
RegistryService registry = conn.getRegistryService(  );
BusinessQueryManager bqm = registry.getBusinessQueryManager(  );            
BusinessLifeCycleManager blcm = registry.getBusinessLifeCycleManager(  ); 

The Properties object created at the start of this example is populated with the URLs required to access the registry's query and update services, and then passed to the ConnectionFactory object, which uses it when creating a Connection . In the case of the JWSDP JAXR reference implementation deployed in a Tomcat web container or in J2EE 1.4, the query and update URLs both have the value http://localhost:8080/RegistryServer/RegistryServerServlet , assuming that the client application and the registry are running on the same system.

The ConnectionFactory getConnection( ) method returns a Connection object that can be used to access a single registry. ConnectionFactory also supports the creation of a FederatedConnection , via the createFederatedConnection( ) method:

 public FederatedConnection createFederatedConnection(Collection
   connections); 

FederatedConnection is an interface derived from Connection that does not add any new methods . Each object in the Collection passed to the createFederatedConnection( ) method must be a Connection representing a connection to a single registry or another FederatedConnection object. The intent of FederatedConnection is to allow queries made via the FederatedConnection to be sent to all of the registries with which it is associated, and for the query results to be merged to create a single result set. Although createFederatedConnection( ) is a capability level 0 method, it is still considered an optional feature, and therefore may not be implemented by all registry providers. In particular, the UDDI registry provider in the JAXR reference implementation does not support it and throws an UnsupportedCapabilityException if the createFederatedConnection( ) method is called.

The act of obtaining a Connection object does not actually make a connection to a registry. The provider is free to defer the establishment of a connection until such time as the client makes an API call that actually requires an operation to be performed by the registry. If the registry requires authentication information from a client, then the setCredentials( ) method should be called after obtaining a Connection object, as shown previously. The argument passed to this method is a java.util.Collection containing one or more credentials, the data type of which depends on the authentication method used by the registry server. Registry authentication is discussed in more detail in Section 7.5.10 at the end of this chapter.

From the Connection object, a registry client obtains an instance of the interface RegistryService . RegistryService is the central object within a JAXR provider. There is one instance of this object for each registry Connection . From a RegistryObject , a client can discover the capability level of the provider (from the getCapabilityProfile( ) method) and also obtain references to the following interfaces:

BusinessQueryManager

BusinessQueryManager is an interface that provides the methods necessary to search the registry using crtiteria that are expressed in terms of the JAXR information model. For example, you can use the methods of the BusinessQueryManager to find all Organization s whose names match a given pattern or with given classifications. BusinessQueryManager is described later in Section 7.5.2.

DeclarativeQueryManager

A DeclarativeQueryManager allows a client to search the registry using expressions written using one or more query languages. DeclarativeQueryManager is supported only by level 1 JAXR providers. Using this interface, a client might be able to search a registry using queries written in SQL-92 syntax or using an OASIS ebXML Registry Filter query. See Section 7.5.3 later in this chapter for more information.

BusinessLifeCycleManager

The BusinessLifeCycleManager interface contains methods that allow a registry client to create, update, or delete registry content. In most cases, a client must be properly authenticated with the registry in order to make changes to its content. BusinessLifeCycleManager is discussed later in Section 7.5.5.

Default Postal Scheme

This represents the ClassificationScheme used by the JAXR provider for handling PostalAddress objects. This may be null if the registry includes native support for postal addresses that is consistent with that required by the JAXR specification (as is the case with the ebXML registry). See Section 7.5.7 later in this chapter for more on this subject.

RegistryObject also provides a method ( makeRegistrySpecificRequest( ) ) that can be used by specialized registry applications to send a request to a registry in the native format used by that registry. Since this method requires an understanding of the message schemes used by specific registries, it will not be further discussed in this book.

7.5.2 Retrieving Objects from the Registry

The BusinessQueryManager and QueryManager interfaces in the javax.xml.registry package provide methods that allow information held in the registry to be retrieved. The methods provided by QueryManager return objects based on object type, object owner, or the unique key assigned to them when they are created. BusinessQueryManager is derived from QueryManager and provides methods that allow the registry to be searched using various business- related criteria, including object name or a list of classifications. A search by name is equivalent to looking up an entry in a White Pages directory, while a classification-based search is analogous to using the Yellow Pages to locate a company or service based on its type (e.g., Book Publisher, Plumber, etc.).

7.5.2.1 Using QueryManager methods to retrieve registry objects

You can use the methods provided by

 QueryManager 

when you know the key assigned to the objects that you want or the type of that object, or you want to fetch all of the objects that you have created in the registry.

When you know the registry key of the object that you want, you can use one of the following methods to retrieve it:

 public RegistryObject getRegistryObject(String id) throws JAXRException;
public RegistryObject getRegistryObject(String id, String type) 
   throws JAXRException; 

The first of these methods requires only the key, but is supported only by level 1 JAXR providers. A level 0 provider (such as a UDDI V2 registry) requires the type of the object as well as the key generated when it was stored in the registry. The possible values for the object type are available as constants defined by the LifeCycleManager interface. The following code, for example, returns the Organization object corresponding to the ID supplied by the variable key , where bqm is assumed to refer to a QueryManager instance:

 String key = "f1d13cc1-e0f1-d13c-8671-22c9c07a9a76";
Organization org = (Organization)bqm.getRegistryObject(key,
   LifeCycleManager.ORGANIZATION); 

The getRegistryObject( ) method throws a JAXRException if the key or the type are invalid, or if the key does not correspond to a RegistryObject of the specified type. JAXRException is actually a base class for the small number of checked exceptions that a JAXR provider can throw, which are shown in Figure 7-13.

Figure 7-13. Response and exception classes in the JAXR API
figs/jwsn_0713.gif

There are four getRegistryObjects( ) methods that fetch different sets of RegistryObject s:

 public BulkResponse getRegistryObjects(  ) throws JAXRException;
public BulkResponse getRegistryObjects(String type) throws JAXRException;
public BulkResponse getRegistryObjects(Collection keys) 
   throws JAXRException;
public BulkResponse getRegistryObjects(Collection keys, String type) 
   throws JAXRException; 

Each of these methods returns an object of type BulkResponse . BulkResponse contains a Collection that holds the RegistryObject s that meet the selection criteria implied by the arguments (if any) supplied to getRegistryObjects( ) , a reference to which can be obtained by calling the getCollection( ) method. The following code can be used to iterate over the results of a getRegistryObjects( ) method call:

 BulkResponse res = bqm.getRegistryObjects(  );
Collection coll = res.getCollection(  );
Iterator iter = coll.iterator(  );
while (iter.hasNext(  )) {
    RegistryObject obj = (RegistryObject)iter.next(  );
} 

BulkResponse also contains a collection of exceptions that indicate errors that occurred while completing the method call. The getExceptions( ) method returns a reference to this Collection , or returns null if no exceptions occurred. The exceptions in this collection are all derived from JAXRException and are therefore checked exceptions. The exception hierarchy for the JAXR API is shown in Figure 7-13. Both BulkResponse and JAXRException implement the JAXRResponse interface. JAXRResponse contains methods that are used in conjunction with asynchronous requests to the registry, which are described later in Section 7.5.4.

The set of objects returned by each of the getRegistryObjects( ) methods depends on the arguments supplied, as follows :

getRegistryObjects( )

Returns all of the objects in the registry that are owned by the user making the call.

getRegistryObjects(String type)

Returns all of the objects in the registry of the supplied type (such as LifeCycleManager.ORGANIZATION ) that are owned by the user making the call.

getRegistryObjects(Collection keys)

Returns the objects from the registry for which the keys are supplied in the Collection passed as the argument. Each element of the collection must be an object of type Key .

getRegistryObjects(Collection keys, String type)

Returns the objects from the registry of the supplied type, for which the keys are passed in the given Collection . The objects corresponding to the supplied keys must all be of the specified type.

For those methods that return collections of objects owned by the calling user, the user's identity is obtained from the credentials passed to the ConnectionFactory setCredentials( ) method. If valid credentials have not been set, then a JAXRException is thrown.

7.5.2.2 Finding registry objects using BusinessQueryManager

The BusinessQueryManager interface provides methods that allow registry searches based on the values of specific attributes of RegistryObject s to be performed. These methods form two groups: those that return an individual RegistryObject given a specification of exact attributes that the object must have, and those that return a BulkResponse containing a collection of RegistryObject s that meet specified criteria. In this section, we look at the methods that fall into the second of these groups.

The methods that return a set of results all require a set of input parameters, some of which are optional, that supply the acceptable values for specific attributes of a particular class of object to be found within the registry. The findOrganizations( ) method is a typical example that we'll use to demonstrate how these methods work:

 public BulkResponse getOrganizations(Collection findQualifiers, 
   Collection namePatterns, Collection classifications, Collection
   specifications, Collection externalIdentifiers, Collection
   externalLinks); 

In this case, all of the arguments are Collection s. Each Collection is expected to contain objects of a specific type and may be null if it is not required to search on the basis of the corresponding attribute. Supplying null for the externalIdentifiers argument, for example, allows Organization s with any associated ExternalIdentifier s (including none) to be included in the response set. In some cases, where there is more than one entry in a Collection , the corresponding attribute of a RegistryObject must have all of the specified values (i.e., there is an implied AND operation), while in other cases, it is required to have only one of the given values (i.e., an implied OR operation). Supplying two values in the externalIdentifiers argument, for example, requires that RegistryObject s have both ExternalIdentifier s in order to be returned. Table 7-4 shows the types of the objects required in each Collection and the way in which multiple entries are handled.

Table 7-4. Arguments used by the BusinessQueryManager find methods

Collection argument

Object in Collection

Combination rule

associationTypes

Concept

OR

classifications

Classification

AND

externalIdentifiers

ExternalIdentifier

AND

externalLinks

ExternalLink

AND

namePatterns

String or LocalizedString

OR

specifications

See the following description

AND

In most cases, the type of object in the Collection can be inferred directly from the name used for the argument in the method signature. There are, however, a few cases that require more description:

associationTypes

An Assocociation is a formal linkage between two RegistryObject s that reflects a specific kind of relationship between them. The different types of relationships that may exist are defined as an enumeration, each element of which is represented within the registry model by a Concept . To select Assocation s of various types, include the Concept s representing each of those relationship types in the Collection and call the f indAssociations( ) or findCallerAssociations( ) method. See Section 7.5.5.7 later in this chapter for more on this topic.

namePatterns

This Collection contains one or more objects of type String or LocalizedString that is used to match against the name attribute of RegistryObject s of the type searched by the method in use. To facilitate wildcard searching, the string may contain the character % to match any number of characters ”for example, the string % would match any name, while %a% would match any name that contains at least one letter a . Whether the search is case-sensitive depends on the find qualifiers in use, as described shortly. Note that the JAXR specification states that a name pattern follows the syntax defined for the SQL-92 LIKE clause in a SELECT statement. At the time of this writing, however, the JAXR reference implementation does not support the full syntax of this clause.

specifications

This Collection can contain any object that might be used as the specification object of a SpecificationLink . These objects are most likely to be Concept s in the case of a UDDI registry, and ExtrinsicObject s for an ebXML registry. See Section 7.5.5.2 later in this chapter for more on the use of SpecificationLink s.

Many of the search methods (including findOrganizations( ) ) accept a Collection of find qualifiers that can be used to control the search or to place requirements on the way in which the return values are organized. Each element in this Collection must be one of the constant values defined by the FindQualifier s interface and described in the reference materials for the javax.xml.registry package in the second part of this book.

7.5.2.3 Searching by Classification

In order to demonstrate how to locate information in a registry, we'll look at an example that searches for an organization based on its associated classification ” in other words, a Yellow Pages search. Specifically, the example searches for organizations that are categorized as Book Publishers in the NAICS classification scheme. Initially, this is a simple task, since the BusinessQueryManager interface provides a method called findOrganizations( ) that lets you search given various different criteria, including the classifications applied to Organization objects. The interesting part of this example is the way in which the Classification objects that the findOrganizations( ) method requires are created.

As noted in Section 7.4.4, earlier in this chapter, a JAXR provider is obliged to support the NAICS scheme as an internal classification scheme. This means that we can create either an internal or an external Classification to represent Book Publishers under this scheme. Classification s are created using the createClassification( ) methods of the BusinessLifeCycleManager interface, which will be covered in detail in Section 7.5.5, later in this chapter. There are three such methods:

 public Classification createClassification(ClassificationScheme scheme,
   String name, String value);
public Classification createClassification(ClassificationScheme scheme,
   InternationalString name, String value);
public Classification createClassification(Concept concept); 

The first two methods can be used with either an internal or an external classification scheme and create an internal classification. The third method creates a Classification corresponding to a Concept in the node hierarchy of an internal classification scheme, as illustrated in Figure 7-12.

The code required to create an external classification is very simple ” all that is necessary is to look up the parent

 ClassificationScheme 

and then supply the name and value of the classification itself. This is shown in the following code extract, where the variables bqm and blcm refer to instances of BusinessQueryManager and BusinessLifeCycleManager , respectively.

 ClassificationScheme naics = bqm.findClassificationSchemeByName(null,
   "%naics%");
if (naics == null) {
    naics = bqm.findClassificationSchemeByName(null, "%NAICS%");
    if (naics == null) {
        System.out.println("COULD NOT FIND NAICS CLASSIFICATION SCHEME.");
        System.exit(1);
    }
}

// Create external classification  Classification bookPublishers = blcm.createClassification(naics  , 
   "  Book Publishers", "51113");  

When creating an external classification, it is important to supply the correct value argument (in this case, "51113"), since this is what actually identifies the classification. The name argument ("Book Publishers") is not so important, because it is not used when comparing the created Classification against those associated with objects in the registry during a search. It should be clear from this code that it is perfectly possible to create an external classification that has no real meaning, simply by supplying an inappropriate value argument:

 Classification bookPublishers = blcm.createClassification(naics, 
   "Book Publishers", "ABCDEF"); 

This code succeeds in creating a Classification object, even though the NAICS scheme does not contain a classification with value "ABCDEF".

It is a little more difficult to create an internal classification, since it is necessary to locate the Concept that represents the node of the hierarchy for which a Classification object is required. The benefit of doing so, however, is that an internal classification is known to be valid. Recall from Figure 7-12 that the Concept nodes are arranged beneath the ClassificationScheme object with which they are associated, in a hierarchy that reflects the classification scheme itself. In order to create an internal classification that represents Book Publishers, you need first to locate the Concept with the value "51113" shown at the bottom right of Figure 7-12, which can then be passed to the third variant of the createClassification( ) method just shown.

There are two ways to locate the required Concept . The first involves searching for it in the set of Concept s that are descendents of the ClassificationScheme :

 Concept publisherConcept = null;
Collection concepts = naics.getDescendantConcepts(  );
Iterator conceptIter = concepts.iterator(  );
while (conceptIter.hasNext(  )) {
    Concept concept = (Concept)conceptIter.next(  );
    String value = concept.getValue(  );
    if (value != null && value.equals("51113")) {
        publisherConcept = concept;
        break;
    }
} 

The getDescendentConcepts( ) method returns a Collection containing all of the Concept s associated with the ClassificationScheme . To find the one that is required, it is necessary to iterate over this Collection until a Concept for which the value attribute of "51113" is found. A more elegant solution, however, is to use the findConceptByPath( ) method, which requires that you specify the location of the required Concept by giving its path. The path is formed by specifying first the key of the ClassificationScheme , then appending the values of each Concept on the node tree leading from the ClassificationScheme to the Concept itself, with each part being separated by a " / " character. By referring to Figure 7-12, you can see that the appropriate path for the Concept whose name is "Book Publisher" is therefore given by the expression:

 "/" + naics.getKey(  ).getId(  ) + "/51/511/5111/51113" 

Note that the path contains the value attribute of each Concept node, not the name. The following code locates the correct Concept and creates the corresponding internal classification object:

 String path = "/" + naics.getKey(  ).getId(  ) + "/51/511/5111/51113";
Concept publisherConcept = bqm.findConceptByPath(path); 
Classification bookPublishers = blcm.createClassification(publisherConcept); 

Even though the createClassification( ) methods are part of the BusinessLifeCycleManager interface, which is intended to allow registry updates, you don't actually make any changes to the registry itself by calling them; therefore, you don't need to have authenticated with the registry in order to do so.

Once you have a Classification object, you can use the findOrganizations( ) method to carry out the required search, as shown in Example 7-2.

Example 7-2. Searching the registry for an Organization with a given classification
 ArrayList classifications = new ArrayList(  );
classifications.add(bookPublishers);
BulkResponse res = bqm.findOrganizations(null, null, classifications,
                                         null, null, null);

// Process results (if any)
Collection coll = res.getCollection(  );
System.out.println("Internal classification search #2 found " + coll.size(  ));
Iterator iter = coll.iterator(  );
while (iter.hasNext(  )) {
    Organization org = (Organization)iter.next(  );
    System.out.println("\t" + org.getName(  ).getValue(  ));
} 

The example source code for this chapter includes a client that uses this code to search the registry for book publishers. To run this code, make chapter7\jaxr your working directory and type the commands:

 ant compile
ant run-uddi-classify-client 

The client uses the three different methods shown above to create the Classification object used to perform the search. Each search should return and display the Organization object for O'Reilly & Associates . You can use the command:

 ant run-ebxml-classify-client 

to use an ebXML registry instead of the UDDI registry in the reference implementation.

7.5.2.4 Extended Classification searches

When you use the findOrganizations( ) method (or any of the other find methods of BusinessQueryManager ) to search for a registry object based on its associated classifications, a match is made against the exact classifications passed in the method call. This can have some unexpected consequences. For example, suppose you want to find the entries for all organizations that operate under the classification "Newspaper, Periodical, Book, and Database Publishers" in the NAICS classification scheme. If you simply create a Classification object for this category and then call findOrganizations( ) , in all probability you won't find anything, since the companies that operate in this marketplace are likely to have categorized themselves using the more specific classifications such as "Book Publisher" and "Newspaper Publishers" that appear below the "Newspaper, Periodical, Book, and Database Publishers" classification in Figure 7-12.

In order to perform a query for an organization that has a given classification or any of the more specific classifications that reside below it in the classification scheme, you need to include all of the individual classifications in the Collection supplied to the findOrganizations( ) method. If the classification scheme is external, in order to achieve this you must explicitly create Classification objects for all of the descendent classifications by referring to a diagram like a more complete version of Figure 7-12, which shows how the classification scheme is organized. For an internal classification scheme, however, you can make use of the fact that the structure of the scheme is known to the JAXR provider to get all of the Concep ts that are descended from "Newspaper, Periodical, Book, and Database Publishers," and then create Classification s for each of them. The advantage of working with an internal classification scheme is that this does not require prior knowledge of the structure of the scheme below the node representing the initial classification.

The code required to find the Concept for the "Newspaper, Periodical, Book and Database Publishers" classification node, and then to create the corresponding Classification , is straightforward:

 ArrayList classifications = new ArrayList(  );
String path = "/" + naics.getKey(  ).getId(  ) + "/51/511/5111";
Concept publisherConcept = bqm.findConceptByPath(path); 
Classification bookPublishers = blcm.createClassification(publisherConcept);
classifications.add(bookPublishers); 

Since NAICS is an internal classification scheme, we can use the findConceptByPath( ) method to find the correct Concept . The path that corresponds to this node begins with the ID of the classification scheme itself, followed by the values of the nodes leading to the one that we require. You can read off the required values from Figure 7-12. Once the correct Concept is found, the createClassification( ) method is called to create a Classification object, which is then added to an ArrayList that is eventually passed to the findOrganizations( ) method. The next step is to add to this ArrayList the Classification objects for all of the descendents of the Concept that we just found. To do this, we use the getDescendentConcepts( ) method, which locates all of the direct descendents of the Concept to which it is applied, and all of the child Concept s of those descendents, and so on:

 Collection concepts = publisherConcept.getDescendantConcepts(  );
Iterator conceptIter = concepts.iterator(  );
while (conceptIter.hasNext(  )) {
    Concept concept = (Concept)conceptIter.next(  );
    System.out.println("Adding child: " + concept.getName(  ).getValue(  ));
    classifications.add(blcm.createClassification(concept));
} 

Each descendent Concept is then wrapped in a Classification object and added to the ArrayList .

Finally, to locate the set of Organization s with any of these classifications, we pass the list of Classification s to the findOrganizations( ) method:

 ArrayList findQualifiers = new ArrayList(  );
findQualifiers.add(FindQualifier.OR_LIKE_KEYS);
BulkResponse res = bqm.findOrganizations(findQualifiers, null,
   classifications,  null, null, null); 

If you refer back to Table 7-4, you'll see that in a find operation that includes more than one Classification , they are combined using an AND operation ” in other words, the AND operation looks for objects that have all of the specified Classification s. Since we want to retrieve Organization s that have any of the Classification s, we need to convert this to an OR operation. To do this, we need to pass a set of find qualifiers containing FindQualifier.OR_LIKE_KEYS as the first argument to the findOrganizations( ) method. You can try out this code by making chapter7\jaxr your working directory and typing the command:

 ant run-extended-uddi-classify-client 

or:

 ant run-extended-ebxml-classify-client 

The output from either of these commands shows the classifications that are being included and the result of the search, which should be the single entry for O'Reilly & Associates.

7.5.3 Declarative Queries

In addition to the search facilities provided by BusinessQueryManager , level 1 registries also allow you to retrieve information using queries written in one or more query languages. The query language or languages available may vary from registry to registry, and the JAXR specification does not require any particular query language to be supported. The javax.xml.registry.Query interface defines constants for three specific query languages, which are listed in Table 7-5.

Table 7-5. Query languages identified by the Query interface

Constant

Description

QUERY_TYPE_EBXML_FILTER_QUERY

An ebXML filter query, as described in the ebXML Registry Service specification.

QUERY_TYPE_SQL

A query using a subset of SQL-92.

QUERY_TYPE_XPATH

A query using the W3C XPath language.

The rules that govern the use of ebXML filters and SQL as query languages are defined in the ebXML Registry Service specification, which can be downloaded from http://www.oasis- open .org/ committees /regrep/documents/2.1/ specs /ebrs.pdf.

In order to submit a query, you first need to obtain an instance of the DeclarativeQueryManager interface from the RegistryService object associated with your connection to the registry. You can then use the DeclarativeQueryManager object to build a query and submit it to the registry. The code in Example 7-3 illustrates how to retrieve all of the Organization s from the registry, using an SQL query.

Example 7-3. Using an SQL query to fetch a set of Organizations from the registry
 RegistryService registry = conn.getRegistryService(  );
DeclarativeQueryManager dqm = registry.getDeclarativeQueryManager(  ); 
Query query = dqm.createQuery(Query.QUERY_TYPE_SQL, 
    "SELECT o.id FROM Organization o")
BulkResponse res = dqm.executeQuery(query);

// Process results (if any)
Collection coll = res.getCollection(  );
Iterator iter = coll.iterator(  );
while (iter.hasNext(  )) {
    Organization org = (Organization)iter.next(  );
    System.out.println("\t" + org.getName(  ).getValue(  ));
} 

The query is represented by a Query object, which is obtained by passing the constant representing the query language to be used and the query itself to the createQuery( ) method of DeclarativeQueryManager . The query is then performed using the executeQuery( ) method, which returns a BulkResponse containing the RegistryObject s that match the query.

Although a registry may support SQL as a query language, it does not necessarily follow from this that the registry is implemented using a relational or object database. Instead, the registry server is required to provide a logical mapping of the registry information model to a set of SQL tables, which can then be the target of SQL queries. The details of this mapping are outside the scope of this book, and can be found in the ebXML Registry Service specification, together with a specification of the subset of the SQL SELECT statement syntax that can be used in conjunction with the executeQuery( ) method. The following is an incomplete summary that covers only the major points:

  • The SELECT statement may only return a single column from one registry table. That column must be the id column, which represents the unique identifier assigned to the RegistryObject when it is created. The BulkResponse will contain the RegistryObject s that correspond to the IDs returned by the SELECT statement. [7]

    [7] At the time of this writing, the ebXML registry implementation available from sourceforge .net actually requires that the SELECT statement return more than just the ID column. As a workaround for this problem, you can use SELECT * FROM .... . instead of SELECT id FROM .

  • Each RegistryObject is mapped to a table that is typically named for its concrete type, such as Organization , ClassificationScheme , etc. There are some exceptions, such as User , which is mapped to a table named User_ .

  • RegistryObject and RegistryEntry are represented as relational views that include all of the rows from the individual tables that correspond to the concrete types and thus allow queries that are independent of the actual type of an object to be made. Therefore, while SELECT o.id FROM Organization o returns all Organization objects, the query SELECT r.id FROM RegistryObject r produces a result set containing all RegistryObject s, including all Organization s.

  • Not all attributes of a RegistryObject appear in the table that represents that object. Some attributes, such as the name and description, appear as rows in a separate table that has a column called parent that links it to the RegistryObject that it should be associated with. For example, the query SELECT o.id FROM Organization o, Name nm WHERE o.id = nm.parent AND nm.value LIKE '%ei% ' returns all Organization s for which the name contains the characters ei (such as O'Reilly & Associates).

  • Attributes of a RegistryObject that are defined as Collection s ” such as the set of Classification s, ExternalLink s, etc. ” can be obtained by invoking a stored procedure, and passing the ID of the RegistryObject as a parameter. The set of stored procedures that are defined for this purpose can be found in the ebXML Registry Service specification.

The example source code for this book contains a simple example that shows how to submit an SQL query to an ebXML registry. To run this example, make chapter7\jaxr your working directory and type the command:

 ant run-ebxml-query-client 

This example uses the SQL query shown above to find all Organization objects in the registry for which the name contains the string ei and then fetches the Classification s associated with each of them. The SQL query required to fetch the Classification s demonstrates how to use one of the small number of stored procedures that return attributes defined as Collection s, as shown in Example 7-4.

Example 7-4. Using a stored procedure to get the Classifications for a RegistryObject
 Query classQuery = dqm.createQuery(Query.QUERY_TYPE_SQL,
    "SELECT c.id FROM Classification c WHERE id IN " +
    "(RegistryObject_classifications('" + org.getKey(  ).getId(  ) + "'))"); 

The stored procedure RegistryObject_classifications( ) requires the ID of the RegistryObject for which the Classification s are required, and returns the ID for each of them. Note that the ID argument must be surrounded by single quotes to satisfy the SQL syntax. The output from this command, when run against an ebXML registry with just the sample data for this chapter installed, looks like this:

 Query found 1
      OReilly & Associates, Inc
     Book Publishers   United States 

7.5.4 Asynchronous Queries

It has been implicitly assumed in the examples shown so far in this chapter that when a request is made to the registry, the calling thread blocks until a reply is received. For most applications, this is an acceptable mode of operation, since they have little or nothing else to do until the result of the operation is known. However, applications that do not wish to be blocked awaiting a response can use the Connection setSynchronous( ) method with the argument false to select an asynchronous mode of operation. Once this mode is selected, queries made via BusinessQueryManager or DeclarativeQueryManager , and registry modifications made using BusinessLifeCycleManager (described shortly), behave as follows:

  • The provider allocates a unique identifier to the request and creates a JAXRResponse (which, in practice, is a BulkResponse ), with its request identifier attribute set to the value of this identifier and its status set to JAXRResponse.STATUS_UNAVAILABLE . The JAXRResponse is returned to the caller, as usual.

  • The provider is responsible for arranging for the request to be performed without blocking the calling thread. A provider typically does this by creating a new thread to handle the request, or by handing it off to an existing thread dedicated to handling asynchronous requests.

  • When the request completes, successfully or otherwise , the provider loads any responses or exceptions into the JAXRResponse and sets its status to either STATUS_SUCCESS , STATUS_WARNING , or STATUS_FAILURE .

Application code can treat the BulkResponse returned by the provider in the usual way by immediately calling the getCollections( ) or getExceptions( ) methods to retrieve the results of the call. However, if it does so, it is blocked until the request actually completes. In order to make use of the asynchronous nature of the request, it must instead wait to be notified that the results are available, by inspecting the value returned by the JAXRResponse getStatus( ) method or calling the convenience method isAvailable( ) . These methods do not block. The code shown in Example 7-5 illustrates how to asynchronously request the set of all Organization s known to a registry.

Example 7-5. Making an asynchronous registry request
 ConnectionFactory cf = ConnectionFactory.newInstance(  );

// Get and initialize the connection
Connection conn = cf.createConnection(  );  conn.setSynchronous(false);  // Get the RegistryService and the BusinessQueryManager
RegistryService registry = conn.getRegistryService(  );
bqm = registry.getBusinessQueryManager(  );            

// Request all organizations
ArrayList namePatterns = new ArrayList(  );
namePatterns.add("%");
BulkResponse res = bqm.findOrganizations(null, namePatterns, 
    null, null, null, null);

// Wait until the request has completed
System.out.println("Request submitted - id = " + res.getRequestId(  ));
while (!res.isAvailable(  )) {
    System.out.println("Request status: " + res.getStatus(  ));
    try {
        Thread.sleep(1000);
    } catch (InterruptedException ex) {
    }
}
System.out.println("Request completed"); 
        
// Process results (if any)
Collection coll = res.getCollection(  ); 

You can try this example by making chapter7\jaxr your working directory and typing the following command:

 ant run-uddi-async-client 

or:

 ant run-ebxml-async-client 

The JAXR specification does not require that support for asynchronous operations be provided to clients that reside in a J2EE container because of the restrictions placed on container-resident code by the programming environment in which it must execute (such as the inability to use threads). Asynchronous operations are therefore limited to J2SE application clients.

7.5.5 Modifying the Registry

Although registries typically allow any client to browse their content, registry updates can only be made by authenticated users. In order to authenticate yourself with the registry, you need to obtain a valid user identity (which might consist of a username and password or an X509 certificate), and then use the Connection setCredentials( ) method to provide your authentication details before calling getRegistryService( ) . Section 7.5.10 later in this chapter describes in more detail how to supply authentication information to the JAXR provider.

The methods that allow registry data to be modified are defined by two interfaces: LifeCycleManager and BusinessLifeCycleManager , which is derived from it. Since RegistryService allows only the creation of a BusinessLifeCycleManager object, in the rest of this section I'll discuss the methods of both these interfaces as if they were all defined by BusinessLifeCycleManager .

It is important to note that you don't need to be authenticated in order to obtain a reference to a BusinessLifeCycleManager object. As we have already seen, it is necessary for unauthenticated clients to be able to create Classification objects using BusinessLifeCycleManager in order to be able to search for RegistryObject s that have specific classifications. Authentication is required only if you intend to use the methods that actually save information in the registry or delete entries from it.

BusinessLifeCycleManager allows you to modify the registry in three different ways:

  • Creating new objects

  • Modifying existing objects

  • Deleting existing objects

In order to enter new data into the registry, first use one or more of the BusinessLifeCycleManager createXXX( ) methods to build an object representation of the information model that you want to create. To add a new organization, for example, you would use the createOrganization( ) method to obtain an object that represents the organization itself, then create and add a Service object for each service that the organization will offer. Within each Service , you would create and add ServiceBinding s that provide the information needed to access the service itself. Having built the object representation of the organization, save it to the registry. No changes are made to the registry unless they are explicitly committed using one of the BusinessLifeCycleManager saveXXX( ) methods.

To modify existing data, use a BusinessQueryManager findXXX( ) method or the getRegistryObject( ) method to obtain a memory-resident copy of its current value, make the required changes, and then commit them using the BusinessLifeCycleManager saveXXX( ) methods.

Registry objects can be deleted using one of the BusinessLifeCycleManager deleteXXX( ) methods. In order to delete an object, you need to supply the unique key assigned to it when it was created.

The rest of this section uses extracts from the registry client used to install the test data used for the examples in this chapter to illustrate how new content can be installed in the registry. You can find the source code for this client in the file chapter7\setup\src\ora\jwsnut\chapter7\setup\RegistrySetup.java relative to the installation directory of the example source code for this book. For more information on the LifeCycleManager and BusinessLifeCycleManager interfaces, refer to the reference material for the javax.xml.registry package in the second part of this book.

7.5.5.1 Creating Organizations and Users

An Organization object is created using the BusinessLifeCycleManager createOrganization( ) method, which has two variants:

 public Organization createOrganization(String name) throws JAXRPCException;
public Organization createOrganization(InternationalString name) 
   throws JAXRPCException; 

Every RegistryObject has a name attribute of type InternationalString , which allows representations of the name to be stored in a form suitable for any number of Locale s. Typically, when creating a RegistryObject , you may choose to supply the name either as a String or as an InternationalString . In the former case, the name is actually stored as an InternationalString in which the supplied value is used as the representation for the Locale on which the client application is running.

An InternationalString is a collection of LocalizedString objects, each of which contains a Locale specifier , the character set to be used for that Locale , and a String appropriate to that Locale . Since both InternationalString and LocalizedString are interfaces, you need to use factory methods provided by BusinessLifeCycleManager to create instances of them. Here's an example that shows how to create an Organization , supplying both the name and description attributes as InternationalString s:

 InternationalString name = blcm.createInternationalString(
   "OReilly & Associates, Inc");
Organization ora = blcm.createOrganization(name);
ora.setDescription(blcm.createInternationalString("Book Publisher")); 

An InternationalString is created with values for either zero or one Locales . You can add additonal Locale s by creating LocalizedString s and calling the addLocalizedString( ) or addLocalizedStrings( ) methods:

 LocalizedString str = blcm.createLocalizedString(Locale.UK, "O'Reilly and Associates UK");
name.addLocalizedString(str); 

Further information on both InternationalString and LocalizedString can be found in the reference material in the second part of this book.

Other than its name, an Organization is initially created with an empty set of attributes. An Organization has the following set of attributes, in addition to those inherited from RegistryObject :

Address

Level 1 registries allow an Organization to have an address attribute of type PostalAddress . If you are using a level 0 registry, the Organization 's address is taken to be that of its primary contact. See Section 7.5.7 later in this chapter for a discussion of the way in which level 0 registries store PostalAddress objects, which is not as straightforward a subject as you might expect.

Telephone Numbers

An Organization can have any number of contact numbers, each of which is of type TelephoneNumber . These objects can be created using the BusinessLifeCycleManager createTelephoneNumber( ) method.

Primary Contact

A User object containing contact information for the person responsible for the Organization 's registry data. In the case of a level 0 registry, the PostalAddress attribute of the primary contact is taken to be the address of the Organization itself.

Users

A collection of User objects represents users affiliated to the Organization . This list always contains the primary contact; the JAXR provider inserts an entry for the primary contact in this collection if it is not already present.

Services

A list of Service objects describing the services offered by the Organization . Refer to Section 7.5.5.2 later in this chapter for further information.

Parent, Children, and Root Organizations

Level 1 registries allow the creation of Organization hierarchies. Each Organization in such a hierarchy has a single parent Organization and zero or more child Organization s. A reference to the root Organization for the hierarchy is also available from each Organization . Organization hierarchies are not supported by level 0 registries.

The User object is the registry's representation of a person affiliated to a business. In addition to those that it inherits from RegistryObject , User has the following attributes:

Person Name

This attribute provides personal identification information for the user in the form of a PersonName object. In a level 0 registry, the PersonName object provides only the full name of the user, expressed as a single String . Level 1 registries additionally allow the user's first, middle, and last name to be supplied as separate String s. [8] Note that the PersonName attribute is additional to the name attribute inherited from RegistryObject .

[8] Version 1.0 of the JAXR specification is not clear what should happen if you choose to set the full name of a user for a level 1 registry. In particular, it does not specify whether the supplied value should be broken into several fields and stored as the first, middle, and last names, or whether the full name should be treated as a separate attribute from the first, middle, and last name attributes.

Email Addresses

This is a collection of zero or more email addresses for the user, each of which is represented as an object of type EmailAddress . An EmailAddress object contains the actual email address (e.g., info @amazon.com ) and a type attribute that allows qualifying information specifying the way in which the address is to be used to be included.

Telephone Numbers

A collection of zero or more contact telephone numbers for the user. Each entry in the collection is a TelephoneNumber object that, in a level 0 registry, holds the telephone number in the form of a single, uninterpreted string. Level 1 registries break the telephone number down into its constituent parts , including country code, area code, and so on.

Addresses

A collection of PostalAddress objects giving the contact addresses for the user. These are taken as the addresses of the Organization itself if the User object represents the primary contact in a level 0 registry.

Type

This attribute contains a string whose content is not interpreted by the registry. It is typically used to describe the role that the user plays within the Organization , such as "Technical Contact."

URL

A URL that is in some way associated with the user. This attribute is supported only by level 1 registries.

A User object is created by calling the BusinessLifeCycleManager createUser( ) method, which initializes all of its attributes to null . The code extract in Example 7-6 shows how to create an Organization , associate it with a primary contact User , and initialize some of the attributes of both objects.

Example 7-6. Creating Organization and User objects in a registry
 // Create an entry for Amazon.com
Organization amazon = blcm.createOrganization(blcm.
createInternationalString("Amazon.com"));
amazon.setDescription(blcm.createInternationalString(
    "Amazon.com e-commerce web services"));

// Add telephone numbers
TelephoneNumber number = blcm.createTelephoneNumber(  );
number.setNumber("206-266-2335");
ArrayList list = new ArrayList(  );
list.add(number);
amazon.setTelephoneNumbers(list);
list.clear(  );

// Add a link to the home page
ExternalLink eLink = blcm.createExternalLink("http://www.amazon.com", 
    "Amazon.com home page");
eLink.setValidateURI(connected);
list.add(eLink);
amazon.setExternalLinks(list);
list.clear(  );

// Create the Amazon submitting user 
User user = blcm.createUser(  );
personName = blcm.createPersonName("Amazon.com Corporate");
user.setDescription(blcm.createInternationalString(
    "Amazon.com primary contact")); 
user.setPersonName(personName);
address = blcm.createPostalAddress("1200", "12th Avenue South", "Seattle", 
    "Washington", "US", "98144", "Headquarters");
list.add(address);
user.setPostalAddresses(list);
list.clear(  );

list.add(number);
user.setTelephoneNumbers(list);
list.clear(  );        

EmailAddress email = blcm.createEmailAddress("info@amazon.com");
list.add(email);
user.setEmailAddresses(list);
list.clear(  );
        
// Install the primary contact for Amazon.com
amazon.setPrimaryContact(user); 

You'll notice that because many of the attributes of both Organization and User can have multiple associated values, in order to set them, you have to create a Collection (in this case, an ArrayList is used), add the value or values to the Collection , and call the appropriate setter method. This is a pattern that you'll find yourself using very often when writing code that installs registry data.

Another point worth noting is that this code associates an ExternalLink with the Organization object for Amazon.com. As mentioned earlier, an ExternalLink points to an arbitrary URL that might contain some information of use to registry clients. In this case, the ExternalLink points to the organization's home page. The registry is free to check the validity of the link by attempting to access it. If this is not convenient , perhaps because the URL is not currently valid or because you are using a private test registry that is not connected to the Internet, you can use the setValidateURI( ) method to disable the check. In this case, the Boolean variable connected is assumed to be true if the client is connected to the Internet, and it is false if the client is not connected.

When the test data for this chapter is installed in the registry, three Organization objects are created for Amazon.com, O'Reilly & Associates, and Keyboard Edge Limited (my consulting company). You can see that the data has been correctly installed by using a registry browser. The JAXR reference implementation is supplied with a browser that works with UDDI registries, which we'll use in this chapter. If you are using the ebXML registry available from http://ebxmlrr.sourceforge.net, you'll find that it has a similar tool. To start the registry browser on a Windows platform, go to the bin directory of the JWSDP installation and type:

 jaxr-browser.bat 

On a Unix/Linux platform, type:

 jaxr-browser.sh 

and enter the URL of the registry you want to browse in the Registry Location field. This field has a drop-down box that contains several commonly used URLs, including one that corresponds to the reference implementation's UDDI registry server when run on the local host ( http://localhost:8000/RegistryServer/RegistryServerServlet ). Having selected a URL, you can enter a name pattern in the Name field and look for Organization s whose names match the pattern. You can also choose to search by Classification , which is a common operation when looking for businesses by business type. In fact, all of the Organization s in the test registry data have associated Classification s. You'll see how these are added later in Section 7.5.5.5. Enter the string "%" in the Name field and press the Search button. This string matches all names in the registry; therefore, three entries should be returned, as shown in Figure 7-14.

Figure 7-14. Using the registry browser to examine registry data
figs/jwsn_0714.gif

To take a closer look at the data for a specific Organization , double-click its entry in the table on the righthand side of the window. This opens a dialog box that displays the contact information and classifications for the Organization , and also allows you to examine its associated Service objects, which will be discussed in the next section. Figure 7-15 shows the information returned for Amazon.com. Note that this is test data only and does not necessarily represent the most current information for Amazon.com, which you can find by looking at the public UDDI registries mentioned at the start of this chapter.

Figure 7-15. More detailed registry information for an Organization
figs/jwsn_0715.gif
7.5.5.2 Services, ServiceBindings, and SpecificationLinks

As described in Section 7.4, a service offered by an Organization is represented in the registry by a hierarchy of Service , ServiceBinding , and SpecificationLink objects. Each of these types can be created using factory methods provided by BusinessLifeCycleManager . Typical use of these methods is illustrated in Example 7-7, which shows the code used to add a Service and a ServiceBinding to the Organization entry for Amazon.com when the test data for this chapter is installed.

Example 7-7. Adding a Service and a ServiceBinding to an Organization in the registry
 // Create a Service entry
Service service = blcm.createService("Amazon web service");
service.setName(blcm.createInternationalString("Amazon web service"));
service.setDescription(blcm.createInternationalString(
    "Web services that allow developers to create" +
    " applications that consume Amazon.com core features"));
        
// Add a ServiceBinding
ServiceBinding binding = blcm.createServiceBinding(  );
binding.setName(blcm.createInternationalString("Amazon service binding"));
binding.setDescription(blcm.createInternationalString(
    "Access to Amazon web services"));
binding.setValidateURI(connected);
binding.setAccessURI("http://soap.amazon.com/onca/soap");

// Add the binding to the service and
// the service to the organization
// "list" is an existing, empty ArrayList
list.add(binding);
service.addServiceBindings(list);
list.clear(  );
amazon.addService(service); 

A Service object represents a single service of some kind (possibly a web service) that an Organization provides. An Organization may have any number of associated services. Amazon.com provides a web service interface that allows browsing and purchasing of of items in its online store. The Service object just created is intended to encapsulate that service. The information required to access the service is provided by a ServiceBinding nested within the Service object. Each ServiceBinding describes a particular means that can be used to access the service. In the case of Amazon.com, there is one ServiceBinding that advertises SOAP-based access to the service. [9] Additional access methods, perhaps a user-to-business interface using HTTP to be displayed in a browser, could be advertised by adding additional ServiceBinding objects.

[9] You cannot tell from the ServiceBinding that the access being offered is SOAP-based; you have to examine the SpecificationLink s to infer this.

The ServiceBinding includes an attribute called accessURI that associates an address with the binding. There is no fixed meaning for this attribute ” it might, for example, be an HTTP or mailto : URL to which a SOAP message can be sent. In order to know how to interpret it, you need to read the technical specification for the service binding. As with ExternalLink s, it is possible to use the setValidateURI( ) method (of ServiceBinding ) to specify whether this URI should be validated when the registry entry is created.

In some cases, a ServiceBinding does not directly reference the service implementation but is instead redirected to another ServiceBinding . It might be convenient to do this if you are creating several Organization entries that have some service implementations in common, and you want only to specify the binding details once so that they can easily be changed if necessary. To do this, you can use the ServiceBinding setTargetBinding( ) method to install a reference to the ServiceBinding that holds the real access information. It is not legal to have both a target binding and an access URI in the same ServiceBinding object.

A ServiceBinding can have a number of associated SpecificationLink s. A SpecificationLink is intended to provide a link to information that provides the technical details necessary for a developer to access the service using the binding that it is associated with. An organization might, for example, create human-readable documentation that describes the service and/or a formal specification of the service interface such as a WSDL document. Each of these could be associated with the ServiceBinding using separate SpecificationLink objects.

The useful information is included within the SpecificationLink using a specificationObject attribute, which is formally of type RegistryObject . The way in which you might use a SpecificationLink to refer to the WSDL definition for a web service is not formally defined, but there are conventions that are in use within both the ebXML and UDDI registries.

7.5.5.3 Storing a WSDL document reference in an ebXML registry

Since an ebXML registry has an associated repository, it is possible to actually hold documentation referred to from a SpecificationLink within the repository in the form of an ExtrinsicObject . This means that a registry client can obtain the WSDL document (or any other form of technical specification) directly from the registry rather than having to be redirected to the owning company. An example that shows how to store a WSDL document in an ebXML registry/repository is shown in Section 7.5.9.1, later in this chapter.

7.5.5.4 Storing a WSDL document reference in a UDDI registry

The way in which WSDL document references should be stored in a UDDI registry is not mandated by the UDDI registry specifications. Instead, there is a "best practice" document available from the OASIS web site at http://www.oasis-open.org/committees/uddi-spec/bps.shtml that recommends a convention to be followed. This document uses terms from the UDDI registry information model to describe how WSDL should be referenced. In this section, you'll see how this recommendation can be mapped to the information model defined by the JAXR specification so that it can be used with the JAXR API.

The simplest way to explain the recommendation is to describe an example JAXR implementation of it, which is shown in Example 7-8.

Example 7-8. Creating a reference to a WSDL document in a UDDI registry
 // Set up a SpecificationLink
SpecificationLink sLink = blcm.createSpecificationLink(  );
sLink.setName(blcm.createInternationalString("WSDL specification"));    
sLink.setUsageDescription(blcm.createInternationalString("WSDL specification"));

// Create the specification object
Concept sConcept = blcm.createConcept(null, "AmazonWSDL", "AmazonWSDL");
ExternalLink eLink = blcm.createExternalLink(
    "http://soap.amazon.com/schemas/AmazonWebServices.wsdl",
    "WSDL Specification");
eLink.setValidateURI(connected);
sConcept.addExternalLink(eLink);
ClassificationScheme uddiTypes = bqm.findClassificationSchemeByName(
    null, "uddi-org:types");
Classification wsdlClass = blcm.createClassification(uddiTypes, "wsdlSpec",
    "wsdlSpec");
sConcept.addClassification(wsdlClass);
ArrayList list = new ArrayList(  );
list.add(sConcept);
BulkResponse res = blcm.saveConcepts(list);
list.clear(  );
Collection coll = res.getCollection(  );
sConcept.setKey((Key)coll.iterator(  ).next(  ));

// Store the specification object
sLink.setSpecificationObject(sConcept);

// Associate the SpecificationLink with the ServiceBinding
// and set the accessURI
binding.addSpecificationLink(sLink);
binding.setValidateURI(connected);
binding.setAccessURI("http://soap.amazon.com/onca/soap"); 

The first part of this code creates the SpecificationLink object that contains the reference to the WSDL document. Since the UDDI registry does not have a repository, it is not possible for the SpecificationLink to refer directly to the WSDL specification. Instead, the object stored in the SpecificationLink must be a Concept object that has the following properties:

  • It has an ExternalLink that points to the WSDL document.

  • It has a Classification with value wsdlSpec associated with a ClassificationScheme called uddi-org:types . This ClassificationScheme is defined by the UDDI specification and must be made available by all JAXR providers for UDDI registries.

Having created the Concept and added the appropriate ExternalLink and Classification , the code in Example 7-8 uses the setSpecificationObject( ) method to install it in the SpecificationLink and then adds the SpecificationLink to the ServiceBinding . Finally, the accessURI attribute of the ServiceBinding is set to point to the actual URL of the web service itself. [10]

[10] This last step is also shown in Example 7-7, but is repeated here for the sake of clarity.

When a registry client locates the Organization entry for Amazon.com and accesses this ServiceBinding , it is able to deduce that the SpecificationLink refers to a WSDL document by virtue of the fact that its specification object (the Concept ) has the classification wsdlSpec from the uddi-org:types ClassificationScheme . The WSDL document can then be retrieved from the location specified by the ExternalLink associated with the specification object and used to create the JAX-RPC client-side artifacts or to build a SAAJ client that can access the service.

The final point to note is that, as you saw in Chapter 5, the WSDL document for a service may contain service and port elements that provide the URL at which the service defined by the document may be accessed. That being the case, why is it necessary to also include the service access point address as the accessURI of the ServiceBinding when it could be obtained from the WSDL document? The intent of this apparent duplication of information is to accomodate WSDL documents that don't include the service and port information. As described in Section 5.2.9.2, it is quite reasonable to separate the reusable parts of a WSDL definition (i.e., the type definitions and the service bindings) from the service element, which simply specifies where an instance of the service can be reached and is therefore not reusable. As an example of a case in which this might be useful, suppose that at some future time a group of companies agrees on a common service interface for an online shopping service. This common interface would be expressed as a WSDL definition and placed in a central location, probably under the control of the industry body responsible for the specification of the interface. This WSDL document would not, of course, indicate where actual implementations of the service could be found. Instead, the ServiceBinding elements associated with the Organization entries for each online shopping store would refer to the single WSDL document describing the service that they provide from a SpecificationLink , and the URL of its particular implementation of the shopping interface would be provided using the accessURI attribute of the ServiceBinding .

7.5.5.5 Classifying registry objects

Any registry object can have zero or more associated Classification s. As mentioned earlier in this chapter, Classification s are added to allow registry users to search for businesses or services using criteria such as geographical location or business type. Adding Classification s to an object is simply a matter of creating the appropriate Classification object and calling the RegistryObject addClassification( ) or addClassifications( ) method. You have already seen how to create a Classification object to represent a specific element of a classification scheme (see Section 7.5.2.3, earlier in this chapter.) The code extract shown in Example 7-9 illustrates how the Classification s listed in Figure 7-15 were added to the Organization entry for Amazon.com.

Example 7-9. Adding Classifications to RegistryObjects
 ClassificationScheme naics = bqm.findClassificationSchemeByName(null, "%naics%");
if (naics == null) {
    naics = bqm.findClassificationSchemeByName(null, "%NAICS%");
    if (naics == null) {
        System.out.println("COULD NOT FIND NAICS CLASSIFICATION SCHEME.");
        System.exit(1);
    }
}
ClassificationScheme isocs = bqm.findClassificationSchemeByName(null, "%iso%");
if (isocs == null) {
    isocs = bqm.findClassificationSchemeByName(null, "%ISO%");
    if (isocs == null) {
        System.out.println("COULD NOT FIND ISO CLASSIFICATION SCHEME.");
        System.exit(1);
    }
}

ArrayList amazonClass = new ArrayList(  );
amazonClass.add(blcm.createClassification(naics, 
    "Book, Periodical, and Music Stores", "4512"));
amazonClass.add(blcm.createClassification(isocs, "United States", "US"));
amazon.addClassifications(amazonClass); 
7.5.5.6 Saving content in the registry

Content created using the various createXXX( ) methods of BusinessLifeCycleManager exists only within the client application until it is explicitly saved. One way to save previously created objects is to use the saveObjects( ) method:

 public BulkResponse saveObjects(Collection objects) throws JAXRException; 

The Collection passed to this method contains any number of RegistryObject s that need not all be of the same type. As each object is saved, a unique key is generated for it and entered into the Collection that is returned in the BulkResponse . If any of the objects could not be saved for any reason, then a SaveException is generated and stored in the set of exceptions associated with the BulkResponse .

Note that the key attribute of a RegistryObject held within a client application is not automatically updated as a result of a successful save operation, even though the object is assigned a valid key by the registry. Furthermore, even though the key is returned in the BulkResponse , since Collection s are not ordered, there is no way to match each returned key with the RegistryObject that it is associated with, unless only one object is saved in each call. The only way to acquire the key for a RegistryObject is to use a findXXX( ) method to retrieve the object, and to use the getKey( ) method to extract it.

BusinessLifeCycleManager provides additional methods that allow groups of objects of the same type to be saved together:

 public BulkResponse saveAssociations(Collection associations, 
   boolean replace) throws JAXRException;
public BulkResponse saveClassificationSchemes(Collection schemes) 
   throws JAXRException;
public BulkResponse saveConcepts(Collection concepts) throws JAXRException;
public BulkResponse saveOrganizations(Collection organizations) 
   throws JAXRException;
public BulkReponse saveServiceBindingd(Collection bindings) 
   throws JAXRException;
public BulkResponse saveServices(Collection services) throws JAXRException; 

All of the saveXXX( ) methods either create a new registry object or update an existing one if the object already exists in the registry. An update occurs if the object being saved contains a valid key. A perhaps unexpected consequence of this is that it is possible to create any number of RegistryObject s of the same type with the same name, simply by calling methods like createOrganization( ) and then saving the objects that are returned. To avoid this, it is advisable to check before adding new registry content whether the objects that it contains already exist in the registry.

When a memory-resident RegistryObject is saved (or updated), the provider is required to traverse all of the objects that it references and create or update their registry counterparts as well. As a result of this, in order to create registry entries for a set of Organization s together with their associated links, services, and service bindings, it is only necessary to create a Collection containing the Organization objects and use the saveOrganizations( ) method to save the content of this Collection :

 ArrayList orgs = new ArrayList(  );
orgs.add(ora);
orgs.add(kbedge);
orgs.add(amazon);
BulkResponse res = blcm.saveOrganizations(orgs); 
7.5.5.7 Associations

It is sometimes useful to be able to reflect the existence of a relationship of some kind between two objects within the registry. A business, for example, might want to indicate that it makes use of the services of another organization whose details can be found in the same registry. Relationships of this type can be recorded in the registry by creating an association between a pair of RegistryObject s, which is represented by an object of type javax.xml.registry.infomodel.Association . An Association has three major attributes, which are illustrated in diagram form in Figure 7-16.

Figure 7-16. An Association object
figs/jwsn_0716.gif
Source object

The object to which the Association is applied.

Target object

The object that is to be considered to be associated with the source object.

Association type

Describes the nature of the association.

The JAXR specification defines 15 different association types, which are listed here:

Contains

EquivalentTo

Extends

ExternallyLinks

HasChild

HasMember

HasParent

Implements

InstanceOf

RelatedTo

Replaces

ResponsibleFor

SubmitterOf

Supersedes

Uses

 

Notice that each of these association types is clearly directional. To reflect this fact, the two RegistryObject s involved are labeled as the source and target of the association. The example shown in Figure 7-16 expresses the fact that RegistryObject "A" uses RegistryObject "B". Here, the association type is "Uses", the source object is "A", and the target object is "B".

Although an Association can theoretically be applied to any pair of RegistryObject s, limitations may be imposed by specific registry types. In particular, the UDDI registry in the JAXR reference implementation permits Association s to be made only between Organization s.

There are two distinct Association types: intramural and extramural . An intramural Association is one in which the source and target objects, and the Association itself, are all submitted by the same user. An extramural Association is one in which at least one of the objects does not have the same owner (i.e., was submitted to the registry by a different user) as the Association itself.

In order to be considered trustworthy, an extramural Association must be confirmed by the owner of both objects involved. A registry user confirms an Association by using the confirmAssociation( ) method of BusinessLifeCycleManager , and may unconfirm it at any point using the unConfirmAssocation( ) method. The isConfirmed( ), isConfirmedBySourceOwner( ) , and isConfirmedByTargetOwner( ) methods of Association can be used to retrieve the current confirmation status. Extramural Association s are not visible to registry owners that are not a party to the Association until they have been confirmed.

An intramural Association is always considered to be confirmed, since the three objects involved are all owned by the same registry user.

An Association is created using the createAssociation( ) method of LifeCycleManager :

 public Association createAssociation(RegistryObject target, Concept type)
   throws JAXRException; 

The association type required by this method is a Concept from an enumerated type under a ClassificationScheme called AssociationType . The code extract in Example 7-10 shows how to create and apply a "Uses" Association between two Organization s.

Example 7-10. Creating an Association between two Organizations
 // Find the source organization
ArrayList list = new ArrayList(  );
list.add("%Reilly%");
BulkResponse res = bqm.findOrganizations(null, list, null, null, null, null);
Collection coll = res.getCollection(  );
Iterator iter = coll.iterator(  );
if (!iter.hasNext(  )) {
    System.out.println("Please load sample data into the registry");
    System.exit(1);
}
Organization ora = (Organization)iter.next(  );
list.clear(  );

// Find the target organization
list.add("%Keyboard%");
res = bqm.findOrganizations(null, list, null, null, null, null);
coll = res.getCollection(  );
iter = coll.iterator(  );
if (!iter.hasNext(  )) {
    System.out.println("Please load sample data into the registry");
    System.exit(1);
}
Organization kbedge = (Organization)iter.next(  );
list.clear(  );
        
// Get the concept for the association type
ClassificationScheme types = bqm.findClassificationSchemeByName(null,
    "AssociationType");
if (types == null) {
    System.out.println("Cannot find AssociationTypes scheme");
    System.exit(1);
}
String path = "/" + types.getKey(  ).getId(  ) + "/Uses";
Concept uses = bqm.findConceptByPath(path);
if (uses == null) {
    System.out.println("Cannot find 'Uses' concept");
    System.exit(1);
}

// Create the association
Association a = blcm.createAssociation(kbedge, uses);
ora.addAssociation(a);

// Save the association
list.add(a);
res = blcm.saveAssociations(list, false);
if (res.getExceptions(  ) != null) {
    System.out.println("Failed to save association");
    iter = res.getExceptions(  ).iterator(  );
    while (iter.hasNext(  )) {                
        System.out.println(iter.next(  ));
    }
    System.exit(1);
}
list.clear(  ); 

The first part of this code uses the findOrganizations( ) method of BusinessQueryManager to obtain references to the source and target Organization s, which are O'Reilly & Associates and Keyboard Edge Limited, respectively. The next step is to obtain the Concept for the type of Association to be created, which in this case is a "Uses" Association . Since the association types are predefined enumerations, the JAXR specification allows the path /AssociationType/Uses to be passed to findConceptByPath( ) to get a reference to this Concept . However, as noted in Section 7.4.4.2, earlier in this chapter, this path is a special case that is not supported by all providers. The code shown here does not rely on the availability of the special case, because it retrieves the ClassificationScheme object for the enumeration and builds the path using its unique identifier, as described earlier in Section 7.5.2.3.

The Association is created by supplying the target object and the type to the LifeCycleManager createAssociation( ) method:

 Association a = blcm.createAssociation(kbedge, uses); 

Note that the source object is not supplied ” instead, it is implied by calling the addAssociation( ) method of the source object itself, which is the Organization object for O'Reilly & Associates:

 ora.addAssociation(a); 

Finally, the Association is made effective by storing it in the registry:

 list.add(a);
res = blcm.saveAssociations(list, false); 

The saveAssocations( ) method requires a Collection of Assocation s and a Boolean argument that, if true, replaces all existing Association s owned by the caller with those in the supplied Collection . This argument is usually false, which has the effect of adding the supplied Association s to those already in existence. There is no need to save the affected Organization objects because they are automatically updated.

Since the two Organization s and the Association are all owned by the same user, this will be an intramural Association , and therefore there is no need for it to be explicitly confirmed.

You can obtain the list of Association s for a RegistryObject by calling its getAssociations( ) method. You can also use the findAssociations( ) and findCallerAssociations( ) methods of BusinessQueryManager to search for Association s using appropriate criteria.

7.5.6 Creating User-Defined Classification Schemes and Enumerated Types

It is sometimes useful to be able to add your own classification scheme or enumerated type to the registry. Since an enumerated type is just a classification scheme with only a single hierarchy level, the same technique can be used to create either. Unfortunately, this is one of the few cases in which you have to use different approaches depending on the actual type of the registry that is being used. In this section, you'll see how to add an enumerated type called TestEnum that has the distinguished values 1, 2, 3, and 4 with both the UDDI and ebXML providers.

7.5.6.1 Adding classification schemes to an ebXML provider

An ebXML registry provides the internal mechanisms necessary to install a new ClassificationScheme and its associated Concept hierarchy within the registry itself. The code required to do this is very simple and is shown in Example 7-11.

Example 7-11. Creating a custom classification scheme in an ebXML registry
 ClassificationScheme scheme = blcm.createClassificationScheme("TestEnum", 
    "Custom Enumeration");
scheme.setValueType(ClassificationScheme.VALUE_TYPE_UNIQUE);
ArrayList list = new ArrayList(  );
list.add(blcm.createConcept(scheme, "Value 1", "1"));
list.add(blcm.createConcept(scheme, "Value 2", "2"));
list.add(blcm.createConcept(scheme, "Value 3", "3"));
list.add(blcm.createConcept(scheme, "Value 4", "4"));
scheme.addChildConcepts(list);
blcm.saveConcepts(list);
list.clear(  );

list.add(scheme);
blcm.saveClassificationSchemes(list); 

The first line creates the ClassificationScheme itself, supplying the name TestEnum under which it can be found by other registry clients. The value type of the scheme is then set to VALUE_TYPE_UNIQUE , which is one of three possible values that describe the constraint that will apply to the values of the Concept s within this ClassificationScheme : [11]

[11] Note that the value type attribute can be used only with a level 1 JAXR provider.

VALUE_TYPE_UNIQUE

Each Concept has a unique value (obtainable by calling its getValue( ) method).

VALUE_TYPE_NON_UNIQUE

The same value may be associated with more then one Concept in the hierarchy.

VALUE_TYPE_EMBEDDED_PATH

The value associated with each Concept is the complete path from the ClassificationScheme to the Concept itself. The developer is responsible for arranging that each Concept has the correct value. Obviously, this implies VALUE_TYPE_UNIQUE .

Next, the individual Concept s are created using the createConcept( ) method of LifeCycleManager , of which there are two variants that differ according to whether the name is suppled as a String or an InternationalString :

 public Concept createConcept(RegistryObject parent, String name, String 
value) throws JAXRException;
public Concept createConcept(RegistryObject parent, InternationalString 
name, String value) 

throws JAXRException; 

In this case, the parent argument is supplied as a reference to the ClassificationScheme to which the Concept s will belong, since there is only a single hierarchy level. In the case of a more complex hierarchy, a Concept might be created with another Concept as its parent.

The name argument supplies the display name for the Concept . The value argument is the one that really matters ” it is the one that is considered to be the distinguished value in the case of an enumerated type. It is also the value that must appear in the path used to locate the Concept when using the getConceptByPath( ) method.

Having created the ClassificationScheme and all of the Concept s, the final step is to commit them all to the registry using the saveConcepts( ) and saveClassificationSchemes( ) methods. Once this is done, the scheme becomes visible to all registry clients.

7.5.6.2 Adding classification schemes to a UDDI provider

Unfortunately, UDDI registries currently do not support the storage within the registry of user-defined classification schemes that have Concept hierarchies. [12] However, a JAXR provider is required to provide a means whereby such a scheme can be made available to registry clients. How this is achieved is implementation-dependent. Here, we'll look at the mechanism provided by the JAXR reference implementation.

[12] In fact, you can create ClassificationScheme s and

 Concepts 

within a UDDI registry, but it is not possible to link them in a hierarchal fashion. You can actually run the code shown in Example 7-11 against a UDDI registry and it will appear to work. However, although the ClassificationScheme and Concept s are created, you won't be able to locate any of the Concept s by calling findConceptByPath( ) or by trying to access them as children of the ClassificationScheme object.

In the reference implementation, user-defined classification schemes are loaded by the JAXR provider from one or more files whose locations are provided by a system property called com.sun.xml.registry.userTaxonomyFilenames . This property consists of a list of files, separated by vertical bar () characters. Note that, at least at the time of this writing, the filenames are not allowed to have any embedded spaces. This may be inconvenient for Windows users, since users' home directories very frequently have pathnames that contain spaces. At some point during the execution of a JAXR client application, the provider reads this property, extracts the filenames that it contains, and attempts to load the contents of each named file as a Concept hierarchy for a ClassificationScheme .

Note that com.sun.xml.registry.userTaxonomyFilenames is a system property, and not one of the properties set on the ConnectionFactory object.

The steps that a developer must take to create a custom ClassificationScheme are as follows:

  1. Define the ClassificationScheme itself in the registry using the createClassificationScheme( ) method, then save it.

  2. Get the UUID assigned to the ClassificationScheme .

  3. Create the file that contains the definition of the Concept hierarchy. In order to create this file, you need the scheme's UUID.

  4. In JAXR client applications, include the full pathname of the file in the com.sun.xml.registry.userTaxonomyFilenames system property.

Once these steps are completed, you can treat the ClassificationScheme in the same way as if it had been installed into the registry. In particular, you can use findConceptByPath( ) to locate the Concept objects within the hierarchy, and then use them to create internal Classification s.

The installation process for the example registry data for this book includes the creation of a ClassificationScheme called TestEnum , which we will use to illustrate how to populate a custom enumeration (which is actually a single-level ClassificationScheme ). By installing the test registry data, therefore, you completed the first of the steps listed previously. [13] To get the UUID, make chapter7\jaxr your working directory, and type the command:

[13] Refer to Section 7.3 earlier in this chapter for the procedure for installing the test data in the registry, if you have not already done so.

 ant run-uddi-enum-client-step1 

This command retrieves the UUID of the ClassificationScheme from the registry and displays it. Make sure to note the UUID, which will look something like this:

 uuid:f1ef390d-08f1-ef39-3f48-e3438b38f908 

You'll need this value for the second part of this example. The next step is to define the hierarchy of Concept s within the ClassificationScheme . The example source code for this chapter includes a file that defines the same Concept hierarchy as that created programmatically within the ebXML registry in the previous section. The content of this file (which is called chapter7\jaxr\CustomScheme.xml.template ) is shown in Example 7-12.

Example 7-12. Template for a definition of a custom ClassificationScheme
 <PredefinedConcepts>
  <JAXRClassificationScheme id="@@" name="TestEnum">
    <JAXRConcept id="@@/1" name="Value 1" parent="@@" code="1"></JAXRConcept>
    <JAXRConcept id="@@/2" name="Value 2" parent="@@" code="2"></JAXRConcept>
    <JAXRConcept id="@@/3" name="Value 3" parent="@@" code="3"></JAXRConcept>
    <JAXRConcept id="@@/4" name="Value 4" parent="@@" code="4"></JAXRConcept>
  </JAXRClassificationScheme>
</PredefinedConcepts> 

Within this file, the token @@ represents the places at which the UUID of the ClassificationScheme itself should be inserted to convert this template into a valid definition. Looking at the structure of this XML file, the elements are used as follows:

PredefinedConcepts

The root element of the file and must always be present.

JAXRClassificationScheme

Wraps the definition of a new ClassificationScheme . Any number of these elements may appear within the PredefinedConcepts element.

The id attribute supplies the UUID of the ClassificationScheme in the registry for which this element defines the Concept hierarchy, while the name attribute fairly obviously gives the scheme's name.

JAXRConcept

Defines a Concept at some level within the ClassificationScheme . The id attribute supplies a unique ID for the Concept . It is typically formed by appending a unique value to the UUID of the scheme itself.

The name attribute supplies the name of the Concept .

The code attribute gives the actual value of the Concept , which is returned by its getValue( ) method.

The parent attribute is the id of the Concept that resides immediately above this Concept in the hierarchy, or of the ClassificationScheme itself in the case of a top-level Concept .

The Ant buildfile in the chapter7\jaxr directory contains a target that replaces the @@ token in the template file with the UUID of the ClassificationScheme , and writes the result to a given location in the filesystem. To use this target, you need to add the following two properties to the jwsnutJaxrExamples.properties file in your home directory:

 CUSTOM_ENUM_UUID = uuid:f1ef390d-08f1-ef39-3f48-e3438b38f908
CUSTOM_ENUM_FILE = c:\temp\CustomSchemes.xml 

The value of the CUSTOM_ENUM_UUID property must be the UUID of the ClassificationScheme that was obtained by running the first part of this example, not the value just shown. The CUSTOM_ENUM_FILE property gives the name of the file to be created by the editing process, which you are free to choose, subject to the following important constraints that apply to the filename:

  • It cannot contain spaces.

  • For Windows users, the "\" separator must be represented as "\\", since a single "\" is taken as an escape character.

The editing process can be carried out using the command:

 ant build-enum-file 

The result of performing this operation on the template from Example 7-12 is shown in Example 7-13.

Example 7-13. Edited ClassificationScheme definition file
 <PredefinedConcepts>
  <JAXRClassificationScheme id="uuid:f1ef390d-08f1-ef39-3f48-e3438b38f908"
    name="TestEnum">
    <JAXRConcept id="uuid:f1ef390d-08f1-ef39-3f48-e3438b38f908/1" name="Value 1"
        parent="uuid:f1ef390d-08f1-ef39-3f48-e3438b38f908" code="1"/>
    <JAXRConcept id="uuid:f1ef390d-08f1-ef39-3f48-e3438b38f908/2" name="Value 2" 
        parent="uuid:f1ef390d-08f1-ef39-3f48-e3438b38f908" code="2"/>
    <JAXRConcept id="uuid:f1ef390d-08f1-ef39-3f48-e3438b38f908/3" name="Value 3" 
        parent="uuid:f1ef390d-08f1-ef39-3f48-e3438b38f908" code="3"/>
    <JAXRConcept id="uuid:f1ef390d-08f1-ef39-3f48-e3438b38f908/4" name="Value 4" 
        parent="uuid:f1ef390d-08f1-ef39-3f48-e3438b38f908" code="4"/>
  </JAXRClassificationScheme>
</PredefinedConcepts> 

In order to use this file, a JAXR client application must contain the following line of code, which results in the definition of the custom classification being loaded from the file given by the value of the com.sun.xml.registry.userTaxonomyFilenames property:

 System.setProperty("com.sun.xml.registry.userTaxonomyFilenames", 
   "c:\temp\CustomScheme.xml"); 

The following command:

 ant run-uddi-enum-client-step2 

runs an example that sets the com.sun.xml.registry.userTaxonomyFilenames property as shown previously and uses the ClassificationScheme getDescendentConcepts( ) method to demonstrate that the four Concept s with values 1, 2, 3 and 4 listed in Example 7-13 have been created. It also uses the findConceptByPath( ) method to access one of the Concept s, as shown in Example 7-14.

Example 7-14. Using the getDescendentConcepts( ) and getConceptByPath( ) methods
 // First find the classification scheme
ClassificationScheme scheme = bqm.findClassificationSchemeByName(null,
    "TestEnum");
if (scheme == null) {
    System.out.println("Please install the registry test data");
    System.exit(1);
}
System.out.println("Scheme id is " + scheme.getKey(  ).getId(  ));

// Now look for the Concepts
Collection coll = scheme.getDescendantConcepts(  );
System.out.print("Enumeration values: ");
Iterator iter = coll.iterator(  );
while (iter.hasNext(  )) {
    Concept c = (Concept)iter.next(  );
    System.out.print(c.getValue(  ) + "  ");
}
System.out.println(  );

// Locate by path
String path = "/" + scheme.getKey(  ).getId(  ) + "/1";
Concept c = bqm.findConceptByPath(path);
if (c != null) {
    System.out.println("Located concept by path: value = " + c.getValue(  ));
} else {
    System.out.println("Failed to locate concept by path");
} 

The result of running this command should be:

 Scheme id is uuid:f1ef390d-08f1-ef39-3f48-e3438b38f908
 Enumeration values: 1  2  3  4
 Located concept by path: value = 1 

None of the code used in Example 7-14 is specific to the UDDI registry, and you can actually run the same example against the ebXML registry to demonstrate that the technique used in the previous section to programmatically create the same ClassificationScheme produces an identical result. You can try this using the command:

 ant run-ebxml-enum-client. 

Although this example creates a ClassificationScheme with a single hierarchy level, it is equally simple to produce one with more than one level by making appropriate use of the parent attribute of the JAXRConcept element. For example, Example 7-15 demonstrates how to add a Concept with the value 41 immediately below the one with the value 4.

Example 7-15. Adding an additional level to a ClassificationScheme
 <PredefinedConcepts>
  <JAXRClassificationScheme id="uuid:f1ef390d-08f1-ef39-3f48-e3438b38f908"
      name="TestEnum">
    <JAXRConcept id="uuid:f1ef390d-08f1-ef39-3f48-e3438b38f908/1" name="Value 1" 
        parent="uuid:f1ef390d-08f1-ef39-3f48-e3438b38f908" code="1"/>
    <JAXRConcept id="uuid:f1ef390d-08f1-ef39-3f48-e3438b38f908/2" name="Value 2" 
        parent="uuid:f1ef390d-08f1-ef39-3f48-e3438b38f908" code="2"/>
    <JAXRConcept id="uuid:f1ef390d-08f1-ef39-3f48-e3438b38f908/3" name="Value 3" 
        parent="uuid:f1ef390d-08f1-ef39-3f48-e3438b38f908" code="3"/>
    <JAXRConcept id="uuid:f1ef390d-08f1-ef39-3f48-e3438b38f908/4" name="Value 4" 
        parent="uuid:f1ef390d-08f1-ef39-3f48-e3438b38f908" code="4"/>  <JAXRConcept 
        id="uuid:f1ef390d-08f1-ef39-3f48-e3438b38f908/41" name="Value 41" 
        parent="uuid:f1ef390d-08f1-ef39-3f48-e3438b38f908/4" code="41"/>  </JAXRClassificationScheme>
</PredefinedConcepts> 

Note the following with regard to the line that has been added:

  • The code attribute is set to 41, which is the required value for the new Concept .

  • The parent attribute is set to the ID value of the Concept 4, which indicates that the new Concept should be a child of that Concept .

  • The id attribute for the new Concept ends in /41 rather than /4/41. This is actually a matter of choice, and either would be valid. The IDs are not required to reflect the actual hierarchy, since that job is done by the parent attribute.

You can see more examples of the use of these XML elements by looking at the definition files that the JAXR provider for UDDI uses to create the NAICS, ISO-3166, and UNSPSC classification schemes. These can be found in the jaxr-ri.jar file for the JAXR reference implementation, at the following locations:

NAICS

com\sun\xml\registry\common\tools\resources\naics.xml

ISO 3166

com\sun\xml\registry\common\tools\resources\iso3166.xml

UNSPSC

com\sun\xml\registry\common\tools\resources\unspsc.xml

7.5.7 Postal Addresses

The JAXR API represents address information using the PostalAddress interface, which has attributes that map to the various parts of the address, such as street number, street name, and so on. Mapping this structure to an ebXML registry is simple, since its information model represents an address in exactly the same way. However, the UDDI registry does not have such a well-defined address structure. The UDDI information model stores address information in an address element, which may contain any number of nested addressLine elements. The information model specification does not define how individual addressLine s are to be interpreted as street number, street, city, and so on. It does, however, allow two attributes to be associated with each addressLine that can be used to provide linkage between its content and the part of the original address to which it corresponds. The values that these attributes may take are, however, not defined within the specification. Instead, they are given meaning by an address scheme, which is itself referred to from the parent address element. Registries can therefore define their own ( essentially arbitrary) address schemes, and as a result, it is not possible for a JAXR provider to implement a fixed mapping between the attributes of a PostalAddress object and the addressLine s of the registry's address structure.

Example 7-16 shows an example of an address stored in a UDDI registry. Note that each address line has a keyName and a keyValue attribute. These attributes indicate what role the addressLine plays in the original address. To interpret each addressLine , you need to look at the address scheme referred to by the tModelKey attribute of the surrounding address element. In fact, the representation shown here is the default provided by a JAXR provider for UDDI, and the tModelKey attribute refers to a ClassificationScheme called PostalAddressAttributes that is defined within the provider itself. [14] You can think of this as the "reference" layout for addresses, as recognized by the JAXR provider in the reference implementation.

[14] This ClassificationScheme is defined using the technique shown in Section 7.5.6, earlier in this chapter. You can find its definition in the file jaxrconcepts.xml , which is in the com\sun\xml\registry\common\tools\resources directory of the jaxr-ri.jar file.

Example 7-16. A PostalAddress stored in a UDDI registry using the default JAXR address scheme
 <address sortCode="" tModelKey="PostalAddressAttributes" useType="Headquarters">
    <addressLine keyName="StreetNumber" keyValue="StreetNumber">1005
        </addressLine>
    <addressLine keyName="Street" keyValue="Street">Gravenstein Highway North
        </addressLine> 
   <addressLine keyName="City" keyValue="City">Sebastopol</addressLine>
    <addressLine keyName="State" keyValue="State">CA</addressLine>
    <addressLine keyName="PostalCode" keyValue="PostalCode">95472</addressLine>
    <addressLine keyName="Country" keyValue="Country">USA</addressLine>
</address> 

The JAXR reference implementation also includes a definition for a second address ClassificationScheme called IBMDefaultPostalAddressAttributes . Using this scheme, the same address would be represented slightly differently, as shown in Example 7-17.

Example 7-17. A PostalAddress stored using the IBM postal address scheme
 <address sortCode=""
    tModelKey="uuid:6EAF4B50-4196-11D6-9E2B-000629DC0A2B" useType="Headquarters">
    <addressLine keyName="StreetAddressNumber" keyValue="StreetAddressNumber">
1005</addressLine>
    <addressLine keyName="StreetAddress" keyValue="StreetAddress">
        Gravenstein Highway North</addressLine>
    <addressLine keyName="City" keyValue="City">Sebastopol</addressLine>
    <addressLine keyName="State" keyValue="State">CA</addressLine>
    <addressLine keyName="ZipCode" keyValue="ZipCode">95472</addressLine>
    <addressLine keyName="Country" keyValue="Country">USA</addressLine>
</address> 

Note first that the tModelKey of the address element is different: the value shown in Example 7-17 is the UUID assigned to the IBMDefaultPostalAddressAttributes scheme within the registry. This indicates immediately that the addressLine s within this element are not going to be encoded in the same way as they would be in the default JAXR representation, and, as you can see, there are some differences. For example, the addressLine s that represent the street address and postal code attributes of the PostalAddress object have their keyValue attributes set to StreetAddress and ZipCode in this case, whereas in Example 7-16, the values Street and PostalCode are used.

7.5.7.1 Creating a representation of a registry addressing scheme

There are two problems for the JAXR provider: how to map the attributes of a given PostalAddress attribute to the proper set of addressLines for the UDDI registry that it is connected to, and, when reading an address from the registry, how to determine which PostalAddress attribute each addressLine corresponds to. The solution is to require the registry client to provide the information required to perform this mapping. Here's what the client is required to do:

  1. Define the address scheme for the registry as a ClassificationScheme with one concept corresponding to each of the PostalAddress attributes that is stored by the registry's addressLine elements. The name and value attributes for each Concept are taken from the keyName and keyValue attributes of the addressLine element to which it corresponds.

  2. Specify the mapping from the Concept s of the new address scheme to those of the default JAXR addressing scheme (which is a predefined ClassificationScheme called PostalAddressAttributes ).

  3. Include the UUID of the address ClassificationScheme as well as a representation of the Concept mapping in the Properties object supplied to the ConnectionFactory used to create the Connection to the registry.

The mapping that is created for a typical addressing scheme (in this case the IBM scheme used in Example 7-17) is shown in Figure 7-17. The blocks at the top of the diagram represent the ClassificationScheme and Concept s for the default JAXR addressing scheme. These are all predefined within the provider and cannot be renamed . The lower part of the diagram shows the ClassificationScheme and Concept s for the IBM addressing scheme, which is also predefined within the JAXR reference implementation. To create a mapping for a third-party addressing scheme, use the technique described earlier in Section 7.5.6 to define the ClassificationScheme hierarchy to the JAXR provider. The Concept names and values must be taken from the keyName and keyValue attributes that appear in the

 addressLine 

elements within the registry. In this case, you can see by reference to Example 7-17 that these should be StreetAddressNumber , StreetAddress , and so on. The arrows represent the relationship between these Concept s and those defined within the default JAXR addressing scheme.

Figure 7-17. Creating a mapping from a registry address scheme to the JAXR default scheme
figs/jwsn_0717.gif

In order to activate this mapping, you need to set the following two properties in the Properties object passed to the ConnectionFactory :

javax.xml.registry.postalAddress

Specifies the ID of the ClassificationScheme that represents the postal address scheme to be used for the registry.

javax.xml.registry.semanticEquivalences

Specifies the mapping from the Concept s defined for the registry address scheme and the JAXR default postal addressing scheme. The format of this property is shown later in Section 7.5.7.3.

To see how the mapping information is used, consider what happens if the JAXR provider needs to create a PostalAddress object from the registry information shown in Example 7-17. First of all, notice that the tModelKey attribute of the address element contains the ID assigned to the IBM addressing scheme, as shown in the lower part of Figure 7-17. If this ID is supplied to the provider via the javax.xml.registry.postalAddress property, then it knows that it can use the mapping in the javax.xml.registry.semanticEquivalences property to map from the keyName and keyValue attributes in the addressLine elements to the Concept s that make up the standard JAXR addressing scheme.

For the example shown in Example 7-17, the first addressLine has keyValue StreetAddressNumber . Referring to Figure 7-17, you can see that this Concept corresponds to StreetNumber in the JAXR addressing scheme. (In reality, this match is made using the javax.xml.registry.semanticEquivalences property, as you'll see shortly.) The provider knows that the StreetNumber Concept corresponds to the StreetNumber attribute of the PostalAddress object, so it uses the value 1005 to set this attribute. The next addressLine has keyValue StreetAddress . This is mapped to the Street Concept of the JAXR addressing scheme; therefore, the value Gravenstein Highway North is assigned to the Street attribute of the PostalAddress object. The same procedure is used to map all of the addressLines to PostalAddress attributes, and is used in reverse when storing the content of a PostalAddress into the registry.

In summary, the way in which address information in the UDDI registry is translated into PostalAddress objects (and vice versa) depends on the settings of the javax.xml.registry.postalAddressScheme and javax.xml.registry.semanticEquivalences properties in the Properties object associated with the ConnectionFactory at the time that the Connection object for the registry is created. The following sections show in more detail ”and with specific examples ”how the values of these properties affect the translation process, both when a PostalAddress is stored in the registry and when one is created as a result of loading an object from the registry. The explanations that follow make use of a client application supplied with the example source code for this book that stores a PostalAddress in the UDDI registry using the IBM addressing scheme. Before proceeding, you should make chapter7\jaxr your working directory and install the new PostalAddress using the following command:

 ant run-postal-install-client 

The address is stored by creating another Organization and including it as the address of its primary contact. The result of storing the address is shown in Example 7-18.

Example 7-18. A PostalAddress stored in a UDDI registry using an IBM registry address scheme
 <address sortCode="1234"
    tModelKey="uuid:6EAF4B50-4196-11D6-9E2B-000629DC0A2B" useType="Headquarters">
    <addressLine keyName="StreetAddressNumber" keyValue="StreetAddressNumber">
        1005</addressLine>
    <addressLine keyName="StreetAddress" keyValue="StreetAddress">Gravenstein
        Highway North</addressLine>
    <addressLine keyName="City" keyValue="City">Sebastopol</addressLine>
    <addressLine keyName="State" keyValue="State">CA</addressLine>
    <addressLine keyName="ZipCode" keyValue="ZipCode">95472</addressLine>
    <addressLine keyName="Country" keyValue="Country">USA</addressLine>
</address> 

As a result of installing this address, the registry now contains two copies of the address for O'Reilly & Associates. One is stored using the default JAXR addressing scheme, and the other is stored with the IBM scheme. There are two differences between the IBM representation of the address shown in Example 7-18 and the default JAXR representation in Example 7-16:

  • The addressLine elements are different, since a different addressing scheme is used.

  • The address element in Example 7-18 has the value 1234 associated with its sortCode attribute, while in Example 7-16, this attribute is empty. The sortCode attribute is part of the UDDI information model for addresses, but it is not mapped as an attribute in the JAXR PostalAddress interface. You can, however, supply a sortCode value by attaching a Slot to the PostalAddress , using the following code (which is used when creating the PostalAddress in Example 7-18). This is independent of the addressing scheme in use and could also have been done when the address in Example 7-16 was stored.

     address.addSlot(blcm.createSlot(Slot.SORT_CODE_SLOT, "1234", null));  
    // Attach sortCode value 
7.5.7.2 Mapping without a PostalAddress scheme

When the javax.xml.registry.postalAddressScheme property has not been set, the JAXR provider does not know how addresses are mapped within the registry. In this case, when populating a PostalAddress object with registry data, it makes no attempt to guess which addressLine s might correspond to the various attributes of the PostalAddress object. Instead, it adds a Slot called Slot.ADDRESS_LINES_SLOT and stores the text from the addressLine elements as the Slot value (which, as described in Section 7.4.3, earlier in this chapter, is a Collection ). To try this out, use the command:

 ant run-postal-none-client 

Part of the output of this command is shown in Example 7-19.

Example 7-19. Retrieving a PostalAddress with no postal scheme installed
 POSTAL ADDRESS for OReilly & Associates, Inc
     Street number:
     Street:
     City:
     State:
     Postcode:
     Country:

SLOTS:
     sortCode: ;
     addressLines: 1005; Gravenstein Highway North; Sebastopol; CA; 95472; USA; 

As you can see, the PostalAddress attributes are all empty and the information retrieved from the registry is stored in its addressLines slot.

Storing a PostalAddress in a registry when no value has been set for the javax.xml.registry.postalAddressScheme property is slightly different. There are two cases to consider:

  • A PostalAddress may have its own postal address scheme that overrides the default, which may be set using the PostalAddress setPostalScheme( ) method. If this is not null , then the address is stored according to that scheme.

  • Otherwise, the PostalAddress is stored as if the JAXR default postal address scheme were in use. As a result, the address is mapped to the registry as shown in Example 7-16.

7.5.7.3 Using the JAXR default PostalAddress scheme

You can specify use of the default JAXR postal address scheme by setting the javax.xml.registry.postalAddressScheme and javax.xml.registry.semanticEquivalences properties as shown in Example 7-20.

Example 7-20. Specifying use of the default JAXR postal scheme
 props.setProperty("javax.xml.registry.postalAddressScheme",
    "PostalAddressAttributes")
props.setProperty("javax.xml.registry.semanticEquivalences",
  "urn:uuid:PostalAddressAttributes/StreetNumber," +
  "urn:uuid:PostalAddressAttributes/StreetNumber" +
  "urn:uuid:PostalAddressAttributes/Street," +
  "urn:uuid:PostalAddressAttributes/Street" +
  "urn:uuid:PostalAddressAttributes/City," +
  "urn:uuid:PostalAddressAttributes/City" +
  "urn:uuid:PostalAddressAttributes/State," +
  "urn:uuid:PostalAddressAttributes/State" +
  "urn:uuid:PostalAddressAttributes/PostalCode," +
  "urn:uuid:PostalAddressAttributes/PostalCode" +
  "urn:uuid:PostalAddressAttributes/Country," +
  "urn:uuid:PostalAddressAttributes/Country"); 

The value PostalAddressAttributes assigned to the javax.xml.registry.postalAddressScheme property selects the default addressing scheme. The value assigned to the other property simply sets up a direct equivalence between the default scheme and itself. It really should not be necessary to do this but, at least at the time of this writing, the JAXR reference implementation requires it. The format of the value assigned to this property will be described in the next section.

When this scheme is selected, all PostalAddress objects saved to the registry use the default JAXR mapping shown in Example 7-16.

When the provider receives address information from the registry, it inspects the tModelKey attribute of the address element. If it has the value PostalAddressAttributes , then the addressLine s should have keyValue attributes that are consistent with the default postal address scheme ” that is, they should be the same as those shown in Example 7-16. The content of the addressLines with keyValue attributes that are valid are mapped to the corresponding attributes of the PostalAddress object (that is, the value of the addressLine element with the keyValue attribute set to StreetNumber is mapped to the StreetNumber attribute, and so on). If the set of addressLines should, for some reason, contain one or more elements that have keyValue attributes that do not match the values of the Concepts in the PostalAddressAttributes classification scheme, then they are grouped together in a Collection and stored as the value of a Slot.ADDRESS_LINES_SLOT slot that is added to the PostalAddress .

If you type the command:

 ant run-postal-default-client 

you'll see the results of loading two addresses from the registry with the default classification scheme selected. You'll notice that the PostalAddress object for O'Reilly & Associates is properly populated, since it was stored when the default scheme was selected. However, the other address was written to the registry using the IBM postal address scheme. Therefore, when it is retrieved, all of its addressLines are stored in the Slot.ADDRESS_LINES_SLOT slot, as shown in Example 7-21.

Example 7-21. Result of retrieving an address whose postal does not match the selected scheme
 POSTAL ADDRESS for Another Organization
    Street number:
    Street:
    City:
    State:
    Postcode:
    Country:

SLOTS:
    sortCode: 1234;
    addressLines: 1005; Gravenstein Highway North; Sebastopol; CA; 95472; USA; 

This output is actually the same as that shown in Example 7-19, which illustrates that the result of loading an address that uses a different postal address scheme from the one in use by the JAXR provider is the same as if a postal address scheme had not been selected at all. Even though this scheme has several addressLine s that have keyValue s that are valid for the default JAXR address scheme (such as City and Country ), the values still do not appear in the PostalAddress object.

7.5.7.4 Using a different PostalAddress scheme

In order to select a nondefault postal address scheme, you need to properly initialize the same two properties. Example 7-22 shows how you would select the IBM postal address scheme.

Example 7-22. Specifying the use of the IBM postal address scheme
 props.setProperty("javax.xml.registry.postalAddressScheme",  
    "uuid:6EAF4B50-4196-11D6-9E2B-000629DC0A2B") 
props.setProperty("javax.xml.registry.semanticEquivalences",
  "urn:uuid:PostalAddressAttributes/StreetNumber," +
  "urn:uuid:6EAF4B50-4196-11D6-9E2B-000629DC0A2B/StreetAddressNumber" +
  "urn:uuid:PostalAddressAttributes/Street," +
  "urn:uuid:6EAF4B50-4196-11D6-9E2B-000629DC0A2B/StreetAddress" +
  "urn:uuid:PostalAddressAttributes/City," +
  "urn:uuid:6EAF4B50-4196-11D6-9E2B-000629DC0A2B/City" +
  "urn:uuid:PostalAddressAttributes/State," +
  "urn:uuid:6EAF4B50-4196-11D6-9E2B-000629DC0A2B/State" +
  "urn:uuid:PostalAddressAttributes/PostalCode," +
  "urn:uuid:6EAF4B50-4196-11D6-9E2B-000629DC0A2B/ZipCode" +
  "urn:uuid:PostalAddressAttributes/Country," +
  "urn:uuid:6EAF4B50-4196-11D6-9E2B-000629DC0A2B/Country"); 

As before, the value of the javax.xml.registry.postalAddressScheme property must be the ID of the postal address scheme to be used; the value shown here is the UUID assigned to the IBM postal address scheme. The javax.xml.registry.semanticEquivalences property describes the mapping between the Concept s of the default JAXR postal address scheme and the one to be used. Its task is to describe the mapping shown diagrammatically in Figure 7-17. It is made up of a set of Concept pairs, in which each pair is separated from the other pairs by a vertical bar () character. Each pair is made up of two identifiers separated by a comma, where the first identifier is the path for the Concept in the default JAXR postal address scheme, and the second identifier is for the corresponding Concept in the target postal address scheme. For example, the following extract:

 "urn:uuid:PostalAddressAttributes/StreetNumber," +
"urn:uuid:6EAF4B50-4196-11D6-9E2B-000629DC0A2B/StreetAddressNumber" +
"urn:uuid:PostalAddressAttributes/Street," +
"urn:uuid:6EAF4B50-4196-11D6-9E2B-000629DC0A2B/StreetAddress" + 

maps the StreetAddressNumber Concept in the IBM address scheme to the StreetNumber Concept in the JAXR address scheme, and maps the IBM scheme's StreetAddress Concept to the JAXR scheme's Street concept.

If there are Concept s in the target address scheme that do not map to any of those in the default scheme, then they cannot be included. As a result, the corresponding addressLine is mapped to the Slot.ADDRESS_LINES_SLOT slot instead. As an example of this, if the target scheme includes an addressLine with a keyValue of District , which cannot be mapped to one of the standard Concept s, then it will not appear in the javax.xml.registry.semanticEquivalences property and its value will be stored in the slot (although the keyValue is lost). Similarly, if there is no addressLine in the target scheme that can be mapped to one or more of the standard Concept s (e.g., because the target scheme does not have a PostalCode equivalent), the entry for that Concept in the javax.xml.registry.semanticEquivalences property should be omitted, and the corresponding attribute in a PostalAddress object created from registry data will be empty.

When a nondefault scheme is active, all PostalAddress objects saved to the registry are mapped using the addressLine s defined by that scheme, an example of which is shown in Example 7-18 in which the IBM postal address scheme is active. The results of loading a PostalAddress from the registry mirror those described in the last section.

  • Addresses whose tModelKey attribute matches the ID of the selected postal address ClassificationScheme are mapped to PostalAddress attributes as described in the previous section.

  • All other addresses are mapped directly to the Slot.ADDRESS_LINES_SLOT slot of an otherwise empty PostalAddress object.

The command:

 ant run-postal-ibm-client 

retrieves the same two addresses used in the previous section, but this time with the IBM address scheme selected. This time, the O'Reilly & Associates address, which was stored using the JAXR default address scheme, is unloaded into the Slot.ADDRESS_LINES_SLOT slot. The other address is decoded properly, since it was stored using the IBM postal address scheme.

7.5.8 Deleting Registry Objects

Objects in the registry can be deleted using one of the deleteObjects( ) methods provided by BusinessLifeCycleManager :

 public BulkResponse deleteObjects(Collection keys) throws JAXRException;
public BulkResponse deleteObjects(Collection keys, String objectType) throws 
JAXRException; 

These methods remove the objects whose keys are supplied by the Collection argument. The first variant, which allows the deletion of an arbitrary set of objects, is supported only by level 1 providers. If you are using a level 0 provider, you need to use the second variant, which allows the removal of only a single type of object with each call. The type is supplied as the second argument, using one of the constants defined in the LifeCycleManager interface:

 BulkResponse res = blcm.deleteObjects(keys, LifeCycleManager.ORGANIZATION); 

BusinessLifeCycleManager also includes convenience methods that delete objects of specific types:

 public BulkResponse deleteAssociations(Collection associationKeys) 
   throws JAXRException;
public BulkResponse deleteClassificationSchemes(Collection schemeKeys)
   throws JAXRException;
public BulkResponse deleteConcepts(Collection conceptKeys) 
   throws JAXRException;
public BulkResponse deleteOrganizations(Collection organizationKeys) 
   throws JAXRException;
public BulkResponse deleteServiceBindings(Collection bindingKeys) 
   throws JAXRException;
public BulkResponse deleteServices(Collection serviceKeys) 
   throws JAXRException; 

The BulkResponse returned by all of the deleteXXX( ) methods contains the keys for those objects that were successfully removed and a DeleteException for any that were not. An attempt to delete an object that is referenced by another object in the registry may succeed or may result in an InvalidRequestException , depending on the level of checking performed by the registry. Since the JAXR specification does not specify whether deletion of an object automatically results in the deletion of any related objects that are no longer referenced, it may be necessary (depending on the registry implementation) for application code to manually delete all Service s attached to an Organization being deleted as well as all ServiceBinding s for those Service s, and so on.

It is important to note that the Collection s passed to the deleteXXX( ) methods must contain the keys for the objects to be deleted, not the objects themselves. Some JAXR provider implementations (including the UDDI provider in the reference implementation) silently ignore elements of the Collection that are not of type Key , with the result that no objects are deleted and there is no error reported .

7.5.9 Level 1 Registry Features

Level 1 providers, such as those used with ebXML registries, provide additional functionality that can be used by client applications that do not need to be portable to all registry types. This section describes the most important of these additional features. Since the motivation for this functionality comes from ebXML, additional information can be found in the ebXML Registry Service specification, which can be obtained from the OASIS web site at http://www.oasis-open.org.

7.5.9.1 ExtrinsicObjects

ExtrinsicObject s are RegistryObject s that have associated data that is not of a type that is normally handled by the registry and is therefore stored in the repository rather than the registry itself. When creating an ExtrinsicObject , it is necessary to supply the data to be stored and the MIME type of that data. BusinessLifeCycleManager provides a method that allows an ExtriniscObject to be created:

 public ExtrinsicObject createExtrinsicObject(DataHandler data) 
   throws JAXRException; 

The javax.activation.DataHandler passed to this method encapsulates both the data and its MIME type. As discussed in Section 3.6.3.4, there are several ways to create a DataHandler , one of which uses a URL to point to the associated data. In this case, the MIME type of the data is obtained from the data source (e.g., an HTTP server) if it is available, or inferred from the data content or URL if possible. Once an ExtrinsicObject is created, the MIME type can be changed if necessary by calling the setMimeType( ) method, and new data can be installed by calling setRepositoryItem( ) , passing a new DataHandler instance. If the data is not in a form in which it can be directly read by the registry (or application clients), perhaps because it is encrypted, the setOpaque( ) method should be called with the argument true to indicate this.

ExtrinsicObject s are often used in conjuction with SpecificationLink s to include service-related documentation in the repository, to be retrieved by users who locate the parent ServiceBinding . The following code extract shows how you might create an ExtrinsicObject to store a WSDL definition for a web service (in this case, the one offered by Amazon.com) in an ebXML registry and associate with it with a SpecificationLink :

 ExtrinsicObject eObj = blcm.createExtrinsicObject(
   new DataHandler(new URL(
   "http://soap.amazon.com/schemas/AmazonWebServices.wsdl")));
SpecificationLink sLink = blcm.createSpecificationLink(  );
sLink.setName(blcm.createInternationalString("WSDL specification"));    
sLink.setUsageDescription(sLink.getName(  ));
sLink.setSpecificationObject(eObj); 
7.5.9.2 RegistryPackages

A RegistryPackage is a container that allows arbitrary groupings of RegistryObject s to be created. Since a RegistryPackage is a RegistryObject , a RegistryPackage may contain another RegistryPackage . BusinessLifeCycleManager provides two methods that allow the creation of an empty RegistryPackage :

 public RegistryPackage createRegistryPackage(String name) 
   throws JAXRException;
public RegistryPackage createRegistryPackage(InternationalString name)
   throws JAXRException; 

Objects can be added to and removed from the package either individually or in groups by using methods provided by the RegistryPackage interface. The getRegistryObjects( ) method returns a Collection containing all of the objects in the package. One possible use for a RegistryPackage is to group together all of the objects relating to an Organization so that they can be easily deleted:

 BulkResponse res = package.getRegistryObjects(  );
ArrayList keys = new ArrayList(  );
Iterator iter = res.getCollection(  ).iterator(  );
while (iter.hasNext(  )) {
    keys.add(((RegistryObject)iter.next(  )).getKey(  ));
} 
7.5.9.3 Object deprecation

The life cycle for RegistryObject s in a level 1 registry includes a deprecated state. In this state, the object continues to exist, but cannot be made the target of new links from other objects. An attempt to create a new link results in a JAXRException . Objects can be deprecated and undeprecated using the following LifeCycleManager methods:

 public BulkResponse deprecateObjects(Collection keys) throws JAXRException;
public BulkResponse unDeprecateObjects(Collection keys) 
   throws JAXRException; 

Note that the Collection s passed to these methods contain the keys for the objects whose state is to be changed, not the objects themselves.

Even though deprecation applies at the RegistryObject level, there is no way for a registry client to determine in advance whether a given object is deprecated, except in the case that the object is a RegistryEntry . To determine whether a RegistryEntry is deprecated, use the getStatus( ) method and test for the return value RegistryEntry.STATUS_DEPRECATED .

7.5.9.4 Auditing

The registry contains an audit trail that keeps a record of the following events relating to RegistryObject s:

  • Creation

  • Deletion

  • Deprecation

  • Undeprecation

  • Updates, other than classification, being made the target of an Assocation or being added to or removed from a RegistryPackage

  • Changes to the version numbers

The audit trail for a RegistryObject can be obtained by calling its getAuditTrail( ) method, which returns a Collection of objects of type AuditableEvent , each of which records the following information:

  • The event type, as defined in the AuditableEvent interface. Object creation is recorded with type AuditableEvent.EVENT_TYPE_CREATED .

  • The time at which the event occurred, which is available in the form of a java.util.Date object.

  • The RegistryObject to which the event relates .

  • The User object for the user that caused the event.

The code extract shown in Example 7-23 obtains and processes the complete audit trail for each Organization in the registry.

Example 7-23. Getting and processing the registry audit trail for an object
 // Get the Organization entries
ArrayList namePatterns = new ArrayList(  );
namePatterns.add("%");
BulkResponse res = bqm.findOrganizations(null, namePatterns, null, null, null, 
null);

// Process the results
Collection coll = res.getCollection(  );
if (!coll.isEmpty(  )) {
    Iterator iter = coll.iterator(  );
    while (iter.hasNext(  )) {
        Organization org = (Organization)iter.next(  );
        coll = org.getAuditTrail(  );
        System.out.println("Events for " + org.getName(  ).getValue(  ) + 
            ": " + coll.size(  ));
        Iterator aIter = coll.iterator(  );
        while (aIter.hasNext(  )) {
            AuditableEvent evt = (AuditableEvent)aItet.next(  );
            // Do something with "evt"
        }
    }
} 

Since you cannot get a RegistryObject for an object that has been deleted, you cannot see the AuditableEvent that records the deletion by using the getAuditTrail( ) method. An alternate way to retrieve AuditableEvent s is to exploit the fact that they are RegistryObject s and use the BusinessQueryManager getRegistryObjects( ) method, as shown in Example 7-24.

Example 7-24. Getting all AuditableEvents caused by the current user
 BulkResponse   res = bqm.getRegistryObjects(LifeCycleManager.AUDITABLE_EVENT);
if (res != null) {
    coll = res.getCollection(  );
    System.out.println("Events for current user: " + coll.size(  ));
    Iterator aIter = coll.iterator(  );
    while (aIter.hasNext(  )) {
        AuditableEvent evt = (AuditableEvent)aIter.next(  );
        // Do something with "evt"
    }
} 

Since getRegistryObjects( ) returns only objects belonging to the authenticated user, you must have set valid credentials on the Connection object used to establish a connection to the registry. This code returns all AuditableEvent s caused by the actions of the authenticated user, which will include those for object deletion.

7.5.10 Registry Security

Since a registry contains business-related information, it is important that its integrity is protected and that mechanisms are provided to ensure that only the legitimate owner of registry data is allowed to modify it. From the developer's viewpoint, the registry security model provided by the JAXR API is a particularly simple one that requires the provider and the registry itself to be responsible for providing and interpreting security tokens when necessary. The only obligations placed on the application developer are to choose an authentication method and to supply credentials that are acceptable to the target registry before obtaining the RegistryService object. This section briefly looks at the security features that may be offered by a registry and how the developer can make use of them.

7.5.10.1 Authorization

The rules regarding who can access and modify registry data are very simple:

  • Any user is allowed to submit queries to the registry, and therefore read-only access requires no authorization checks to be made. It follows that there is no need for a client who does not intend to modify registry data to supply any kind of identifying information before accessing the registry.

  • Operations that modify registry data can be performed only by properly authenticated users. Furthermore, with the possible exception of "super users" who might be required for administration purposes by some registries, registry objects can only be modified or deleted by the user that originally created them. In order to enforce these restrictions, write access to the registry requires that clients provide authentication information that link them to a predefined registry User . Objects created in the registry are associated with the User object of their creator; attempts to change or delete them are valid only if they are made from a client authenticated as the same User .

The way in which users are registered with a registry is registry-dependent. The public UDDI registries, for example, provide a web-based interface that allows you to sign up and obtain a username and password that enables you to enter registry data. In the case of an ebXML registry, the procedure is likely to be more complex because these registries use stronger, certificate-based authentication. The way in which a JAXR client supplies identification information to a registry is described later in Section 7.5.10.3.

7.5.10.2 Confidentiality

While it might be acceptable for the results of registry query operations to be transmitted in-clear over public networks, it is less desirable for messages that contain usernames and passwords to be easily readable. In order to provide confidentiality, registries may require the use of HTTPS when write access to the registry is required. Since the JAXR API recognizes both a query URL and an update URL for any given registry, it is possible for a registry to provide an unauthenticated query service over HTTP, together with an authenticated and confidential update service that uses HTTPS as the transport mechanism. Since HTTPS encrypts messages in transit and includes a mechanism that can detect modification en route, it not only protects the integrity of the data being installed in the registry, but also ensures that the credentials supplied to authenticate the client are not readable by third parties.

From the point of view of a JAXR client developer, there is very little that you need to do in order to support the use of HTTPS connections to a registry. Most importantly, you need to ensure that the Java Secure Sockets Extension (JSSE) is installed on the client system. If you are using J2SE Version 1.4 or higher, then this requirement is already satisfied because JSSE is included. If not, then you need to download and install the JSSE extension from http://java.sun.com.

HTTPS uses public key certificates. During connection establishment, the registry server sends a certificate identifying itself. In order for the certificate to be recognized, either the certificate itself or that of its issuing Certificate Authority (CA) must already be installed in a trust store on the client system. Since the most commonly used CA certificates are included with the J2SE distribution, in most cases you will probably find that this requirement is automatically satisfied. If not, then you need to obtain and install in a local trust store the appropriate CA certificate. If the trust store you intend to use is not the default provided by J2SE, then you may need to set the javax.net.ssl.trustStore and javax.net.ssl.trustStorePassword system properties to those appropriate for your trust store. Information on installing certificates and setting up these properties can be found in Section 3.8.2.

7.5.10.3 User authentication

Different registries use different authentication mechanisms. A JAXR client is required to select an authentication method and set the javax.xml.registry.security.authenticationMethod property in the Properties object passed to the ConnectionFactory setProperties( ) method accordingly . The literal strings shown in the lefthand column of Table 7-6 are to be used to indicate the most common authentication methods. Note that the current JAXR specification does not define constant values to represent these strings.

Table 7-6. JAXR registry authentication mechanisms

Value

Meaning

UDDI_GET_AUTHTOKEN

An authentication scheme defined in the UDDI specification. The client supplies a username and password and obtains in return a token that is included by the client in each subsequent request that requires user credentials. Naturally, in order for this mechanism to offer any kind of security, it must be safeguarded against tampering. Therefore, HTTPS should be used as the underlying transport protocol.

HTTP_BASIC

The standard HTTP basic authentication scheme. The client supplies a username and password when challenged by the server. This type of authentication is carried out by the HTTP server itself. The registry server can obtain a Principal object for the validated user when handling a request that requires the caller's identity to be known.

CLIENT_CERTIFICATE

Uses a public key certificate as proof of identity. This is a much stronger security mechanism than those listed previously. It is used by ebXML registries.

MS_PASSPORT

Microsoft Passport authentication mechanism.

Providers are not required to implement all of these authentication mechanisms. In fact, it is only required to support the mechanism or mechanisms used by the registry type for which it is designed. At the time of this writing, the UDDI provider in the JAXR reference implementation supports only UDDI_GET_AUTHTOKEN , while the ebXML provider available from http://ebxmlrr.sourceforge.net recognizes only CLIENT_CERTIFICATE . If an unsupported authentication method is requested , the ConnectionFactory createConnection( ) method throws an UnsupportedCapabilityException .

Having selected an authentication method, the JAXR client must also supply appropriate credentials using the Connection setCredentials( ) method:

 public void setCredentials(Collection credentials) throws JAXRException; 

This call should be made before a RegistryService object is obtained. Changing credentials has no effect on any existing RegistryService object.

The argument passed to this method is a Collection that may contain objects of any kind. A provider is required to search the Collection to find objects that supply authentication information in a form that is consistent with the authentication method that has been selected by the javax.xml.registry.security.authenticationMethod property, and ignore all other content. This makes it possible for a JAXR client to create a Collection that contains credentials suitable for several different authentication methods. The JAXR specification specifies the form that the credentials supplied by the JAXR client must take for the UDDI_GET_AUTHTOKEN , HTTP_BASIC , and CLIENT_CERTIFICATE cases as described in the following paragraphs.

For the simple cases in which a username and password are required (i.e., UDDI_GET_AUTHTOKEN and HTTP_BASIC ), the credentials are supplied in the form of a java.net.PasswordAuthentication object. An object of this type can be created very easily, since the constructor requires only the username and password:

 String user = "testuser";
String password = "testuserpassword";
PasswordAuthentication auth = new PasswordAuthentication(user, 
   password.toCharArray(  )); 

In the CLIENT_CERTIFICATE case, an object of type javax.security.auth.x500.X500PrivateCredential must be provided. This object combines an X.509 public certificate with the corresponding private key as well as the alias used to retrieve it from a keystore. The code shown in Example 7-25 illustrates how to create such an object. [15]

[15] This code can be found in the file chapter7\util\src\ora\jwsnut\chapter7\util\Util.java in the example source code for this book.

Example 7-25. Creating an X500PrivateCredential object for certificate-based authentication
 public static X500PrivateCredential getCredentials(String file, String alias,

String keyPassword, String storePassword) throws Throwable {
    KeyStore ks = KeyStore.getInstance("JKS");
    ks.load(new FileInputStream(file), storePassword.toCharArray(  ));
    X509Certificate cert = (X509Certificate)ks.getCertificate(alias);
    PrivateKey key = (PrivateKey)ks.getKey(alias, keyPassword.toCharArray(  ));
    return new X500PrivateCredential(cert, key, alias);
} 

Supplying the certificate and private key is sometimes not sufficient. For example, at the time of this writing, the ebXML provider available from http://ebxmlrr.sourceforge.net also requires access to the keystore from which the certificate was obtained in order to obtain the complete certificate chain. Since the X500PrivateCredential object does not point to the keystore itself, its location is obtained from a property held in a configuration file that must be installed in your home directory. See Section 7.3.2 earlier in this chapter for further information.

The arguments to be supplied to this method are as follows:

file

The full pathname of the keystore file in which the certificate and private key are found. If you create a self-signed certificate for testing purposes using the J2SE keytool command as described in Section 3.8.2, the value of this argument should be the full path to the file named with the -keystore command-line argument to keytool .

alias

The alias under which the certificate is stored in the keystore. This is the same as the value of the -alias argument of the keytool utility.

keyPassword

The password assigned to the private key. When creating a self-signed certificate, the private key is generated and stored in the keystore at the same time. The private key password required here is the same value as that supplied to keytool using its -keypass command-line argument.

storePassword

The password required to access the keystore. This is the same value supplied to keytool using its -storepass argument.