Wednesday, April 24, 2013

Debugging Java webservices

In this post I'll describe two methods which can be used to help developers debug Java webservices (JAX-WS). First I will describe how a debugger can be used from Netbeans. Next I'll describe how SOAP-UI can be used to perform a load test and check the response using an XPATH expression.

Implementation

Setup

To illustrate debugging
I've used a simple webservice to illustrate the above;

package ms.testapp.services;

import java.util.Calendar;
import javax.jws.WebService;
import javax.jws.WebMethod;
import javax.jws.WebParam;

/**
 *
 * @author Maarten
 */
@WebService(serviceName = "HelloWorldService")
public class HelloWorldService {

    /**
     * This is a sample web service operation
     */
    @WebMethod(operationName = "hello")
    public String hello(@WebParam(name = "name") String txt) throws Exception {
        Calendar myCal = Calendar.getInstance();
        if ((myCal.getTimeInMillis() % 10) == 0) {
            throw new Exception("Not right now !");
        }
        if (txt.equals("Maarten")) {
            throw new Exception("Maarten is not allowed !");
        }
        return "Hello " + txt + " !";
    }
}


This webservice will throw exceptions in case the supplied input is Maarten and during every 10th millisecond . This allows me to illustrate both the debugger and SOAP UI usage. After I've deployed the application, the URL of the WSDL of the webservice in my case is;
http://[server]:[port]/HelloWorldApp/HelloWorldService?wsdl

You can download the sample WAR file here; https://dl.dropboxusercontent.com/u/6693935/blog/HelloWorldApp.war

Using the debugger in Netbeans 

Netbeans has a build-in remote debugger. To use this debugger, first debugging needs to be activated on the application server by adding a JVM parameter (and restarting the server). This can be done in the Weblogic console by going to the Servers item in the 'Domain Structure'. Click on the relevant server and go to the 'Server Start' tab. Here you can add under 'Arguments';

-Xrunjdwp:server=y,suspend=n,transport=dt_socket,address=12998

After the server has been restarted, you can set a breakpoint in Netbeans by left clicking the relevant line number;


Next you can attach a debugger in Netbeans.



Now you can test your project in SOAP UI. First use any name. The Netbeans debugger is not triggered. Next use Maarten. The breakpoint is triggered.


SOAP UI will wait for a response (within a timeout). The debugger is triggered and you can see the contents of variables by hovering over them with the mouse. When you press the green play button, the program will continue and you will get the response in SOAP UI.



SOAP UI Xpath expression testing the response.

If you have defined a project in SOAP UI based on the WSDL of the service,you can create a TestCase.

Add an assertion;


Property Content, XPath Match. Define your namespaces and your XPath expression. Click the 'Select from current' button to test your query.


Now create a new load test and put some load on the service;


As you can see, there are a lot of errors. This is as expected since every 10ms, requests will fail with an exception.

Conclusion

Often it is not enough to put log statements in your code to solve problems. Using SOAP UI to do a load test and check the response can help determine the behavior of a service under load. Also using a debugger to track exactly what happens on the server can provide additional information on the cause of errors.

Do not forget that on a production environment, the server should (of course) not be in debug mode and log messages should be sufficient for the people responsible for application maintenance to solve problems.

Thursday, April 11, 2013

SOA Suite PS6 (11.1.1.7); Service loose coupling and tokens

Oracle SOA Suite Patchset 6 (11.1.1.7) has been released (https://blogs.oracle.com/SOA/entry/new_release_of_oracle_soa). Several new features are available such as the implementation of configuration tokens. This could be an interesting new feature, since it could potentially allow configuration plans to become obsolete, so I decided to try it out in order to determine it's usefulness.

Using tokens

Edwin Biemond has described in his post on http://biemond.blogspot.nl/2013/04/token-configurations-in-oracle-soa.html how tokens can be used in configurations. He describes the following limitation; "Important to know this only works on the location attribute of the binding.ws element of the composite.xml file.". So if you use references in other locations to for example concrete WSDL's , you can't replace them by using tokens.

Service Loose Coupling

One of the general principles of Service Orientation is Service Loose Coupling (http://serviceorientation.com/serviceorientation/service_loose_coupling); "This principle advocates the creation of a specific type of relationship within and outside of service boundaries, with a constant emphasis on reducing ("loosening") dependencies between the service contract, its implementation, and its service consumers.". On the following blog, a method is described to reduce the dependency between Oracle SOA composite services by placing abstract WSDL's in the MDS; https://blogs.oracle.com/aia/entry/aia_11g_best_practices_for_dec. You can find a clear description of what an abstract WSDL is and how you can create one at; http://sathyam-soa.blogspot.nl/2012/10/abstract-wsdl-vs-concrete-wsdl.html. In the AIA 11g best practice blog post it is mentioned that in the composite.xml file there is a reference to an abstract and a concrete WSDL. The concrete WSDL is referenced in the binding.ws location attribute, which is exactly the attribute which can be replaced with tokens!

An example of a reference to an abstract WSDL is;
http://localhost:8001/soa-infra/services/default/HelloWorld/HelloWorld.wsdl

An example of a reference to a concrete WSDL is;
http://localhost:8001/soa-infra/services/default/HelloWorld/helloworld_client_ep?WSDL

Implementation

I created a HelloWorld process and a CallHelloWorld process. The CallHelloWorld process is a proxy to the HelloWorld process.

When creating the process, the following composite.xml was generated for the CallHelloWorld process (indicated in bold the references to the HelloWorld process);

<?xml version="1.0" encoding="UTF-8" ?>
<!-- Generated by Oracle SOA Modeler version 11.1.1.7.0 at [4/11/13 12:22 PM]. -->
<composite name="CallHelloWorld"
           revision="1.0"
           label="2013-04-11_12-22-56_153"
           mode="active"
           state="on"
           xmlns="http://xmlns.oracle.com/sca/1.0"
           xmlns:xs="http://www.w3.org/2001/XMLSchema"
           xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy"
           xmlns:orawsp="http://schemas.oracle.com/ws/2006/01/policy"
           xmlns:ui="http://xmlns.oracle.com/soa/designer/">
  <import namespace="http://xmlns.oracle.com/HelloWorld/CallHelloWorld/CallHelloWorld"
          location="CallHelloWorld.wsdl" importType="wsdl"/>
  <import namespace="http://xmlns.oracle.com/HelloWorld/HelloWorld/HelloWorld"
          location="
http://localhost:8001/soa-infra/services/default/HelloWorld/HelloWorld.wsdl"
          importType="wsdl"/>

  <service name="callhelloworld_client_ep"
           ui:wsdlLocation="CallHelloWorld.wsdl">
    <interface.wsdl interface="http://xmlns.oracle.com/HelloWorld/CallHelloWorld/CallHelloWorld#wsdl.interface(CallHelloWorld)"/>
    <binding.ws port="http://xmlns.oracle.com/HelloWorld/CallHelloWorld/CallHelloWorld#wsdl.endpoint(callhelloworld_client_ep/CallHelloWorld_pt)"/>
  </service>
  <property name="productVersion" type="xs:string" many="false">11.1.1.7.0</property>
  <component name="CallHelloWorld" version="2.0">
    <implementation.bpel src="CallHelloWorld.bpel"/>
    <property name="bpel.config.transaction" type="xs:string" many="false">required</property>
    <property name="bpel.config.oneWayDeliveryPolicy" type="xs:string"
              many="false">async.persist</property>
  </component>
  <reference name="HelloWorld"
             ui:wsdlLocation="
http://localhost:8001/soa-infra/services/default/HelloWorld/HelloWorld.wsdl">
    <interface.wsdl interface="http://xmlns.oracle.com/HelloWorld/HelloWorld/HelloWorld#wsdl.interface(HelloWorld)"/>
    <binding.ws port="http://xmlns.oracle.com/HelloWorld/HelloWorld/HelloWorld#wsdl.endpoint(helloworld_client_ep/HelloWorld_pt)"
                location="http://localhost:8001/soa-infra/services/default/HelloWorld/helloworld_client_ep?WSDL"
                supports="" soapVersion="1.1">

      <property name="weblogic.wsee.wsat.transaction.flowOption"
                type="xs:string" many="false">WSDLDriven</property>
    </binding.ws>
  </reference>
  <wire>
    <source.uri>callhelloworld_client_ep</source.uri>
    <target.uri>CallHelloWorld/callhelloworld_client</target.uri>
  </wire>
  <wire>
    <source.uri>CallHelloWorld/HelloWorld</source.uri>
    <target.uri>HelloWorld</target.uri>
  </wire>
</composite>


This composite.xml refers to a concrete WSDL; http://localhost:8001/soa-infra/services/default/HelloWorld/helloworld_client_ep?WSDL. It also refers to the abstract WSDL at http://localhost:8001/soa-infra/services/default/HelloWorld/HelloWorld.wsdl.These WSDL's however cannot be accessed if the HelloWorld process has not been deployed. For compilation and loading of the process in the SOA runtime, only the abstract WSDL's are required. Therefore, putting the abstract WSDL in the MDS resolves some dependency issues which are for example observed during server start (since there is no guarantee to the order in which services are started).

Putting the abstract WSDL in the MDS

MDS usage in Oracle SOA Suite 11g could in my opinion be improved. You can deploy to the MDS by using scripts; http://biemond.blogspot.nl/2009/11/soa-suite-11g-mds-deploy-and-removal.html or you can create a ZIP-file and upload it. See http://www.oracle.com/technetwork/articles/soa/fonnegra-storing-sca-metadata-1715004.html for an overview of what Oracle provides to interact with the MDS. When developing from JDeveloper though I recommend using a local file based MDS from a version controlled workarea. There are several arguments in support of this but since it is not the focus of this post, I'll not go into details here. For simplicity, I've created a ZIPfile with the following structure;

/apps/hello/wsdl/HelloWorld.wsdl
/apps/hello/xsd/HelloWorld.xsd

And uploaded it using the MDS Configuration page (Import)


Updating the composite.xml from CallHelloWorld
 
In order to use the files put in the MDS, the composite.xml from the CallHelloWorld process needs to be updated. Indicated in bold are the parts I've changed.

<?xml version="1.0" encoding="UTF-8" ?>
<!-- Generated by Oracle SOA Modeler version 11.1.1.7.0 at [4/11/13 12:22 PM]. -->
<composite name="CallHelloWorld"
           revision="1.0"
           label="2013-04-11_12-22-56_153"
           mode="active"
           state="on"
           xmlns="http://xmlns.oracle.com/sca/1.0"
           xmlns:xs="http://www.w3.org/2001/XMLSchema"
           xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy"
           xmlns:orawsp="http://schemas.oracle.com/ws/2006/01/policy"
           xmlns:ui="http://xmlns.oracle.com/soa/designer/">
  <import namespace="http://xmlns.oracle.com/HelloWorld/CallHelloWorld/CallHelloWorld"
          location="CallHelloWorld.wsdl" importType="wsdl"/>
  <import namespace="http://xmlns.oracle.com/HelloWorld/HelloWorld/HelloWorld"
          location="oramds:/apps/hello/wsdl/HelloWorld.wsdl"
          importType="wsdl"/>

  <service name="callhelloworld_client_ep"
           ui:wsdlLocation="CallHelloWorld.wsdl">
    <interface.wsdl interface="http://xmlns.oracle.com/HelloWorld/CallHelloWorld/CallHelloWorld#wsdl.interface(CallHelloWorld)"/>
    <binding.ws port="http://xmlns.oracle.com/HelloWorld/CallHelloWorld/CallHelloWorld#wsdl.endpoint(callhelloworld_client_ep/CallHelloWorld_pt)"/>
  </service>
  <property name="productVersion" type="xs:string" many="false">11.1.1.7.0</property>
  <component name="CallHelloWorld" version="2.0">
    <implementation.bpel src="CallHelloWorld.bpel"/>
    <property name="bpel.config.transaction" type="xs:string" many="false">required</property>
    <property name="bpel.config.oneWayDeliveryPolicy" type="xs:string"
              many="false">async.persist</property>
  </component>
  <reference name="HelloWorld"
             ui:wsdlLocation="oramds:/apps/hello/wsdl/HelloWorld.wsdl">

    <interface.wsdl interface="http://xmlns.oracle.com/HelloWorld/HelloWorld/HelloWorld#wsdl.interface(HelloWorld)"/>
    <binding.ws port="http://xmlns.oracle.com/HelloWorld/HelloWorld/HelloWorld#wsdl.endpoint(helloworld_client_ep/HelloWorld_pt)"
                location="http://localhost:8001/soa-infra/services/default/HelloWorld/helloworld_client_ep?WSDL"
                supports="" soapVersion="1.1">
      <property name="weblogic.wsee.wsat.transaction.flowOption"
                type="xs:string" many="false">WSDLDriven</property>
    </binding.ws>
  </reference>
  <wire>
    <source.uri>callhelloworld_client_ep</source.uri>
    <target.uri>CallHelloWorld/callhelloworld_client</target.uri>
  </wire>
  <wire>
    <source.uri>CallHelloWorld/HelloWorld</source.uri>
    <target.uri>HelloWorld</target.uri>
  </wire>
</composite>



Configuring the MDS connection (JDeveloper)

For the compilation of the process, make sure you have a local file based MDS configured; In the Application Resources tab under Applications there is a Connections folder. Here you can create the MDS connection.


Make sure the connection is correctly present in the adf-config.xml of your application. My MDS was in /dev/HelloWorld/mds. My adf-config.xml was as followed (in bold what I needed to add to make the oramds references work correctly);

<?xml version="1.0" encoding="windows-1252" ?>
<adf-config xmlns="http://xmlns.oracle.com/adf/config"
            xmlns:config="http://xmlns.oracle.com/bc4j/configuration"
            xmlns:adf="http://xmlns.oracle.com/adf/config/properties"
            xmlns:sec="http://xmlns.oracle.com/adf/security/config">
  <adf-adfm-config xmlns="http://xmlns.oracle.com/adfm/config">
    <defaults useBindVarsForViewCriteriaLiterals="true"/>
    <startup>
      <amconfig-overrides>
        <config:Database jbo.locking.mode="optimistic"/>
      </amconfig-overrides>
    </startup>
  </adf-adfm-config>
  <adf:adf-properties-child xmlns="http://xmlns.oracle.com/adf/config/properties">
    <adf-property name="adfAppUID" value="HelloWorld.ms.testapp.soa.utils"/>
  </adf:adf-properties-child>
  <sec:adf-security-child xmlns="http://xmlns.oracle.com/adf/security/config">
    <CredentialStoreContext credentialStoreClass="oracle.adf.share.security.providers.jps.CSFCredentialStore"
                            credentialStoreLocation="../../src/META-INF/jps-config.xml"/>
  </sec:adf-security-child>
  <adf-mds-config xmlns="http://xmlns.oracle.com/adf/mds/config">
    <mds-config xmlns="http://xmlns.oracle.com/mds/config">
      <persistence-config>
        <metadata-namespaces>
          <namespace path="/soa/shared" metadata-store-usage="mstore-usage_1"/>
          <namespace path="/apps" metadata-store-usage="mstore-usage_3"/>
        </metadata-namespaces>
        <metadata-store-usages>
          <metadata-store-usage id="mstore-usage_1">
            <metadata-store class-name="oracle.mds.persistence.stores.file.FileMetadataStore">
              <property name="metadata-path"
                        value="${oracle.home}/integration"/>
              <property name="partition-name" value="seed"/>
            </metadata-store>
          </metadata-store-usage>
          <metadata-store-usage id="mstore-usage_3">
                  <metadata-store class-name="oracle.mds.persistence.stores.file.FileMetadataStore">
                     <property value="D:\dev\HelloWorld"
                      name="metadata-path"/>
                     <property value="mds" name="partition-name"/>
                  </metadata-store>
               </metadata-store-usage>

        </metadata-store-usages>
      </persistence-config>
    </mds-config>
  </adf-mds-config>
</adf-config>


Next I undeployed HelloWorld and compiled/deployed CallHelloWorld to confirm they were loosely coupled. I got the following warning; Warning(28,72): Failed to Find Binding "Service1":"{http://xmlns.oracle.com/HelloWorld/HelloWorld/HelloWorld}HelloWorld_pt" in WSDL Manager

This is correct since the binding is still;
    <binding.ws port="http://xmlns.oracle.com/HelloWorld/HelloWorld/HelloWorld#wsdl.endpoint(helloworld_client_ep/HelloWorld_pt)"
                location="http://localhost:8001/soa-infra/services/default/HelloWorld/helloworld_client_ep?WSDL"
                soapVersion="1.1">


I could create the JAR file and deploy it. Even without the HelloWorld process.

Tokens

As you might have noticed, the binding is the only concrete coupling between the endpoint of HelloWorld and the CallHelloWorld. Here tokens come in. We can use a token to specify the endpoint and thus become completely independant of configuration plans for the specification of the endpoint to use!

    <binding.ws port="http://xmlns.oracle.com/HelloWorld/HelloWorld/HelloWorld#wsdl.endpoint(helloworld_client_ep/HelloWorld_pt)"
                location="http://localhost:8001/soa-infra/services/default/HelloWorld/helloworld_client_ep?WSDL"
                soapVersion="1.1">


How to use tokens has been described on http://docs.oracle.com/cd/E28280_01/dev.1111/e10224/sca_bindingcomps.htm#CIHFJFJC. I will not go into detail here. The steps are as followed;

Use from the Composite Editor the Binding URL tokenizer

The binding.ws entry is replaced in my example by;

<binding.ws port="http://xmlns.oracle.com/HelloWorld/HelloWorld/HelloWorld#wsdl.endpoint(helloworld_client_ep/HelloWorld_pt)"
                location="${helloworld_protocol}://${helloworld_host}:${helloworld_port}/soa-infra/services/default/HelloWorld/helloworld_client_ep?WSDL"
                supports="" soapVersion="1.1">


Update the tokens on the server


On the server, there is now a file mdm-url-resolver.xml present. In my example this file could be found in; /u01/app/Middleware/user_projects/domains/base_domain/config/fmwconfig/mdm-url-resolver.xml. This file is an example token file;

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<entry key="helloworld_host">localhost</entry>
<entry key="helloworld_protocol">http</entry>
<entry key="helloworld_port">8001</entry>
</properties>


You can place the token file in the MDS so it is easily available to other developers. I deployed the HelloWorld process and called the CallHelloWorld service with the following result;


After restarting the (Admin)Server, it worked.

Predefined token ${serverURL} 

There is one predefined token; the ${serverURL} token. This token uses the soa-infra server URL as it's value. When I changed my binding.ws location entry to ${serverURL}/soa-infra/services/default/HelloWorld/helloworld_client_ep?WSDL and tested the service, it worked.This means no token configuration on the server is required if you want to call processes on the same server.

You can download the sample code here; https://dl.dropboxusercontent.com/u/6693935/blog/HelloWorldTokens.zip. It does not require server configuration since the serverURL token is used. Keep in mind though that you will need an 11.1.1.7 (PS6) SOA Suite installation for this to work.

Conclusion

Tight coupling between services by using references to WSDL's and/or XSD's which are part of another process, should be avoided. This can be done by putting abstract WSDL's in the MDS.

When the above has been implemented, the loose coupling between processes can be extended by using tokens in the composite.xml file to set the correct binding.ws location attribute. When changing tokens/token values, the server needs to be restarted though. Since only one property can be replaced, it's use is limited to the concrete WSDL used to obtain the binding in the composite.xml file. When calling processes on the same server, the serverURL token can be used which is always available and does not require specific definition of tokens on the server.

With the above in place; loose coupling to WSDL's/services and tokens, one can argue that configuration plans become obsolete. When however variables/properties in the composite.xml are used, you still might need a mechanism to make these values differ per environment and configuration plans provide a (deploy time) solution to that. By using the MBean browser, these properties can be updated on runtime (see http://beatechnologies.wordpress.com/2011/11/04/persisting-component-preferences-in-oracle-soa-suite-11g/). This would not be needed if global variables could be used. I'm however not aware of an out of the box (GUI supported, little coding) implementation for deploytime and runtime global variables in Oracle SOA Suite 11g. Of course a database can be used, Server MBeans or plain old property files, but you have to build your own implementation.