Showing posts with label WSDL-First. Show all posts
Showing posts with label WSDL-First. Show all posts

Sunday, June 14, 2009

Truly Dynamic Web Services on WebLogic using JAX-WS

In my last blog (a long time ago I know) I talked about the potential for using JAX-WS on WebLogic 10.3 to fulfil the 8 key values that I believe are important for Web Service based Messaging. I've been using JAX-WS in WebLogic a lot over the last half a year and I've been very impressed with its power. As promised, I'm now (finally) going to describe the results of my experiments, with JAX-WS Providers/Dispatchers specifically, to see if they can achieve 8 out of 8 on my scorecard. Does JAX-WS give me the power and freedom I desire when doing web services?

For the experiment, I have used a Customer based web service example, like I've used before. Using an Interface-First approach, I created a WSDL and associated XSD Schema for a fictitious "Change Customer Information" HTTP SOAP web service. Then I created the Java Web Service (JWS) implementing a JAX-WS Provider interface, rather than using a strongly-typed JAX-WS Service Endpoint Interface (SEI). To test out the integration between the WebLogic's JAX-WS runtime and WebLogic's Security sub-system, I included a WS-Policy in my WSDL to force service consumer applications to provide a WS-Security UserToken (username/password) in the SOAP header for authentication.

My code for the Provider is shown below:
@WebServiceProvider(
  serviceName="CustomerInfoChangeService",
  targetNamespace="testns:custinfochngsvc",
  portName="CustomerInfoChangeServiceSOAP",
  wsdlLocation="/WEB-INF/wsdls/CustomerInfoChangeService.wsdl"
)
@ServiceMode(value=Service.Mode.PAYLOAD)
public class CustomerInfoChangeService implements Provider<Source> {
  private final static String XSD_NMSPC = "testns:custinfo";
  @Resource
  private WebServiceContext context;
 
  public Source invoke(Source request) {  
    try {
      // Check some security stuff
      System.out.println("Service invoked with principal: " + 
                                       context.getUserPrincipal().getName());
      System.out.println("Is in Administrators group? " + 
                                     context.isUserInRole("Administrators"));
   
      // Process request
      Transformer transformer = TransformerFactory.newInstance().newTransformer();
      StreamResult sysOutStream = new StreamResult(System.out);
      System.out.println("Changing following customer info:");
      transformer.transform(request, sysOutStream);
      System.out.println();
   
      // INVOKE REAL XML PROCESSING AND CUSTOMER DB UPDATE LOGIC HERE
   
      // Construct response
      String responseText = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
        "<CustomerInfoChangeAcknowledgement xmlns=\"" + XSD_NMSPC + "\">" +
        "  <Ack>SUCCESS</Ack>" +
        "  <Comment>Successfully processed change for customer</Comment>" +
        "</CustomerInfoChangeAcknowledgement>";
      return new StreamSource(new StringReader(responseText));
    } catch (Exception e) {
      throw new WebServiceException("Error in Provider JAX-WS service", e);
    }
  }  
}

I bundled this Provider JWS into a plain WAR archive and deployed to WebLogic. I then created the service consumer code as a standalone Java application. The code for this client, which uses the JAX-WS Dispatch API to locate and invoke the remote Web Service, is shown here:
public class CustomerInfoChangeClient {
  private final static String WSDL_URL_SUFFIX = "?WSDL";
  private final static String WSDL_NMSP = "testns:custinfochngsvc";
  private final static String WSDL_SRVC_PORT = "CustomerInfoChangeServiceSOAP";
  private final static String WSDL_SRVC_NAME = "CustomerInfoChangeService";
  private final static String XSD_NMSP = "testns:custinfo";
  private final static String USERNAME = "weblogic";
  private final static String PASSWORD = "weblogic";
 
  public static void main(String[] args) {  
    if (args.length <= 0) {
      throw new IllegalArgumentException("Must provide endpoint URL arg");
    }

    try {
      new CustomerInfoChangeClient(args[0]);
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  public CustomerInfoChangeClient(String endpointURL) throws IOException, 
        SAXException, TransformerException, ParserConfigurationException {
    // Construct request
    String requestText = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
                         "<CustomerInfo xmlns=\"" + XSD_NMSP + "\">" +
                         "  <Id>1</Id>" +
                         "  <Address1>6</Address1>" +
                         "  <PostCode>AB1 2YZ</PostCode>" +
                         "</CustomerInfo>";
    Source request = new StreamSource(new StringReader(requestText));

    // Invoke service operation, adding appropriate credentials
    Service service = Service.create(new URL(endpointURL + WSDL_URL_SUFFIX),
                                      new QName(WSDL_NMSP, WSDL_SRVC_NAME));
    Dispatch<Source> dispatcher = service.createDispatch(new QName(
            WSDL_NMSP, WSDL_SRVC_PORT), Source.class, Service.Mode.PAYLOAD);
    Map<Object, String> rc = ((BindingProvider) dispatcher).
                                                     getRequestContext();
    List<CredentialProvider> credProviders = new 
                                          ArrayList<CredentialProvider>();
    credProviders.add(new ClientUNTCredentialProvider(USERNAME.getBytes(),
                                                    PASSWORD.getBytes()));
    rc.put(WSSecurityContext.CREDENTIAL_PROVIDER_LIST, credProviders);
    //rc.put(BindingProvider.USERNAME_PROPERTY, USERNAME);
    //rc.put(BindingProvider.PASSWORD_PROPERTY, PASSWORD);
    Source response = dispatcher.invoke(request);

    // Process response  
    Transformer transformer = TransformerFactory.newInstance().
                                                   newTransformer();
    StreamResult sysOutStream = new StreamResult(System.out);
    System.out.println("Change customer service result:");
    transformer.transform(response, sysOutStream);
    System.out.println();
  }
}

Notice that I have not used any generated XML-to-Java classes for the parameters and return values from within the client or in the service itself. Once compiled and run, the client application works completely as expected, and successfully authenticates with the remote web service using the specified username/password WS-Security credentials.

Here are my observations from the experiment:
  • In the simple example I just used StreamSource for processing the XML request and responses at both ends. However, I could have used other APIs such as DOMSource, JAXBSource, SAXSource, StAXSource from the Java 1.6 standard 'javax.xml.transform' packages instead (and even mixed and matched these for the request and the response).
  • I have used javax.xml.transform.Source for accessing the SOAP request and response messages, instead of javax.xml.soap.SOAPMessage. With Source I can only access the contents of the SOAP Body (refered to as the 'PAYLOAD'). With SOAPMessage, I can choose to access the XML elements of the SOAP Envelope (including SOAP headers) as well (refered to as the 'MESSAGE'). However using SOAPMessage restricts me to only being able to process XML using the W3C DOM API, therefore using Source appeals to me more. When using Source, if I need to access SOAP Headers, I'd probably just use JAX-WS Protocol Handlers anyway, to process these headers before or after my main Provider class is called.
  • I don't need to use WebLogic's WSDLC, JWSC or ClientGen Ant tasks because no "build-time" generation of Java classes (using JAXB) or stubs/skeletons is required. For my service, I could have still optionally used JWSC to generate a deployable WAR file with the appropriate web.xml deployment descriptor auto-generated, but I chose to create these artefacts myself, in a way that I can control, using simple Ant build.xml tasks.
  • To ensure that my JAX-WS service is detected properly by the WebLogic runtime, the key thing I needed to do was create a Servlet definition and mapping entry in my web.xml deployment descriptor for my JWS Provider class (even though a provider class does not actually implement javax.servlet.Servlet). During deployment, the WebLogic Web Service Container automatically detects that the web.xml declared servlet is infact a JWS class and automatically maps the URL specified to an internal WebLogic JAX-WS handling Servlet called "weblogic.wsee.jaxws.JAXWSWebAppServlet". As a result, my transport-protocol agnostic SOAP JWS class is now exposed over HTTP. My web.xml file includes the following:
   <servlet>
      <servlet-name>CustomerInfoChangeService</servlet-name>
      <servlet-class>test.service.CustomerInfoChangeService</servlet-class>
   </servlet>
   <servlet-mapping>
      <servlet-name>CustomerInfoChangeService</servlet-name>
      <url-pattern>/CustomerInfoChangeService</url-pattern>
   </servlet-mapping> 
[UPDATE 19-Jun-09: Actually after some further testing, I discovered that if I omit the servlet definition/mapping from my web.xml, the JWS Provider web-app still deploys and runs correctly. WebLogic automatically maps the Provider it discovers on deployment, to a URL which is the Provider class's name (without package name prefix and without .class suffix). This is in-line with the JavaEE 1.5 - Servlet 2.5 spec, where deployment descriptors are now optional. However, it may still be desirable to create a servlet definition/mapping in web.xml to be able to better control the URL of the service).]
  • I would have ideally liked to have included a @RolesAllowed() annotation in my Provider class to help declaratively restrict access to service operations based on role. However, the JAX-WS specification doesn't currently cater for this annotation and WebLogic's JAX-WS implementation does not process this annotation, if its included.
  • In addition to testing authentication using message-level security (eg. a WS-Security user token) I also tested authentication via transport-level security using a client provided HTTP Basic Authentication token (see commented out lines of code in the client app above). In both cases, authentication worked properly, and in my Provider class, when I call context.getUserPrincipal() the proper authenticated principal user object is returned.
  • However, when using context.isUserInRole("Administrators")) in my Provider class, "true" is only returned when using transport-level security, but not when using message-level security. This means that for message-level security, performing programmatic access control is currently limited to checking the user principal object only - the user's roles can't be queried. Here was the security role I defined in web.xml:
    <security-role>
       <role-name>Administrators</role-name>
    </security-role>
...and here is how I mapped the role to my WebLogic EmbeddedLdap 'Administrators' group in weblogic.xml:
    <security-role-assignment>
       <role-name>Administrators</role-name>
       <principal-name>Administrators</principal-name>
    </security-role-assignment>

So to wrap up, how does JAX-WS in WebLogic 10.3 stack up against my 8 criteria?

Well pretty well actually. I would say 7.75 out of 8. A full score on the first 7 criteria in the list and on the 8th (integration with the host container's Security Framework), just a 1/4 point dropped. This is due to a lack of full flexibility in defining declarative or programmatic access control for a service, when using message-level authentication.


Song for today: Festival by Sigur Rós

Friday, October 10, 2008

Web Service Messaging Nirvana

Web Services and specifically SOAP has got a bad rap in recent years, believed to be yet another over-hyped remote procedure call (RPC) protocol, and not cool enough in light of more current hip approaches like REST. Some of this flak is justified and the SOAP community does itself no favours when it releases a newer version of a spec (SOAP 1.2) which goes out of its way to state that SOAP is no longer an acronym. What next? TAFKAS? (The Artist Formerly Know As SOAP)

Anyway, I digress. I blogged in the past about two main areas where SOAP can be used. As you might guess, the main area that interests me is Messaging.

Over the last few years, I've been on a quest to explore how a JavaEE architect and developer can better implement messaging based web services and service consumers in a more loosely coupled and less error prone way. My motivation has been driven by a reaction to the traditional RPC based Web Services programming model that many have used and still use today and all the problems that such a model brings.

Over time I have constructed a list of 8 key values or principles that I believe are necessary for achieving "Web Services Messaging Nirvana". In most cases, these principals are based more on architectural and development approach, than the specific technology or SOAP toolkit used. However, as always, the two can't be completely divorced from each other..

Here's my 8 key values:


  1. Promote Interface/Contract-First Development
  • “Implementation-First” leads to tight coupling between the code in the service provider and the code in the service consumer
  • "Implementation First" leads to a strong dependency on the initial SOAP toolkit used for implementation, which is then difficult to migrate off and leads to the difficulties of vendor lock-in
  1. Enable a Message-Passing Programming Model
  • RPC programming leads to lots of find grained 'chatty' services where brittle pairings exist between provider and consumer which are resistant to change
  • RPC clients tend to assume endpoints are implementations when in reality, they may just be messaging intermediaries (eg. a service bus)
  1. De-couple “Service Interface Definition” from “Message Document Definition”
  • In-lining XML type definitions inside a WSDLs prevents re-use of the message structure definitions across multiple web services and other components in the system as a whole (import external Schemas instead)
  • The message document structures may already be pre-defined in the organisation (eg. industry-wide standard schemas), so the ability to share these definitions across many web services is required‏
  1. Promote Postel’s Law: “Be conservative in what you do; be liberal in what you accept from others”
  • In other words, allow developers to create services with strongly defined interfaces, yet forgiving implementations
  • Service intermediaries and end-points may only care about using part of the message content - they should not concern themselves about trying to validate other parts of a message
  • Enable minor interface version additions without necessarily requiring an upgrade or change to the service provider or service consumer
  1. Avoid the need for manually generated static stubs and skeletons
  • These are invariably an unnecessary time-consuming addition to the development and build process
  • Interface modification always requires re-generation of stubs, skeletons and Java types, forcing service providers and consumers to need to always be corrected, re-compiled and re-deployed.
  • A requirement for Skeletons and Stubs is unlikely when generated Java types are not being used in the implementation
  1. Separate data from the code that operates on it
  • Object-oriented programming is great for service implementations but not for de-coupling disparate elements of the higher level distributed systems that are based on the principle of passing messages or events around
  • Enable message data to be passed between systems without one system caring about or pre-empting how the other system may use and operate on the message’s content
  1. Provide the developer with a choice of XML Tools to use
  • SOAP Toolkits which force messages to be marshalled into a strongly typed Java graph of objects, results in one and only one way that the programmer can operate on the data
  • If only part of a message is required or performance is key, a developer should have the option to use a SAX parser, a Streaming parser or just an XPath based Java API to query and operate on the data
  • If XQuery or XSLT offers some developers a better abstraction and quicker development process then don't prevent a developer from being able plug in this option
  • Retain the choice of being able to generate strongly types Java objects for situations where the developer needs or prefers it, but enable the developer to choose the technology (eg. JAXB, XmlBeans)‏
  1. Integrate with the host container's Security Framework for Web Services Security
  • For an application server based production environment, the server's Security Framework is already likely to be plugged into the organisation's larger security infrastructure (inc. LDAP, Kerberos, PKI and other elements) to provide full support for authentication, access control, single sign-on, identity propagation, encryption and signing. This infrastructure needs to be leveraged by the SOAP Toolkit, not ignored
  • The SOAP Toolkit needs to delegate to, or at least share its security context with, the host container, especially for its support of WS-Security and SAML

In the past, with J2EE 1.2, 1.3 and 1.4 compatible application servers, these values have been very hard to achieve with the application server's built-in SOAP support (ie. JAX-RPC - oriented around the concept of RPC, skeletons, stubs, and generated Java types) or with a Third-Party SOAP Toolkit (eg Apache Axis - little integration with the container's underlying Security Framework).

As a result, on these platforms, I've struggled to be able to get anywhere near achieving my goal of hitting the 8 key values of my Web Service quest.


JavaEE 1.5 introduced the JAX-WS API which is the newer Web Services toolkit for JavaEE developers. The focus of this standard, in my opinion, is still largely based on RPC and generated Java types. However, it does offer a glimmer of hope in form of the option of using a 'dnyamic' server side API (Provider interface) and client-side API (Dispatcher interface).

To my eyes the Provider/Dispatcher facility could finally give me a chance of achieving the 8 key values in a real Web Services based solution.


Version 10.0 of WebLogic was the first version to introduce JAX-WS, but at the time lacked integration with WebLogic's Security Framework and WS-Security and SAML support, thus lessening its relevance to many solutions where securing web services was a key requirement. Many users continued to use WebLogic's JAX-RPC stack due to the need to leverage Weblogic security.

Things have now changed and the JAX-WS implementation in version 10.3 of WebLogic should address this. I hope to blog soon about my experiments with JAX-WS Providers/Dispatchers in WebLogic 10.3 and my quest to realise my 8 key values of Web Services Messaging Nirvana.



Song for today: Burn Baby by Mother Tongue

Friday, September 5, 2008

WSDL-First and Schemas: Use Global Types or Global Elements?

Often I'm faced with needing to expose a Web Service where the request and response bodies are messages, based on a well established shape that is not just the concern of the specific Web Service. The message received by the Web Service is likely to flow through other parts of the internal system 'behind' the service endpoint. The Schemas for these messages are not specific to just the Web Service WSDL but are also used by other internal components which have to deal with XML. Often these Schemas are pre-defined across an organisation or by third-parties (eg. the ARTS IXRetail schema for the Retail industry).

With these sorts of scenarios, a “WSDL first” approach is usually required for creating or generating Web Service end-points, where the WSDL identifies the shape of the service request and response bodies, but does not directly define this shape. Instead, the WSDL imports the externally held Schemas and references entities from the Schema to declare the format of the top-level nodes for a SOAP message body.

With this in mind, when designing Schemas that will be used by WSDLs and other XML components, I've been trying to work out whether top-level schema entities should be defined as Global Types or as Global Elements.

So I tried a little experiment, using a simple home-made schema to represent a Customer, including Name, Date of Birth, Addresses and Telephone Numbers. I created a SOAP Web Service which enables a Customer's personal information to be retrieved by providing the customer's name as the search criteria in the SOAP request.

First I tried using a Global Type in my Customer schema (Customer.xsd) to represent the shape of the top level request XML (CustomerSearchCrtieria) and response XML (Customer), as follows:
<schema ....>
<complexType name="CustomerSearchCriteria">
    <sequence>
        <element name="FirstName" type="tns:FirstName"/>
        <element name="LastName" type="tns:LastName"/>
    </sequence>
</complexType>

<complexType name="Customer">
    <sequence>
        <element name="FirstName" type="tns:FirstName"/>
        <element name="LastName" type="tns:LastName"/>
        <element name="DateOfBirth" type="tns:DateOfBirth"/>
        <element name="Address" type="tns:Address" minOccurs="0" maxOccurs="unbounded"/>
        <element name="Telephone" type="tns:TelephoneNumber" minOccurs="0" maxOccurs="unbounded"/>
    </sequence>
</complexType>
....
In the WSDL, I defined the request and response message parts to use the two Global Types from the imported schemas, as follows:
<wsdl:definitions name="CustomerInfoService" ... xmlns:cust="http://www.example.org/Customer">
<wsdl:types>
    <xsd:schema>
        <xsd:import namespace="http://www.example.org/Customer" schemaLocation="./Customer.xsd"/>
    </xsd:schema>
</wsdl:types>
 
<wsdl:message name="GetCustomerInfoRequest">
    <wsdl:part name="MyCustomerSearchCriteria" type="cust:CustomerSearchCriteria"/>
</wsdl:message>

<wsdl:message name="GetCustomerInfoResponse">
    <wsdl:part name="MyCustomer" type="cust:Customer"/>
</wsdl:message>
....
Then when I fired a SOAP request into the service using my test harness, the SOAP request contained the following (notice no namespace for top level element in the body):
<soapenv:Envelope ....>
<soap:Header ..../>
<soapenv:Body>
    <MyCustomerSearchCriteria xmlns:cus="http://www.example.org/Customer">
        <cus:FirstName>Paul</cus:FirstName>
        <cus:LastName>Done</cus:LastName>
    </MyCustomerSearchCriteria>
</soapenv:Body>
</soapenv:Envelope>
The SOAP response contained the following (notice no namespace for top level element in the body):
<soapenv:Envelope ....>
<soap:Header  ..../>
<soapenv:Body>
    <MyCustomer xmlns:cus="http://www.example.org/Customer">
        <cus:FirstName>Paul</cus:FirstName>
        <cus:LastName>Done</cus:LastName>
        <cus:DateOfBirth>1973-12-12</cus:DateOfBirth>
        <cus:Address>
            <cus:HouseNumber>1</cus:HouseNumber>
            <cus:Street>High Street</cus:Street>
            <cus:Town>Big Town</cus:Town>
            <cus:Postcode>BT1 1AZ</cus:Postcode>
        </cus:Address>
        <cus:Telephone>
            <cus:AreaCode>0111</cus:AreaCode>
            <cus:LocalNumber>1234561</cus:LocalNumber>
        </cus:Telephone>
    </MyCustomer>
</soapenv:Body>
</soapenv:Envelope>
As you can see, the resulting top level XML element inside the SOAP body for both the request and the response is NOT part of the same namespace at the rest of the message XML. This is because the top-level element has actually been defined as an element by the WSDL, rather than being defined externally in the schema itself. To me this is less than ideal because I believe most people would want the whole message to have the same namespace.

In addition I believe most people would want the top level nodes to have an element name mandated by the Schema, not by the WSDL (eg. the top level element should be forced to be 'Customer' and not 'MyCustomer').

So I then tried using a Global Element instead, in my Customer schema (Customer.xsd) to represent the shape of the top level request XML (CustomerSearchCrtieria) and response XML (Customer), as follows:
<schema ...>
<element name="CustomerSearchCriteria" type="tns:CustomerSearchCriteria"/>

<element name="Customer" type="tns:Customer"/>
....
In the WSDL, I defined the request and response message parts to use the two Global Elements from the schemas, as follows:
<wsdl:definitions name="CustomerInfoService" .... xmlns:cust="http://www.example.org/Customer">
<wsdl:types>
    <xsd:schema>
        <xsd:import namespace="http://www.example.org/Customer" schemaLocation="./Customer.xsd"/>
    </xsd:schema>
</wsdl:types>

<wsdl:message name="GetCustomerInfoRequest">
    <wsdl:part element="cust:CustomerSearchCriteria" name="parameters"/>
</wsdl:message>

<wsdl:message name="GetCustomerInfoResponse">
    <wsdl:part element="cust:Customer" name="parameters"/>
</wsdl:message>
....
Then when I fired a SOAP request into the service using my test harness, the SOAP request contained the following:
<soapenv:Envelope ....>
<soap:Header ..../>
<soapenv:Body>
    <cus:CustomerSearchCriteria xmlns:cus="http://www.example.org/Customer">
        <cus:FirstName>Paul</cus:FirstName>
        <cus:LastName>Done</cus:LastName>
    </cus:CustomerSearchCriteria>
</soapenv:Body>
</soapenv:Envelope>
The SOAP response contained the following:
<soapenv:Envelope ....>
<soap:Header ..../>
<soapenv:Body>
    <cus:Customer xmlns:cus="http://www.example.org/Customer">
        <cus:FirstName>Paul</cus:FirstName>
        <cus:LastName>Done</cus:LastName>
        <cus:DateOfBirth>1973-12-12</cus:DateOfBirth>
        <cus:Address>
            <cus:HouseNumber>1</cus:HouseNumber>
            <cus:Street>High Street</cus:Street&
            <cus:Town>Big Town</cus:Town>
            <cus:Postcode>BT1 1AZ</cus:Postcode>
        </cus:Address>
        <cus:Telephone>
            <cus:AreaCode>0111</cus:AreaCode>
            <cus:LocalNumber>1234561</cus:LocalNumber>
        </cus:Telephone>
    </cus:Customer>
</soapenv:Body>
</soapenv:Envelope>
As you can now see, all of the elements in the XML message, contained within the request and response SOAP bodies, now have the same namespace and the top level element name is defined by the Schema rather than by the WSDL. This is much more desirable in my view.

In summary, when dealing with Web Services in a “WSDL-first” scenario and using externally defined schemas, I would recommend defining Global Elements in the Schema and then referencing these elements from a WSDL, rather than using Global Types. This enables the complete message to share the same namespace and enables all elements (and their names), in the message, to be completely defined and enforced by the Schema.


Note: I am not necessarily making a recommendation to use Global Elements over Global Types in all circumstances, rather, just in situations where a Schema could possibly be used directly by a SOAP Web Service description file (WSDL).in addition to other parts of a system.


Soundtrack for today: Gouge Away by Pixies