Collections from an object-oriented paradigm combined with Web Services enable designers to get into trouble by
Do not expose
open -ended query mechanisms tied to the underlying object implementation: Query mechanisms tie collections to the underlying representation of the data, objects use Object Query Language (OQL), and relational databases use SQL. This techniqueviolates the ability to encapsulate the service implementation architecture and design.Return arrays of data from operations that return more than one object rather than Java-specific collection types: There is no guarantee that a Web Service client has a mechanism to represent complex Java- specific types. For example, using a Vector on a service implementation may not translate easily to a type available for a COBOL programmer. Arrays are a more common programming construct and easily translate between most platform architectures.
Collections will exist on the client or on the server: A collection based on the pattern presented here does not span the client and the server. Always remember that the collection used by a client is a copy of the data on the server. Changes to the data on the client move back into the collection on the server through an explicit call to the collection interface.
Certain Web Service scenarios can avoid some of these rules by limiting the types of clients that can access your Web Service. For example, by ensuring that your clients are either .NET or Java clients, you can open up your interface to OQL and simple collection types that translate between platforms. Unfortunately, by
The theme of a collection of customers will continue for the sample implementation presented in this chapter. In the previous chapter, you went through a detailed scenario of business object deployment and the structure of the Simple Access Object Protocol (SOAP) messages that flow between the client and the server.
For this chapter, you will assume that deployment of the service implementation as a Web Service is less interesting than the structure of the service and how to access it from Java. This is a subtle change in tempo for the book, but it is necessary when you realize that Web Services are merely architecture adapters that tools generate for you. The real work becomes building
CustomerCollectionImpl is your service implementation class; it contains instances of CustomerImpl classes. The CustomerImpl class contains an AddressImpl , an instance of CustomerInformationImpl and some simple types, mostly strings. AddressImpl contains strings, and CustomerInformationImpl contains several more nested complex types. Figure 7-3 shows the entire class diagram.
Figure 7-3:
Customer collection implementation
From this hierarchy, an instance of
CustomerCollectionImpl
serves as the point object for the Web Service. A parallel hierarchy of serializable JavaBeans exists on the client after deployment of the Web Service and the creation of the client-side classes using WSDL2Java. These classes allow the client to create local data and call
For your implementation, there is an interesting side effect of your JDO usage. The association between the collection and the customer instances is an indirect relationship that works through the JDO persistence manager. The collection implementation queries extents that represent collections of customers. Figure 7-3 shows the logical structure rather than the actual physical implementation of the system.
The CustomerCollectionImpl class contains several interesting characteristics. Listing 7-2 is not the complete source code for the class, but it is enough for you to get a feel for the implementation. Of particular interest is the series of query methods: getCustomer, getCustomers, getCustomersLastFirst , and getCustomersLast .
The difference between the getCustomer method and the getCustomers method lies in the expected set of objects returned from the method. The getCustomer method must return zero or one instance because of the unique key on the parameter list. The other methods do not guarantee a single instance and, therefore, return arrays.
A second point to note with the retrieval methods is the lack of method overloading. Overloading methods works well in object-oriented languages, but you cannot guarantee that only object-oriented languages will access your application. Rather than place a
Listing 7-2: Customer Collection Implementation
|
|
public class CustomerCollectionImpl {
public void removeCustomer(CustomerKey key) {
// see implementation in sample code
}
public CustomerKey addCustomer(CustomerImpl customer) {
CustomerKey key = null;
if (customer.getCustomerId() == null) {
customer.setCustomerId(
KeyGenerator.generateKey(KeyGenerator.CUSTOMER));
}
key = new CustomerKey();
key.customerId = customer.getCustomerId();
PersistenceManager pm = getPersistenceManager();
Transaction t = pm.currentTransaction();
t.begin();
pm.makePersistent(customer);
t.commit();
return key;
}
public CustomerImpl getCustomer(CustomerKey key) {
// see implementation in sample code
}
public CustomerImpl[] getCustomersLastFirst(
String lastName, String firstName) {
// see implementation in sample code
}
public CustomerImpl[] getCustomersLast(String lastName) {
PersistenceManager pm = getPersistenceManager();
Transaction t = pm.currentTransaction();
t.begin();
Extent ext = pm.getExtent(CustomerImpl.class, false);
String filter = "lastName==last";
String parameter = "String last";
Query q = pm.newQuery(ext, filter);
q.declareParameters(parameter);
Collection c = (Collection)q.execute(lastName);
pm.retrieveAll(c);
CustomerImpl returnCustomers[] = new CustomerImpl[c.size()];
Iterator i = c.iterator();
int j=0;
while(i.hasNext()){
returnCustomers[j] = (CustomerImpl)i.next();
j++;
}
q.closeAll();
t.commit();
return returnCustomers;
}
public CustomerImpl[] getCustomers() {
// see implementation in sample code
}
}
|
|
The
addCustomer
method from Listing 7-2 is straightforward. The client cannot
The
getCustomersLast
is a more interesting method. Consider that JDO returns a
Collection
from the query of the customer extent. This method could return this collection directly to the client. On the other hand, by returning the collection you leave some ambiguity about how your architecture adapters will handle the collection interface along with the data contained in the collection. Rather than leave ambiguity in the interface, the method returns a type-safe array of classes that
Several client-side scenarios are available to you; you will see a simple one using the
getCustomers
method. This code actually retrieves the customer collection from the Web Service and
In this method, it is crucial to remember that the customer query returns copies of the collection to you rather than giving you an iterator to the collection that resides on the server. Generic queries that return large
Listing 7-3: Querying the Customer Collection
|
|
CustomerCollectionImplService service =
new CustomerCollectionImplServiceLocator();
CustomerCollectionImpl port =
service.getCustomerCollection();
Customer[] ci3 = port.getCustomers();
if(ci3!=null) {
for(int i=0 ; i<ci3.length ; i++){
CustomerKey ck = new CustomerKey();
ck.setCustomerId(ci3[i].getCustomerId());
port.removeCustomer(ck);
}
}
|
|
A second point to note in Listing 7-3 is the use of the array structure. Arrays are not very dynamic; on the other hand, it did not use collection instances on methods. The client code points out another interesting reason to avoid collections as parameters and return types. Consider that you received a collection of objects in a dynamic collection that you could easily expand through an
add
method call. Changes to the collection
only
occur on the client side. Modification of the server- side collection
must
occur through the
CustomerCollectionImpl
interface. Using the static array structure on the return of the query methods may help you avoid programming errors when