Showing posts with label webservices. Show all posts
Showing posts with label webservices. Show all posts

Tuesday, May 10, 2011

JDeveloper, the Endorsed Directory, bootclasspath, and JAX-WS 2.2.x

I have been working with JAX-WS 2.2.x recently and one of problem you might fact whilst playing with a client is that it uses more recent version of the JAX-WS API than that supplied with JDK 1.6. This shouldn't be a problem normally but there are some issues that might get in your way when using JDeveloper.

The first thing to note is that I am going to ignore the standard advice to user the java.endorsed.dirs system property as it can only specify a directory and not individual jar files. This is not always a problem; but if your jars are not packaged in a convenient way it means you have to move then to a new directory. Instead I am going for the slightly less standard -Xbootclasspath/p: property which prepends the jars to the boot classpath with much the same effect and I can specify the paths to individual jars. I will note with the screen grabs where you might go differently if you want to ignore this advice.

First things first you need to configure the compiler otherwise when you try to compile a web services client, in this case, you find error messages relating to the constructors on the Service class. You can find these settings under project properties. You could use the -endorsedpath here if you wish. For my purposes I had to place both the jax-xml-bind.jar and jax-xml-ws.jar on the bootclasspath. Of course use you platform specific path separator between the two paths. (If you are doing JAX-WS you are going to need the matching version of JAX-B)

Unfortunately you might find that this point that the project will fail to compile due to bug 12538161, there is a workaround which is to ask the compilation to happen as a separate process. This is going to be slightly slower; but in most cases you are not going to notice the difference.

Depending on your deployment context you might also like to update the run options for the your project, again I have used the bootclasspath but you could use the -Djava.endorsed.dirs depending on how you dependencies are laid out.

You can compile and run against the new API which will solve 99% of problem in the area. Fortunately only a few Java API's are packaged in this way. Hopefully in JDK 8 we can get the modularization sorted out so this sort of thing will be less of a problem.

Monday, April 11, 2011

Generating an inline schema in JDeveloper

A question that comes up now and then is how to generate a WSDL with the Schema in-line rather than as a separate file. This is required for certain mobile platforms and in particular Siebel Tools. This feature wasn't added to the JAX-WS RI until version 2.2.1 which is ahead of the version use in JDeveloper. It is possible to get some integration using the external tools command to make use of a later version of this tool in JDeveloper.

To get this to work you first need to download JAX-WS RI 2.2.1 or later. The configuration is similar to what we have used previously on this blog with a few specific modifications.

The program executable is simply the location of the wsgen.sh or wsget.bat file that comes with the RI download. The second line is more complicated:

-Xendorsed -d ${project.outputdirectory} -s ${project.outputdirectory} -cp ${project.outputdirectory};${project.classpath} ${target.class} -r ${file.dir} -wsdl  -inlineSchemas

Reading left to right we are: making sure we used the endorsed property to use the updated API; specifying the project classes directory for any output; specifying the project classpath; the class name from the current selection; where the wsdl file should be written out, the same one as the java file; and finally that we want to generate a wsdl and that it should be inline.

In the rest of the wizard you are likely to want to configure the tool to appear in the navigator and only appear for java source files. All pretty vanilla stuff. Once this is configured you should have a menu item you can invoke from the JDeveloper UI. After you invoke this action if you didn't check re-load external tools in the wizard you need to press the refresh button to see any changes.

There is one remaining manual step and that is to associate the wsdl with the source file, to do this you simply need to update the wsdlLocation attribute:

package webservice;

import javax.jws.WebService;

@WebService(wsdlLocation = "/webservice/HelloService.wsdl")
public class Hello {
    public String hello(String name) {
        return "";
    }
}

This will now deploy with the new compact all in one wsdl document. JDeveloper will notice you have a file and tell you if the files get out of sync; but you will of course need to use your external tool command otherwise you will loose your in-line schema.

Wednesday, January 27, 2010

A little bit of REST with Oracle ADF / SDO

So I have been working on my BuildAnApp project, that I have mentioned previously, and decided to try and take the day out to build a simple RESTful interface to my ADF application module. Just a simple resource so I can create and update items from a single table.

The problem is always how to convert you objects into XML, so I decided to expose the Application Module with a Service interface. This results in an set of methods that can update the model in terms of SDO object which are easy to convert into XML.

I then created another project in JDeveloper along with a class called Root configured as a JAX-RS resource, you can find more about working with Jersey / JAX-RS web services in the help for 11R1PS1. I won't go into details here.

One of the limitations of Jersey is that it isn't yet properly integrated in to the container so nice JEE annotations such as @Resource don't work properly. I blogged about this previously but you can just create a class that looks like this as a short cut:

package org.concept.model.rest;

import com.sun.jersey.api.core.ResourceConfig;
import com.sun.jersey.core.spi.component.ComponentContext;
import com.sun.jersey.core.spi.component.ComponentScope;
import com.sun.jersey.spi.container.WebApplication;
import com.sun.jersey.spi.container.servlet.ServletContainer;
import com.sun.jersey.spi.inject.Injectable;
import com.sun.jersey.spi.inject.InjectableProvider;

import java.lang.reflect.Type;

import javax.annotation.Resource;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

import javax.servlet.ServletConfig;


public class InjectingAdapter extends ServletContainer {
    @Override
    protected void configure(ServletConfig servletConfig, ResourceConfig rc,
                             WebApplication wa) {
        super.configure(servletConfig, rc, wa);


        rc.getSingletons().add(new InjectableProvider<Resource, Type>() {

                public ComponentScope getScope() {
                    return ComponentScope.PerRequest;
                }

                public Injectable<Object> getInjectable(ComponentContext ic,
                                                        Resource r, Type c) {

                    final String name = r.name();


                    return new Injectable<Object>() {

                        public Object getValue() {
                            
                            Object value = null;

                            try {
                                Context ctx = new InitialContext();
        

                                // Look up a data source
                                try {
                                    value = ctx.lookup(name);
                                } catch (NamingException ex) {

                                    value =
                                            ctx.lookup("java:comp/env/" + name);
                                }

                            } catch (Exception ex) {
                                ex.printStackTrace();
                            }

                            return value;
                        }
                    };
                }
            });
    }
}

You have to modify the web.xml that JDeveloper generates to use this new class rather than the default Jersey servlet. I have also added in a ejb-ref that will pick up the service interface from the application module.

<web-app>

  ...

  <servlet>
    <servlet-name>jersey</servlet-name>
    <servlet-class>org.concept.model.rest.InjectingAdapter</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>jersey</servlet-name>
    <url-pattern>/*</url-pattern>
  </servlet-mapping>
  <ejb-ref>
    <ejb-ref-name>ConceptServiceInterface</ejb-ref-name>
    <ejb-ref-type>Session</ejb-ref-type>
    <remote>org.concept.model.am.common.serviceinterface.ConceptModuleService</remote>
    <ejb-link>org.concept.model.am.common.ConceptModuleServiceBean</ejb-link>
  </ejb-ref>
</web-app>

The next thing you have to worry about is that Jersey doesn't know how to deal with SDO objects out of the box. So we need to provide a Writer and Reader, these can be placed on the class path and Jersey will pick them up. Note that the implementation is far from production quality; but it is enough to get started.

package org.concept.model.rest;


import commonj.sdo.DataObject;
import commonj.sdo.helper.XMLHelper;

import java.io.IOException;
import java.io.OutputStream;

import java.lang.annotation.Annotation;
import java.lang.reflect.Type;

import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyWriter;
import javax.ws.rs.ext.Provider;


@Produces("application/sdo+xml")
@Provider
public class SDOMessageBodyWriter implements MessageBodyWriter<DataObject> {
    public SDOMessageBodyWriter() {
        super();
    }

    public boolean isWriteable(Class c, Type type, Annotation[] annotation,
                               MediaType mediaType) {
        return true;
    }


    public long getSize(DataObject dataObject, Class<?> class1, Type type,
                        Annotation[] annotations, MediaType mediaType) {
        return -1L;
    }

    public void writeTo(DataObject dataObject, Class<?> class1, Type type,
                        Annotation[] annotations, MediaType mediaType,
                        MultivaluedMap<String, Object> multivaluedMap,
                        OutputStream outputStream)  {

        // From http://wiki.eclipse.org/EclipseLink/Examples/SDO/StaticAPI
        
        // and http://www.eclipse.org/eclipselink/api/1.1/index.html


        try {
            commonj.sdo.Type sdoType = dataObject.getType();
            XMLHelper.INSTANCE.save(dataObject, sdoType.getURI(), sdoType.getName(), outputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

And the reader implementation:

package org.concept.model.rest;


import commonj.sdo.DataObject;
import commonj.sdo.helper.XMLDocument;
import commonj.sdo.helper.XMLHelper;

import java.io.InputStream;

import java.lang.annotation.Annotation;
import java.lang.reflect.Type;

import javax.ws.rs.Consumes;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.Provider;


@Consumes("application/sdo+xml")
@Provider
public class SDOMessageBodyReader implements MessageBodyReader<DataObject> {
    public boolean isReadable(Class<?> class1, Type type,
                              Annotation[] annotations, MediaType mediaType) {
        return true;
    }

    public DataObject readFrom(Class<DataObject> class1, Type type,
                               Annotation[] annotations, MediaType mediaType,
                               MultivaluedMap<String, String> multivaluedMap,
                               InputStream inputStream) {
        
        try
        {
            XMLDocument xmldocument = XMLHelper.INSTANCE.load(inputStream);        
            DataObject dos = xmldocument.getRootObject();
            return dos;
        }
        catch (Exception ex) {
            ex.printStackTrace();
            return null;
        }
    }
}

Now for the root resource, and we provide methods to get a list of concepts in the form of a URIList, get the data for a particular Concept, and update the values for a concepts. Delete and create are left as an exercise for the reader.

package org.concept.model.rest;


import commonj.sdo.helper.DataFactory;

import java.math.BigDecimal;

import java.net.URI;

import java.util.List;

import javax.annotation.Resource;

import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;

import oracle.jbo.common.service.types.FindControl;
import oracle.jbo.common.service.types.FindCriteria;

import org.concept.model.am.common.serviceinterface.ConceptModuleService;
import org.concept.model.vo.common.ConceptViewSDO;


@Path("/")
public class Root {

  @Resource(name = "ConceptServiceInterface")
  private ConceptModuleService service;
 

  @GET
  @Path("/concepts/")
  @Produces("application/xml")
  public Response getConcepts(
      @Context UriInfo info,
      @QueryParam("start") Integer start,
      @QueryParam("size") Integer size) {
    
    // If we don't have the start and end then redirect with one otherwise
    // the client could get a lot of data back
    //
    if (size==null) {
      return Response.seeOther(
        info.getRequestUriBuilder().queryParam("start", start!=null?start : 0)
                                   .queryParam("size", 10).build()).build();
    }
    
    //

    FindCriteria criteriaImpl =
      (FindCriteria)DataFactory.INSTANCE.create(FindCriteria.class);
    criteriaImpl.setFetchStart(start);
    criteriaImpl.setFetchSize(size);
    
    
    FindControl controlImpl =
      (FindControl)DataFactory.INSTANCE.create(FindControl.class);
    List list =
      service.findConceptView(criteriaImpl, controlImpl);

    // Build the uri list to return to the client
    //
    
    URI uriList[] = new URI[list.size()];
    int i = uriList.length;
    for (int j = 0; j < i; j++) {
      uriList[j] = info.getBaseUriBuilder().path("concept").path(
        list.get(j).getConceptId().toString()).build();
    }

    return Response.ok(new URIList(uriList)).build();
  }

  

  @GET
  @Path("/concepts/{concept}")
  @Produces("application/sdo+xml")
  public ConceptViewSDO getConcept(@PathParam("concept")
    BigDecimal conceptId) {
    ConceptViewSDO conceptView = service.getConceptView(conceptId);
    return conceptView;
  }


  @PUT
  @Path("/concepts/{concept}")
  @Consumes("application/sdo+xml")
  public void updateConcept(
    @PathParam("concept")
        BigDecimal conceptId,
    ConceptViewSDO concept) {

    // Check we are updating the correct concept
    //
    if (!conceptId.toBigInteger().equals(concept.getConceptId())) {
      throw new WebApplicationException(
              Response.Status.CONFLICT);
    }
    
    service.updateConceptView(concept);
  }


}

Now there is a lot more I could do for this resource, for example next and previous links to make it easier to iterate over the list. In the query method we should modify the FindCriteria to only return the ID attributes for performance reasons. We should also be providing action links for operations that we wish to perform on the concept entity. Finally since SDO allows you to easily generate a change summary it would be nice to be able to support the new HTTP PATCH method.

As I work on the project I will update this page as I add more features. There is some interesting hypermedia stuff coming in future versions of Jersey and I hope to be able to update this example to take this into account.

Tuesday, September 15, 2009

Wssp1.2-2007-Https-ClientCertReq.xml required further configuration

Just a quick post to note a problem I found with the above mentioned security policy. This policy should enabled mutual or two-way https; but you will find that if you deploy this service to what appears to be a properly configured service that it will fail:

@WebService
@Policy(uri="policy:Wssp1.2-2007-Https-ClientCertReq.xml")
public class HelloTwoWay {
   public String sayHello(String name)
   {
      return "Hello " + name;
   }
}

You need another step compared with other https policies to have this work. You need to go to Servers -> [ServerName] -> SSL -> Advanced and under "Two Way Cert Behaviour" you need at least "Client Certs Requested". You can go for the enforced option if you want to use mutual everywhere; but in that case you can use the more general https policies so it doesn't really make sense.

Friday, July 17, 2009

Jersey, your code, might be vulnerable to XXE attack

XXE is an interesting security hole where you use entity expansion in an XML document. Take for example the following xml file:

<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<search><user>&xxe;</user></search>

You might get the following response from a jersey service depending on how your XML parser in configured:

<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<search><response>User root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/bin/sh
...
not found</reponse></search>

There is a bunch of stuff in this thread on how to disable this expansion by default. This is fixed in the latest builds of Jersey 1.1.1ea so it is recommended that you upgrade. This does reproduce when running Jersey on weblogic so this is of interest. (Doesn't affect the JAX-WS stack)

Of course it is possible that any general xml parsing code you have might be vulnerable so it is worth understanding the problem so you can prevent it from happening in your application.

Tuesday, June 16, 2009

WS-SecurityPolicy Examples

Normally I wouldn't want to write and say that an OASIS spec makes an interesting read; but WS-SecurityPolicy Examples is interesting in that is annotates both common policy files and example messages. Could do with a little bit more fleshing out in the SAML section.

Tuesday, May 5, 2009

What version of JAX-WS is my weblogic using

Useful to know if you want to download the source for debugging purposes. Take a look at the following path:

%BEA_HOME%/modules/glassfish.jaxws.rt*.jar

For example you might see a file named "glassfish.jaxws.rt_2.1.3.jar". This tells you that we are using 2.1.3 of JAX-WS (src). On my machine using internal builds I get "glassfish.jaxws.rt_1.0.0.0_2-1-4.jar" which tells me that I am using a patched version of the 2.1.4 build of JAX-WS (src). The latter might differ slightly from the published source; but probably not enough to worry about.

Tuesday, April 21, 2009

Nice WSDL to HTML conversion

Whilst trying to help some one internally I can across this WSDL -> HTML converter (example with a google api). The cool thing is that you can download the XSLT file for use in your own build processes. Useful for automatically generating documentation. (Other tools are available; but many only work on the internal so no good for internal projects.)

I guess combined with the WSDL generation extension features in JAX-WS we could make things a bit prettier for ya basic JAX-WS services. Maybe if I get some time one day....

Thursday, February 19, 2009

Exposing Tables Mapped to Entity Beans as a Webservice

I wanted to create a simple web service to access some database tables. This turns out to be a little bit harder than I though as the classes generated for JPA are a little bit different in intention as those required JAXB / Web Services which are more method driven. This example just uses EJB; but the same should apply to using JPA straight.

Although the screen grabs are from JDeveloper, of course, the the following should apply to most java IDEs. So first of all lets create some entities from tables in the database:

Then I am going to create a connection to the old favourite scott/tiger:

Now in the screen shot and a few more I have selected more than just Emp/Dept. Just ignore that and assume that I had just selected the two. I didn't have the heart to go back and re-shoot them.

So finish the entity bean wizard and then bring up the EJB Sesssion Bean wizard so we can create a suitable Façade to publish as our web service.

You can pretty much accept the defaults on this page as it has already picked up the persistence unit from the previous wizard:

Now we can just clagg on the @WebService annotation and run the service....

... well it turns out that if you try to run the Session Bean that it fails to deploy to the server with something like the following error:


[HTTP:101216]Servlet: "WSEE_SERVLET" failed to preload on startup in Web application: "/EmpDept-EmpDeptFacade-webapp".
javax.xml.ws.WebServiceException: Unable to create JAXBContext
 at com.sun.xml.ws.model.AbstractSEIModelImpl.createJAXBContext(AbstractSEIModelImpl.java:158)
 ...
 at weblogic.work.ExecuteThread.run(ExecuteThread.java:173)
Caused by: java.security.PrivilegedActionException: com.sun.xml.bind.v2.runtime.IllegalAnnotationsException: 1 counts of IllegalAnnotationExceptions
java.sql.Timestamp does not have a no-arg default constructor.
 this problem is related to the following location:
  at java.sql.Timestamp
  at public java.sql.Timestamp empdeptfacade.Emp.getHiredate()
  at empdeptfacade.Emp
  at public java.util.List empdeptfacade.jaxws.QueryEmpFindAllResponse._return
  at empdeptfacade.jaxws.QueryEmpFindAllResponse

 at java.security.AccessController.doPrivileged(Native Method)
 at com.sun.xml.ws.model.AbstractSEIModelImpl.createJAXBContext(AbstractSEIModelImpl.java:148)
 ... 54 more
Caused by: com.sun.xml.bind.v2.runtime.IllegalAnnotationsException: 1 counts of IllegalAnnotationExceptions
java.sql.Timestamp does not have a no-arg default constructor.
 this problem is related to the following location:
  at java.sql.Timestamp
  at public java.sql.Timestamp empdeptfacade.Emp.getHiredate()
  at empdeptfacade.Emp
  at public java.util.List empdeptfacade.jaxws.QueryEmpFindAllResponse._return
  at empdeptfacade.jaxws.QueryEmpFindAllResponse

 at com.sun.xml.bind.v2.runtime.IllegalAnnotationsException$Builder.check(IllegalAnnotationsException.java:102)
 at com.sun.xml.bind.v2.runtime.JAXBContextImpl.getTypeInfoSet(JAXBContextImpl.java:438)
 at com.sun.xml.bind.v2.runtime.JAXBContextImpl.(JAXBContextImpl.java:286)
 at com.sun.xml.bind.v2.ContextFactory.createContext(ContextFactory.java:139)
 at com.sun.xml.bind.api.JAXBRIContext.newInstance(JAXBRIContext.java:105)
 at com.sun.xml.ws.model.AbstractSEIModelImpl$1.run(AbstractSEIModelImpl.java:153)
 at com.sun.xml.ws.model.AbstractSEIModelImpl$1.run(AbstractSEIModelImpl.java:148)
 ... 56 more

Now JAXB will handle quite a few cases/classes by default; but one annoying case it won't deal with is the java.sql.Timestamp class as it doesn't have a no-arg constructor. So work around this you need to define an adapter to do the conversion, here is a trivial implementation that may not work in all cases, particularly is you plan to write back to the database:

package empdeptfacade;

import java.sql.Timestamp;

import java.util.Date;

import javax.xml.bind.annotation.adapters.XmlAdapter;

public class TimestampAdapter
  extends XmlAdapter<Date, Timestamp> {

    public Date marshal(Timestamp v) {
        return new Date(v.getTime());
    }

    public Timestamp unmarshal(Date v) {
        return new Timestamp(
            v.getTime());
    }
}

Now you could attach this to each and every usage of the Timestamp class, admittedly just the once in this case, but a much tidier way to do it is to attach it to the package-info.java class instead and have it apply to a number of classes. Unfortunately JDeveloper doesn't allow you to create one from the java wizard - so you just have to use the "New File" wizard from the gallery.

And the code for the package needs to look like:


@XmlJavaTypeAdapters(
   @XmlJavaTypeAdapter(value=TimestampAdapter.class,type=Timestamp.class))
package empdeptfacade;

import java.sql.Timestamp;

import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapters;

You should now find that your EJB will deploy quite happily; but you will find that if you actually invoke one of the methods on the web service it fails with the following error:

avax.xml.ws.WebServiceException: javax.xml.bind.MarshalException
 - with linked exception:
[com.sun.istack.SAXException2: A cycle is detected in the object graph. This will cause infinitely deep XML: empdeptfacade.Dept@50dd8d -> empdeptfacade.Emp@11541d8 -> empdeptfacade.Dept@50dd8d]
 at com.sun.xml.ws.message.jaxb.JAXBMessage.writePayloadTo(JAXBMessage.java:322)
 at com.sun.xml.ws.message.AbstractMessageImpl.writeTo(AbstractMessageImpl.java:153)
 at com.sun.xml.ws.encoding.StreamSOAPCodec.encode(StreamSOAPCodec.java:108)
 at com.sun.xml.ws.encoding.SOAPBindingCodec.encode(SOAPBindingCodec.java:258)
 at com.sun.xml.ws.transport.http.HttpAdapter.encodePacket(HttpAdapter.java:326)
 at com.sun.xml.ws.transport.http.HttpAdapter.access$100(HttpAdapter.java:99)
 at com.sun.xml.ws.transport.http.HttpAdapter$HttpToolkit.handle(HttpAdapter.java:460)
 at com.sun.xml.ws.transport.http.HttpAdapter.handle(HttpAdapter.java:250)
 at com.sun.xml.ws.transport.http.servlet.ServletAdapter.handle(ServletAdapter.java:140)
 at weblogic.wsee.jaxws.HttpServletAdapter$AuthorizedInvoke.run(HttpServletAdapter.java:289)
 at weblogic.wsee.jaxws.HttpServletAdapter.post(HttpServletAdapter.java:202)
 at weblogic.wsee.jaxws.JAXWSServlet.doPost(JAXWSServlet.java:237)
 at javax.servlet.http.HttpServlet.service(HttpServlet.java:727)
 at weblogic.wsee.jaxws.JAXWSServlet.service(JAXWSServlet.java:77)
 at javax.servlet.http.HttpServlet.service(HttpServlet.java:820)
 at weblogic.servlet.internal.StubSecurityHelper$ServletServiceAction.run(StubSecurityHelper.java:227)
 at weblogic.servlet.internal.StubSecurityHelper.invokeServlet(StubSecurityHelper.java:125)
 at weblogic.servlet.internal.ServletStubImpl.execute(ServletStubImpl.java:292)
 at weblogic.servlet.internal.ServletStubImpl.execute(ServletStubImpl.java:175)
 at weblogic.servlet.internal.WebAppServletContext$ServletInvocationAction.run(WebAppServletContext.java:3586)
 at weblogic.security.acl.internal.AuthenticatedSubject.doAs(AuthenticatedSubject.java:321)
 at weblogic.security.service.SecurityManager.runAs(SecurityManager.java:121)
 at weblogic.servlet.internal.WebAppServletContext.securedExecute(WebAppServletContext.java:2196)
 at weblogic.servlet.internal.WebAppServletContext.execute(WebAppServletContext.java:2102)
 at weblogic.servlet.internal.ServletRequestImpl.run(ServletRequestImpl.java:1428)
 at weblogic.work.ExecuteThread.execute(ExecuteThread.java:201)
 at weblogic.work.ExecuteThread.run(ExecuteThread.java:173)
Caused by: javax.xml.bind.MarshalException
 - with linked exception:
[com.sun.istack.SAXException2: A cycle is detected in the object graph. This will cause infinitely deep XML: empdeptfacade.Dept@50dd8d -> empdeptfacade.Emp@11541d8 -> empdeptfacade.Dept@50dd8d]
 at com.sun.xml.bind.v2.runtime.MarshallerImpl.write(MarshallerImpl.java:282)
 at com.sun.xml.bind.v2.runtime.BridgeImpl.marshal(BridgeImpl.java:90)
 at com.sun.xml.bind.api.Bridge.marshal(Bridge.java:107)
 at com.sun.xml.ws.message.jaxb.JAXBMessage.writePayloadTo(JAXBMessage.java:317)
 ... 26 more
Caused by: com.sun.istack.SAXException2: A cycle is detected in the object graph. This will cause infinitely deep XML: empdeptfacade.Dept@50dd8d -> empdeptfacade.Emp@11541d8 -> empdeptfacade.Dept@50dd8d
 at com.sun.xml.bind.v2.runtime.XMLSerializer.reportError(XMLSerializer.java:244)
 at com.sun.xml.bind.v2.runtime.XMLSerializer.pushObject(XMLSerializer.java:533)
 at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer.java:627)
 at com.sun.xml.bind.v2.runtime.property.SingleElementNodeProperty.serializeBody(SingleElementNodeProperty.java:150)
 at com.sun.xml.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:322)
 at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer.java:681)
 at com.sun.xml.bind.v2.runtime.property.ArrayElementNodeProperty.serializeItem(ArrayElementNodeProperty.java:65)
 at com.sun.xml.bind.v2.runtime.property.ArrayElementProperty.serializeListBody(ArrayElementProperty.java:168)
 at com.sun.xml.bind.v2.runtime.property.ArrayERProperty.serializeBody(ArrayERProperty.java:152)
 at com.sun.xml.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:322)
 at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer.java:681)
 at com.sun.xml.bind.v2.runtime.property.ArrayElementNodeProperty.serializeItem(ArrayElementNodeProperty.java:65)
 at com.sun.xml.bind.v2.runtime.property.ArrayElementProperty.serializeListBody(ArrayElementProperty.java:168)
 at com.sun.xml.bind.v2.runtime.property.ArrayERProperty.serializeBody(ArrayERProperty.java:152)
 at com.sun.xml.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:322)
 at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer.java:681)
 at com.sun.xml.bind.v2.runtime.MarshallerImpl.write(MarshallerImpl.java:277)
 ... 29 more

Problem here is the methods it generates are a bit too helpful. For example Dept has a method that returns all of the Employees in the Department which in turn returns the Department you are in. Now there are a variety of ways you can solve this problem but I am going for the simplest method of marking the employee accessors of Dept as transient.

You can now run the service and get a list of departments back:

Now the Emp class has a similar problem in that for each Emp you get a copy of the Dept for it. This denormlizes the data and might be a problem in certain use cases. You can consider using XMLID/XMLIDref as mentioned in the previous link but this will only work properly if you return a structure that contains the employees and the department. Otherwise you will not get any department objects back to the client. You would need to make the following changes to get this to work:


// Changes to Dept.java note XmlID has to be of type String
// otherwise JAXB will complain.

    @XmlID
    @XmlJavaTypeAdapter(LongAdapter.class)
    public Long getDeptno() {
        return deptno;
    }

// Changes to Empt.java

    @XmlIDREF
    public Dept getDept() {
        return dept;
    }

// Long Adapter

public class LongAdapter extends XmlAdapter<String, Long> {


    public Long unmarshal(String v) {
        return Long.parseLong(v);
    }

    public String marshal(Long v) {
        return v.toString();
    }
}

// Extra query method in SessionEJBBean

    public EmpDeptList queryEmpFindAllWithDepts() {
        List<Emp> list = em.createNamedQuery("Emp.findAll").getResultList();
        Set<Dept> deps = new HashSet<Dept>();
        for (Emp emp : list) {
            deps.add(emp.getDept());
        }
        
        EmpDeptList edl = new EmpDeptList();
        edl.setEmployees(list);
        edl.setDepartments(deps);
        return edl;
    }

The cool thing about JAX-B is that on the client side the object are linked back together even though in the XML message they are sent as two different lists. (Note the cast in Dept, not sure if this is a bug or a feature of using IDRef. Something to look into at a future date)

    sessionEJBBeanService = new SessionEJBBeanService();
    SessionEJBBean sessionEJBBean = sessionEJBBeanService.getSessionEJBBeanPort();
    // Add your code to call the desired methods.

    QueryEmpFindAllWithDeptsResponse allWithDepts =
        sessionEJBBean.queryEmpFindAllWithDepts(new QueryEmpFindAllWithDepts());
    Emp e = allWithDepts.getReturn().getEmployees().get(0);
    Dept d = (Dept)e.getDept();
    System.out.println(d.getDname());

Of course this exposes the limitation of the annotation programming model as it is much harder to present different XML views of your data depending with the same bean model. The key take away point I guess is that database data can be in the form or a mesh whereas you have to be careful when creating web services and generating XML that the data is mapped into a tree.

Wednesday, February 18, 2009

Updated version of JSR 235 spec : Service Data Object (SDO)

Worth a quick read if only for the ability to transfer only the change sets back over the wire. Many focused on SOA at the moment; but will be interested to see if this gets used more widely in web services.

Monday, February 9, 2009

Correlating keys in WS-SX messages with certificates you have on disk

I have been looking at a problem over the last week where we were not sure what certificate was being used in a particular case during a secure web service exchanges. It turns out that there are a number of ways a certificate could be referenced and it required a bit of maths to do the correlation. (Also in some cases the full certificate can be included but that is a rather easier correlation problem as you just need to un-base64 and diff) Both of these cases assume you have the ".der" file for the certificate to work with.

In the first example, thanks to Chris M, the SecurityTokenReference contains the serial number of the certificate:

<wsse:SecurityTokenReference
    xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
    xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
    wsu:Id="str_1VQOGYy2CvP18oEC">
  <X509Data
    xmlns="http://www.w3.org/2000/09/xmldsig#">
    <X509IssuerSerial>
      <X509IssuerName>CN=CertGenCAB,OU=FOR TESTING    ONLY,O=MyOrganization,L=MyTown,ST=MyState,C=US</X509IssuerName>
      <X509SerialNumber>9652974370220359889588047549019486289</X509SerialNumber>
    </X509IssuerSerial>
  </X509Data>
</wsse:SecurityTokenReference> 

Now I came up with the following which uses openssl to print out the serial number, in hex, and then convert it to decimal so you can compare it with the value in the soap message. If you know more unix voodoo than me feel free to comment with a more optimal version, thanks to Alan Davis for some help here.

[gdavison Certificates]$ ( echo ibase=16 ; openssl x509 -serial -inform der -noout  -in ClientCert.der ; echo serial ) | bc -q
9652974370220359889588047549019486289

It is worth know you can convince openssl to dump your entire certificate in a pretty form using the -text option if you want to look at it a bit closer:

[gdavison@ukp79266 Certificates]$ openssl x509 -inform der -noout -text -in ServerCert.der
Certificate:
    Data:
        Version: 1 (0x0)
        Serial Number:
             (Negative)79:76:ab:94:51:fe:b5:7c:a5:c8:15:88:1d:15:a6:31
        Signature Algorithm: md5WithRSAEncryption
        Issuer: C=US, ST=MyState, L=MyTown, O=MyOrganization, OU=FOR TESTING ONLY, CN=CertGenCAB
        Validity
            Not Before: Jan 19 13:46:59 2009 GMT
            Not After : Jan 20 13:46:59 2024 GMT
        Subject: C=US, ST=MyState, L=MyTown, O=MyOrganization, OU=FOR TESTING ONLY, CN=XXXXXX
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
            RSA Public Key: (1024 bit)
                Modulus (1024 bit):
                    00:a4:9b:ae:fe:57:0f:5b:23:11:9c:60:f0:b0:29:
                    2a:6a:2d:d5:3e:44:a9:84:c4:a3:52:7a:d2:1c:1e:
                    bf:ed:eb:5d:90:20:8f:05:da:a1:1c:7b:67:90:36:
                    2e:7d:f9:c5:6e:82:25:b8:72:9a:25:7f:20:d7:ec:
                    9b:dd:d5:a0:95:8a:a0:df:20:d7:f7:04:89:da:17:
                    9c:d7:ce:f8:7f:fa:59:03:dc:ad:3b:51:ba:ad:57:
                    f4:5a:e6:29:85:8e:02:c9:a4:36:35:fb:a2:17:70:
                    74:ab:56:a3:ed:ed:c0:d8:26:f8:68:4f:ad:78:b5:
                    53:cd:f4:7b:93:e0:05:55:fd
                Exponent: 65537 (0x10001)
    Signature Algorithm: md5WithRSAEncryption
        3f:d6:e7:0b:00:0c:14:13:90:9e:7d:de:cf:46:4d:9d:3e:f3:
        57:fa:27:6c:0c:bf:b0:9e:d8:91:93:6b:47:e5:62:31:29:52:
        b2:b5:3e:b8:1b:ac:2d:c0:b3:38:ab:e5:f6:5c:30:5f:04:7b:
        4f:90:90:75:25:81:59:33:24:e8

In the second example we have the following key info but this in case uses the sha1 of the key in base64 format:

<ns3:KeyInfo xmlns:ns3="http://www.w3.org/2000/09/xmldsig#">
  <wsse:SecurityTokenReference xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsse11="http://docs.oasis-open.org/wss/oasis-wss-wssecurity-secext-1.1.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsse11:TokenType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3" wsu:Id="str_fC2JkjjyT0mUbvXk">
    <wsse:KeyIdentifier EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary" ValueType="http://docs.oasis-open.org/wss/oasis-wss-soap-message-security-1.1#ThumbprintSHA1"      
       >eF68uMnGeydQmRc69mWanQzAek0=</wsse:KeyIdentifier>
  </wsse:SecurityTokenReference>
</ns3:KeyInfo>

You can easily use the openssl command and base64 to convert you certificate into the required format for comparison. You can work out what processes you need to apply by looking at the ValueType, ThumbprintSHA1, and the EncodingType, Base64Binary, in this case. It would be relatively easy to generate the correct string should another hashing algorithm be specified.

[gdavison Certificates]$ openssl dgst -binary -sha1 ClientCert.der | base64
eF68uMnGeydQmRc69mWanQzAek0=

The upshot of this is whilst the structure and format of the certificate files can be intimidating at first the openssl tool can be used to make things more transparent.

Thursday, February 5, 2009

What is wrong with this SOAP message?

We were dealing with an issue with database web services and came across something you don't see very often, an badly formed soap message. Before reading on take a few minutes to look at this message and try to work out what is wrong, there are actually two issues:

<?xml version = '1.0'?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <soap:Fault>
      <soap:faultcode>
        <soap:Value>soap:Sender</soap:Value>
      </soap:faultcode>
      <soap:faultstring>Error processing input</soap:faultstring>
      <soap:detail>
        ...
      </soap:detail>
    </soap:Fault>
  </soap:Body>
</soap:Envelope>

The most important problem is that the schema for SOAP 1.1 Envelope doesn't define "elementForDefault" so it uses the default "unqualified" form. This requires that non-top level element do not have namespace prefixes. If you examine the schema closely this will mean that Envelope, Body and Fault need an explicit namespace but faultcode et al don't. WS-I basic profile actually has a rule to catch this case. In some cases if in doubt you can run the WS-I testing tools in JDeveloper.

So after fixing the namespace the message looks like:

<?xml version = '1.0'?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <soap:Fault>
      <faultcode>
        <Value>soap:Sender</Value>
      </faultcode>
      <faultstring>Error processing input</faultstring>
      <detail>
        ...
      </detail>
    </soap:Fault>
  </soap:Body>
</soap:Envelope>

This level of confusion is probably why "qualified" is not considered by many to be a better default, for example this is how the SOAP 1.2 specification is defined. We have a audit rule in JDeveloper that makes this suggested fix when it sees it.

There is one final issue in that the faultcode element is wrong. The schema requires a QName, the Value element isn't part of any specification. It it worth noting that schema validation annotation I mentioned previously was a boon for tracking down this error.

The upshot of this is that JAX-WS would be quite happy as a client of this application just as long as it didn't throw a fault. Of course working this out was a team effort with thanks to Alan Davis who did the donkey work and original investigation and Pyounguk for providing the WS-I link.

Wednesday, February 4, 2009

Where is policy configured on the weblogic console?

On common issue people have is finding the WS-Policy page on the web logic console. Once you have navigated to the web service you tend to think to click on the "Security" tab but find nothing. This is because WS-Policy, at least for JAX-RPC, can be used to configure reliability or mtom as well so the setting for these are under the configuration tab instead:

Once you get your head around the fact that policy is not just for security this kinda makes sense.

Wednesday, January 28, 2009

Deploying JAX-WS RI to Tom Cat

Just some quick notes today on deploying to a JAX-WS RI service to Tomcat. It has to be the RI version as of course Tomcat doesn't have web service support built in. First things first was to edit .../conf/tomcat-users.xml to add a manager account so you can see when deployments go bad. It should look something like this:

<?xml version='1.0' encoding='utf-8'?>
<tomcat-users>
  <role rolename="manager"/>
  <role rolename="standard"/>
  <user username="admin" password="tomcat" roles="standard,manager"/>
</tomcat-users>

You can start tomcat in a variety of ways, I prefer "catalina.sh run" as it doesn't start a process in the background. So it is easier to keep an eye on.

Now if you created your RI class in JDeveloper you might find that the sun-jaxws.xml file is created with incorrect deployment descriptors depending on the build of JDeveloper you are using. Make sure the url-pattern and name match the servlet-name and in web.xml.

<?xml version = '1.0' encoding = 'UTF-8'?>
<endpoints xmlns="http://java.sun.com/xml/ns/jax-ws/ri/runtime" version="2.0">
  <endpoint url-pattern="/Class1Port" implementation="riproject.Class1"
            name="Class1Port"/>
</endpoints>

For comparison the matching web.xml

<?xml version = '1.0' encoding = 'ISO-8859-1'?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5" xmlns="http://java.sun.com/xml/ns/javaee">
    <description>Empty web.xml file for Web Application</description>
    <listener>
        <listener-class>com.sun.xml.ws.transport.http.servlet.WSServletContextListener</listener-class>
    </listener>
    <servlet>
        <servlet-name>Class1Port</servlet-name>
        <servlet-class>com.sun.xml.ws.transport.http.servlet.WSServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>Class1Port</servlet-name>
        <url-pattern>/Class1Port</url-pattern>
    </servlet-mapping>
    ...
</web-app>

When you create a RI web service you do end up with the "JAX-WS RI Web Services" library. This isn't enough to deploy to tomcat, indeed the libraries we are using are modified so they only really make sense when used inside weblogic. Instead you are better of creating a new library from a fresh RI download, make sure you mark as being deployed by default. You could also just add the RI classes to the default classpath for Tomcat, this might make more sense in a production environment.

You can create a connection to your Tomcat instances from the resource pallet in the normal way; but you will find that the web service deployment profile that is created won't allow you to deploy to your new connection.

This is because our profiles default to weblogic as there deployment platform. If you clear this value as in the following screen grab you can then use this deployment profile on tomcat.

If all goes well in deployment you should find that you service is deployed properly, you can monitor this at the default manager URL http://localhost:8080/manager using the password we defined earlier.

Monday, January 26, 2009

Schema Validation for JAX-WS

We had a very simple test case that confused myself and Alan, the replacement Alan not the old one, for a few minutes today. Our web service looked like this:

@WebService
public class Echo {
    public int echoInt (int i) {
        return i;
    }

}

But when we passed in invalid data to the web service the value of i was 0. So here is example message that JAX-WS will consume properly; but is obviously invalid:

<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://project1/">
   <env:Header/>
   <env:Body>
      <ns1:echoInt>
         <arg0>xxxx</arg0>
      </ns1:echoInt>
   </env:Body>
</env:Envelope>

It took us a while to remember that schema validation is optional, (See section 1.1 of the JAX-WS specification), and that there is no standard way to turn it on. The RI on the other hand does provide a way and it is as simple as sticking in a annotation. It also has some cool stuff for handling and ignoring specific errors which I find interesting; but I don't have a use case for yet.

import com.sun.xml.ws.developer.SchemaValidation;

@WebService
@SchemaValidation()
public class Echo {
    public int echo (int i) {
        return i;
    }
}

With our previous test data you now get a fault back:

<?xml version = '1.0' encoding = 'UTF-8'?>
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
   <S:Body>
      <S:Fault xmlns:ns4="http://www.w3.org/2003/05/soap-envelope">
         <faultcode>S:Server</faultcode>
         <faultstring>com.sun.istack.XMLStreamException2: org.xml.sax.SAXParseException: cvc-datatype-valid.1.2.1: 'xxxx' is not a valid value for 'integer'.</faultstring>
         <detail>
            ...
         </detail>
      </S:Fault>
   </S:Body>
</S:Envelope>

Of course there are performance reasons why you might not want to validate the incoming message but it could make your service code much easier if you know it doesn't have to deal with invalid data. Also this is part of the RI so subject to change more than the -WS straight.

Thursday, January 22, 2009

Getting Firefox to use the HTTP Analyzer when run from within JDeveloper

It is really helpful when debugging AJAX application to see the traffic between the the web browser and the client. You can do this using the HTTP Analyzer; but it can get really annoying to have to set and reset your proxy settings in the browser.

It turns out that if you are using firefox you can get around this by using named profiles. The following assumes Linux, but windows people can just clag on .exe to the commands. One thing you have to understand is that by default the firefox command will just open a window on your currently open instance of firefox so you need to use "-no-remote" to create a separately configured instance. So from the command line run:

firefox -no-remote -CreateProfile Debugging

Now just quickly start a firefox with this profile so you can configure the proxy settings to use the analyzer in JDeveloper. (localhost, 8099 and no host exclusions)

firefox -no-remote -P Debugging

So you need to configure JDeveloper to start this special firefox every time, simply go to Tools->Preferences and paste in this command line. It is likely that JDeveloper will complain it cannot verify it and underline it in red; but it does seem to work okay:

So all you have to do is to start the HTTP Analyzer and run HTML/JSP/JSF page from within JDeveloper and a new instance of firefox using the Debugger profile will be started. The downside is that this is less memory efficient as for each run you will be creating a new copy of firefox; but it seems worth it to simplify development.

Annoyingly this trick can't use used directly to configure the java script debugger, due to a bug in the handling of command lines. A work around might be to create a wrapper script for firefox that passed on the above parameters along with any passed values. Don't have time to try that today though.

Wednesday, January 21, 2009

Security Policy Worked Example

Whilst it can be simple in concept many people find configuring security for web services to be something that is very daunting. Unfortunately, and particularly for JAX-WS, there is not yet a nice simple tutorial available that explains all the steps. This isn't that tutorial yet; but I hope to be able to walk through the process providing as much information as possible so as to become a starting point. (Also refer to this weblogic document on the web logic site)

As I work developing tools for weblogic I tend to have a new install every two or three days. For this reason this blog will be from the angle of configuring web logic security from a developers point of view. I will try to annotated the process where you would likely diverge in a production environment. Any commands run in this blog are run in the context of setDomainEnv command that you can find in your domain's "bin" directory. For JDeveloper users who want to configure the integrated domain you will find this in your ~/.jdeveloper/systemXXXXXX/ DefaultDomain/bin directory.

So for the purposes of this blog we are going to consider a simple stock trading application. We are going to pick one of the predefined weblogic policies to make our life easier. My code looks something like this:

@WebService
@Policy(uri = "policy:Wssp1.2-2007-Wss1.1-UsernameToken-Plain-X509-Basic256.xml")
public class BrokerService {

   ...
}

This policy has a bit of everything, user name tokens, encryption of said tokens and then signing of the whole kit and caboodle. A better match for this service in the real world would probably be to encrypt the entire message; but it wouldn't be such a good example so I am going to stick with this policy.

It is worth taking a look at the policy name as the naming convention used by web logic can tell you a lot about what is actually in the file. (For the content of the file take a look at my previous missive) "Wssp1.2-2007" tells you that the policy file contains assertions from the WS-SecurityPolicy 1.2 specification using the revised 2007 name space. "UsernameToken-Plain" tell you that the unecrypted text of the password token if passed in rather than a digest. "x509" for most cases we are talking RSA Public/Private key. Finally "Basic256" tell you which combinations of algorithm suite is being used. The last point takes us to the Wssp1.2 specification section 6.1 which has a table which explains what encryption and what key lengths are required for each suite.

One thing to look at in this document the asynchronous minimum key length, AKL, is 1024 bits this means that you cannot unfortunately make use of the DemoIdentity key store for a simple configuration; but instead need to create some new keys fortunately weblogic has some commands that make this much easier.

Before we get to this we need to take a quick diversion into the topic of trust. For this all to work the server has to be able to trust that the key combination used by the client to sign the message is a valid one. You have two choices for this, either add the client certificate to the server trust store directly or sign the client certificate with a certificate authority that is trusted by the server. The latter is more useful for distributed application as you don't have to worry about out of band key passing so we are going to use it in this example.

Luckily your weblogic instances comes with a demo CA, you can find the certificate and key for this in .../wlserver_10.3 /server/lib/CertGenCA.der and CertGenCAKey.der. This key doesn't appear to change between weblogic installations and is trusted by the default DemoTrust store. For this reason it is very very important you never have the DemoTrust store enabled in a production environment. Otherwise anybody can become trusted fairly easily; but the purposes of setting up a development environment it is really useful.

We are going to use the weblogic CertGen command that will generate keys of the correct key length and more importantly sign it with the demo CA we just mentioned. So we need a client cert/key pair to sign the outgoing message and the server certificate to encrypt the important parts. You are probably bored of me exposing now so lets actually run some commands:

java utils.CertGen -certfile ClientCert -keyfile ClientKey -keyfilepass ClientKey
java utils.CertGen -certfile ServerCert -keyfile ServerKey -keyfilepass ServerKey

The server cert doesn't really need to be signed by the CA; but it is easier to use the same command to save time. Note if you were doing this for a production system you probably want to us something more industrial like openssl to generate your keys as the weblogic documentation recommends. From this you end up with a bunch of .der and .pem files which we need to import into key stores, actually it will create new one for you if they don't exist, to make them easier to use from java, again using the weblogic helper command:

java utils.ImportPrivateKey -certfile ClientCert.der -keyfile ClientKey.der -keyfilepass ClientKey -keystore ClientIdentity.jks -storepass ClientKey -alias identity -keypass ClientKey
java utils.ImportPrivateKey -certfile ServerCert.der -keyfile ServerKey.der -keyfilepass ServerKey -keystore ServerIdentity.jks -storepass ServerKey -alias identity -keypass ServerKey

So now we get into the nitty gritty of configuring the server side of the equation. As mentioned before we are going to rely on the DemoTrust store so we only need to configure the server certificate and private key. Now the easiest way to do this is to make us of a script that comes with the standalone web logic install, for some reason it doesn't get installed with JDeveloper, and you can find the script in ../wlserver_10.3/samples/server/examples/src/ examples/webservices/ wss1.1/configWss.py or from the edocs site. So to configure the server we simply need to run the following command making sure you replace /.../ with the absolute path to the file in each case.

wlst /.../configWss.py weblogic weblogic localhost 7001 /.../ServerIdentity.jks ServerKey identity ServerKey

You can verify that this command has run properly by looking at the "Web Service Security" tab on your domain from the weblogic logic console. Note that the default_ww configuration is used for all web services unless you intimate otherwise. That part of the configuration is for a future blog.

So after restarting your server you can now create a client to invoke this service. The code needs to provide the client key and certificates for signing; the servers certificate so we can encrypt the message and finally something to provide the user name password tokens. For completeness here is the code I used to test this configuration:


import java.security.cert.X509Certificate;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.xml.ws.BindingProvider;
import javax.xml.ws.WebServiceRef;

import weblogic.security.SSL.TrustManager;

import weblogic.wsee.security.bst.ClientBSTCredentialProvider;
import weblogic.wsee.security.unt.ClientUNTCredentialProvider;
import weblogic.wsee.security.util.CertUtils;

import weblogic.xml.crypto.wss.WSSecurityContext;
import weblogic.xml.crypto.wss.provider.CredentialProvider;


//


brokerServiceService = new BrokerServiceService();
BrokerService brokerService =
brokerServiceService.getBrokerServicePort();

// Security stuff
//

// String constants to for server certificate, and client identity store

String serverCertFile = ".../ServerCert.der";
String clientKeyStore = ".../ClientIdentity.jks";
String clientKeyStorePass = "ClientKey";
String clientKeyAlias = "identity";
String clientKeyPass = "ClientKey";

// Create list of credential providers

List credProviders = new ArrayList();

// Create user name token provider

ClientUNTCredentialProvider unt =
 new ClientUNTCredentialProvider("weblogic", "weblogic");
credProviders.add(unt);

// Create a credential provider with the client indentity and the server side
// certificate

final X509Certificate serverCert =
 (X509Certificate)CertUtils.getCertificate(serverCertFile);
serverCert.checkValidity();

CredentialProvider cp =
 new ClientBSTCredentialProvider(clientKeyStore, clientKeyStorePass,
                                 clientKeyAlias, clientKeyPass,
                                 "JKS", serverCert);
credProviders.add(cp);

// Finally add the credential providers to the request context

Map requestContext =
 ((BindingProvider)brokerService).getRequestContext();

requestContext.put(WSSecurityContext.CREDENTIAL_PROVIDER_LIST,
                credProviders);

// Setup the TrustManager to verify the signature on the returned message

requestContext.put(WSSecurityContext.TRUST_MANAGER,
                new TrustManager() {
     public boolean certificateCallback(X509Certificate[] chain,
                                        int validateErr) {
         // Check that the server cert matches
         boolean result = chain[0].equals(
                    serverCert);
         return result;
     }
 });

// Invoke the service

BigDecimal bigDecimal = brokerService.getStockQuote("ORCL");
System.out.println(bigDecimal);

Now of course you need to copy the client keystore and the server certificate to the machine you are running the client from. This is okay as the client keystore with the private key is a secret only the client needs to know and the server certificate is public information.

So here is the general overview of the steps that the weblogic client will go through to send this message:

  • Generate a new aes256 symmetric key, encrypt using the servers certificate. (Think RSA public key) and included in the message
  • Encrypt the WS-Security UNT headers using the aes256 key and replace in message
  • Include the client certificate, which the server trust as has been signed by the demo CA
  • Create signature block with digest for each part of the message and the client private key. (The server can then verify this using the client certificate)

Although this is probably a bit too much detail lets look at an example message that the client could send to the server. I have tried to annotate the message so we can relate it to the configuration we have done do far.

>
<?xml version = '1.0' encoding = 'UTF-8'?>
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
   <S:Header>
      <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis 
-200401-wss-wssecurity-secext-1.0.xsd" S:mustUnderstand="1">
      

      
         <ns1:EncryptedKey xmlns:ns1="http://www.w3.org/2001/04/xmlenc#" 
Id="u2KgDzrQ0fxzn776">
            <ns1:EncryptionMethod 
Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p">
               <ns2:DigestMethod xmlns:ns2="http://www.w3.org/2000/09/xmldsig#" 
Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
            </ns1:EncryptionMethod>
            <ns3:KeyInfo xmlns:ns3="http://www.w3.org/2000/09/xmldsig#">
            

            
               <wsse:SecurityTokenReference 
xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" 
xmlns:wsse11="http://docs.oasis-open.org/wss/oasis-wss-wssecurity-secext-1.1.xsd" 
xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" 
wsse11:TokenType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3" 
wsu:Id="str_c8qqMexjG7qPtxHf">
                  <wsse:KeyIdentifier 
EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary" 
ValueType="http://docs.oasis-open.org/wss/oasis-wss-soap-message-security-1.1#ThumbprintSHA1">
+JvAr7erivisYq6D+P8HGAj0678=</wsse:KeyIdentifier>
               </wsse:SecurityTokenReference>
            </ns3:KeyInfo>




            <ns1:CipherData>
               <ns1:CipherValue>rFSQYWTxid4uY6noIglQTy3uPqzhO7/DeVT6apdp2afD5hzw7pgn2HGO
eYyd06gnveW772BEoS0qQqea/kayEmik6ZO0Lme9BjsEiGMOirM8cxp1
GH8ITQEOWX7ZyrruzJq3nbJtECSUGtxsdLh1+YdybfhgXVZ50zE4mGwT
jQc=</ns1:CipherValue>
            </ns1:CipherData>
            

            
            <ns1:ReferenceList>
               <ns1:DataReference URI="#M107teyC8vM4NGla"/>
            </ns1:ReferenceList>
         </ns1:EncryptedKey>
         

         
         <wsse:BinarySecurityToken 
xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" 
xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" 
EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0 #Base64Binary" 
ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3" 
wsu:Id="bst_OICTd53HaizQ97WY">
MIICLTCCAdcCEIaJVGuuAUqDWjfqd+LqWc8wDQYJKoZIhvcNAQEEBQAweTELMAkGA1UEBhMCVVM
xEDAOBgNVBAgTB015U3RhdGUxDzANBgNVBAcTBk15VG93bjEXMBUGA1UEChMOTXlPcmdhbml6YX
Rpb24xGTAXBgNVBAsTEEZPUiBURVNUSU5HIE9OTFkxEzARBgNVBAMTCkNlcnRHZW5DQUIwHhcNM
DkwMTE5MTM0NjU5WhcNMjQwMTIwMTM0NjU5WjB3MQswCQYDVQQGEwJVUzEQMA4GA1UECBYHTXlT
dGF0ZTEPMA0GA1UEBxYGTXlUb3duMRcwFQYDVQQKFg5NeU9yZ2FuaXphdGlvbjEZMBcGA1UECxY
QRk9SIFRFU1RJTkcgT05MWTERMA8GA1UEAxYIdWtwNzkyNjYwgZ8wDQYJKoZIhvcNAQEBBQADgY
0AMIGJAoGBAKSbrv5XD1sjEZxg8LApKmot1T5EqYTEo1J60hwev+3rXZAgjwXaoRx7Z5A2Ln35x
W6CJbhymiV/INfsm93VoJWKoN8g1/cEidoXnNfO+H/6WQPcrTtRuq1X9FrmKYWOAsmkNjX7ohdw
dKtWo+3twNgm+GhPrXi1U830e5PgBVX9AgMBAAEwDQYJKoZIhvcNAQEEBQADQQA/1ucLAAwUE5C
efd7PRk2dPvNX+idsDL+wntiRk2tH5WIxKVKytT64G6wtwLM4q+X2XDBfBHtPkJB1JYFZMyTo
</wsse:BinarySecurityToken>



         <dsig:Signature xmlns:dsig="http://www.w3.org/2000/09/xmldsig#">
            <dsig:SignedInfo>
            

            
               <dsig:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
               <dsig:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
               <dsig:Reference URI="#Timestamp_2aUNpfbuj63zIItu">
                  <dsig:Transforms>
                     <dsig:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
                  </dsig:Transforms>
                  <dsig:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
                  <dsig:DigestValue>FBrjakBhUyh83bJ+qXPavE0HyS4=</dsig:DigestValue>
               </dsig:Reference>
               

               
               <dsig:Reference URI="#Body_1XLlJAczORkplAwo">
                  <dsig:Transforms>
                     <dsig:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
                  </dsig:Transforms>
                  <dsig:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
                  <dsig:DigestValue>FXY8sR0rX7j5AoF/emb89cPI+94=</dsig:DigestValue>
               </dsig:Reference>



               <dsig:Reference URI="#unt_jGt3DAGS0A5sKDXi">
                  <dsig:Transforms>
                     <dsig:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
                  </dsig:Transforms>
                  <dsig:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
                  <dsig:DigestValue>ZvEVCrm9DiSQCEA2lL6TsCGCUZA=</dsig:DigestValue>
               </dsig:Reference>
               
Client certificate signature
               
               <dsig:Reference URI="#bst_OICTd53HaizQ97WY">
                  <dsig:Transforms>
                     <dsig:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
                  </dsig:Transforms>
                  <dsig:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
                  <dsig:DigestValue>s8yt4r8aHbAJ9+MVEd1MkwDy5Dc=</dsig:DigestValue>
               </dsig:Reference>
            </dsig:SignedInfo>
            

            
            <dsig:KeyInfo>
               <wsse:SecurityTokenReference 
xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" 
xmlns:wsse11="http://docs.oasis-open.org/wss/oasis-wss-wssecurity-secext-1.1.xsd" 
xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" 
wsse11:TokenType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile- 1.0#X509v3" 
wsu:Id="str_gm2kU2SAnassqTl7">
                  <wsse:Reference URI="#bst_OICTd53HaizQ97WY"
 ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile- 1.0#X509v3"/>
               </wsse:SecurityTokenReference>
            </dsig:KeyInfo>
         </dsig:Signature>
         

         
         <ns1:EncryptedData 
xmlns:ns1="http://www.w3.org/2001/04/xmlenc#" 
Encoding="UTF-8" Id="M107teyC8vM4NGla" 
MimeType="text/xml" 
Type="http://www.w3.org/2001/04/xmlenc#Element">
         

         
            <ns1:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes256-cbc"/>
            <ns1:CipherData>
               <ns1:CipherValue>
oUoMOMf12IAMEgZ01UuaHY+xYlKkYeaAIrZyuN5CEafQRco1HecGmaGcjtB5q
cNrRTNmrE9S18Z9K1oE6kwHKp3ZeYMp7KF/TbiqOwvKsy/+YpTQMS8gaqgbDN
XlRi0gG5/CZO6yqParDs//h3xvC9L1uazotzTGJofDWXaF/O8RC0qamD6yuoH
olV38na2mq28X0j44Eki4zJIkQg9q2ORj1juEE8RBt/zNuFKggThJrsmsixUj
AVRHXYA0exbwqUgWGoEEvw/AK4ZQooXfBXfIOsvPn4O4e515QpXhtM6cuqUEM
soGSIO/N+HT8oi4tOurXOAVCMZw6RonqBRvUPvLl2MQm3RIg4kBNFN7VIkqw4c
3jj+BbjGksKrmT3XX9jgypDDKJbW0OcOCTGYMhawQ2/wJMH/JazR55zwRLEzn8
8EIrLQ4SuGU7TN3pRNXdL3GdRxMZXF05mPT+6FLiSZ73kQLXxjlfQ9Bx4U3k2W
m+Pa0nMk0vb8Vj2DV5ZOB830f9C15jCIn7cvFZmYIdXSLOl1zrBJ1zGpdmVov
e1eKETKOyADGhWfE4Rt7mxPhLsUrhOlLIg4YMp+q3MIFEknoB8aBS0JPs9TSPvkQ2tk=
</ns1:CipherValue>
            </ns1:CipherData>
         </ns1:EncryptedData>
         <wsu:Timestamp 
xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" 
wsu:Id="Timestamp_2aUNpfbuj63zIItu">
            <wsu:Created>2009-01-20T14:35:09Z</wsu:Created>
            <wsu:Expires>2009-01-20T14:36:09Z</wsu:Expires>
         </wsu:Timestamp>
      </wsse:Security>
   </S:Header>



   <S:Body 
xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" 
wsu:Id="Body_1XLlJAczORkplAwo">
      <ns2:getStockQuote xmlns:ns2="http://webservice.stockbroker.com/">
         <ticker>ORCL</ticker>
      </ns2:getStockQuote>
   </S:Body>
</S:Envelope>

I will leave the analysis of the response message from the server as an exercise for the reader at this point; but I think you get the general idea of how the different parts of the configuration to hang together to create a message.

It is worth trying, as a sanity check, using a client key that is not trusted by the server. You can easily create such a key using the keytool command

keytool -genkey -keyalg RSA -keystore UnsignedClient.jks -storepass ClientKey -alias identity -keypass ClientKey -dname "CN=Client, OU=WEB_AGE, C=UK" -keysize 1024 -validity 1460

In this case when the client is run with the new keystore the server will response with an indignant response that the certificate could be verified. The stack trace you might see will look something like this the following, this could be fixed by adding the new client certificate to the server key store we configured earlier.

Exception in thread "main" javax.xml.ws.soap.SOAPFaultException: Security token failed
 to validate. weblogic.xml.crypto.wss.SecurityTokenValidateResult@137c90d[status: false]
[msg [
[
  Version: V3
  Subject: CN=Client, OU=WEB_AGE, C=UK
  Signature Algorithm: SHA1withRSA, OID = 1.2.840.113549.1.1.5

  Key:  Sun RSA public key, 1024 bits
  modulus: 10367272402852596114462785541689739976823783564655877706471495047404251812...
  public exponent: 65537
  Validity: [From: Tue Jan 20 16:18:56 GMT 2009,
               To: Sat Jan 19 16:18:56 GMT 2013]
  Issuer: CN=Client, OU=WEB_AGE, C=UK
  SerialNumber: [    4975f970]

]
  Algorithm: [SHA1withRSA]
  Signature:
0000: 65 F7 E6 13 7F 47 50 20   F5 DA 01 BE 44 39 B1 5E  e....GP ....D9.^
0010: 0E A3 23 CD 39 95 BB 3E   D2 CD 1E B8 A2 3E FC 74  ..#.9..>.....>.t
0020: F3 06 78 3C 2D 43 D8 26   E9 A3 2F 24 3F C2 A2 FF  ..x<-C.&../$?...
0030: 10 2D E1 ED 09 34 7F E8   B8 48 04 38 DE 4E B3 D9  .-...4...H.8.N..
0040: 37 27 F1 42 74 C1 A9 0C   61 E7 23 7F 09 A1 2F F1  7'.Bt...a.#.../.
0050: EC 0B 10 40 F8 DD C1 39   62 92 0A 62 96 D2 F8 5F  ...@...9b..b..._
0060: EF AF E3 93 C4 3E 62 D7   2F 5A 78 65 54 BD 28 4B  .....>b./ZxeT.(K
0070: DF 34 55 7D 8C 10 05 5A   DB 91 D8 A0 46 65 C3 16  .4U....Z....Fe..

]]
 at com.sun.xml.ws.fault.SOAP11Fault.getProtocolException(SOAP11Fault.java:190)
 at com.sun.xml.ws.fault.SOAPFaultBuilder.createException(SOAPFaultBuilder.java:122)
 at com.sun.xml.ws.client.sei.SyncMethodHandler.invoke(SyncMethodHandler.java:119)
 at com.sun.xml.ws.client.sei.SyncMethodHandler.invoke(SyncMethodHandler.java:89)
 at com.sun.xml.ws.client.sei.SEIStub.invoke(SEIStub.java:118)
 at $Proxy30.getStockQuote(Unknown Source)
 at com.stockbroker.webservice.BrokerServicePortClient.main(BrokerServicePortClient.java:107)
Process exited with exit code 1.

So this blog should have shown you have to understand policy names, attach policy to a class, configure a server with the correct key stores, create a client and some understanding on how the bit relate to the structures you will find in the resultant soap message. As I say before this is not a definitive tutorial; but perhaps at least a starting point for a further investigation.

Update 11 February: If you have been using this example to try to perform two way encryption you you will find that the client will fail to decrypt the message. This is because of a mistake in my original code, it has a trust manager that only trusts the server certificate:

// Setup the TrustManager to verify the signature on the returned message

requestContext.put(WSSecurityContext.TRUST_MANAGER,
                new TrustManager() {
     public boolean certificateCallback(X509Certificate[] chain,
                                        int validateErr) {
         // Check that the server cert matches
         boolean result = chain[0].equals(
                    serverCert);
         return result;
     }
 });

The failure message from weblogic wasn't particularly useful so I spend a lot of time making sure that the keys in the messages lined up.

Exception in thread "main" javax.xml.ws.soap.SOAPFaultException: weblogic.xml.dom.marshal.MarshalException: weblogic.xml.crypto.wss.WSSecurityException: weblogic.xml.crypto.encrypt.api.XMLEncryptionException: Unable to resolve encryption key for weblogic.xml.crypto.encrypt.api.EncryptedType{keyInfo=null, cipherData=CipherValue: 0i8YETKYNv6PKD9nZqikDDIYpBrqrfDORnLK+nyJiY9HBaE462+v/PCG0NCbO4kqyotFGPqMExSCZ4hYOGtR4nWseqoAWD7Z64SPKfNYv0ugRhTcGIsJ8kya1eJFOzNfwRFPSaalRzLDQ8j+Rl7yiw==, id='null', mimeType='null', encoding='null', encryptionMethod=EncryptionMethod: algorithm = http://www.w3.org/2001/04/xmlenc#aes256-cbc;} 

The fix for this was to replace the trust manager with one that either trusts everything, and returns true, or checks the client certificate. It is better to got for the latter.

List certificate = CertUtils.getCertificate(clientKeyStore,
  clientKeyStorePass,
  clientKeyAlias, "JKS");

final X509Certificate clientCert =
  (X509Certificate)certificate.get(0);


...

// Setup the TrustManager to verify the signature on the returned message

requestContext.put(WSSecurityContext.TRUST_MANAGER,
                new TrustManager() {
     public boolean certificateCallback(X509Certificate[] chain,
                                        int validateErr) {
         // Check that the server cert matches
         boolean result = chain[0].equals(
                    serverCert) || chain[0].equals(clientCert);
         return result;
     }
 });

I did think about fixing the code in the blog and saying no more about it, but I feel that you can learn more from people's mistakes as well as they successes. Thanks for Chris Muir for working through this one with me.

Monday, January 19, 2009

Viewing the Contents of WLS Policies in JDeveloper

If you want to know what is behind each WLS policy then in JDeveloper the simplest bet is to deploy your application. If you want to have a quick read before you decide which one to use you can convince the application navigator to display all the policies. you simply need to check "Show Libraries" in the application navigator and go to the relevant package.....

You can then open the policy file you are interested in like you would any other...

Monday, January 5, 2009

Some nice RESTful articles from InfoQ and Others

Just getting back into the swing of things and ended up with a nice long tab group of links. Figured I would write them down as they represent quite a nice introduction of the key concepts.

Thursday, December 18, 2008

Invoke a single port async service using JAX-WS and WS-Addressing

I was looking over the WSTF pages and found the use cases that include a single port reply to another address. I did wonder if you could safely invoke this using JAX-WS. So I decided to find out.

To make things simple I used the following hello world with addressing turned on. Note this is not a true asynchronous service as the rely is sent before the server returns with a 202 code; but it is close enough for the purposes of this presentation.

@Addressing
@WebService
public class Hello {

    @Action(input="http://project1/Hello/sayHello"
            output="http://project1/Hello/sayHelloReponse")
    public String sayHello(String name) {
        return name;
    }
}

So I published this service and then generated a client to another project. We need to create a suitable callback service so first of all you have to find the port SEI:

@WebService(wsdlLocation="HelloService.wsdl", targetNamespace="http://project1/",
  name="Hello")
@XmlSeeAlso(
  { project1.ObjectFactory.class })
public interface Hello
{
  @WebMethod
  @Action(input="http://project1/Hello/sayHelloRequest", output="http://project1/Hello/sayHelloResponse")
  @ResponseWrapper(localName="sayHelloResponse", targetNamespace="http://project1/",
    className="project1.SayHelloResponse")
  @RequestWrapper(localName="sayHello", targetNamespace="http://project1/",
    className="project1.SayHello")
  @WebResult(targetNamespace="")
  public String sayHello(@WebParam(targetNamespace="", name="arg0")
    String arg0);
}

Then I used Refactor->Duplicate to create a copy and converted the interface into a class. As you can see you need to turn the interface in-side out. The changes I made are:

  • Remove WSDL declaration in @WebService, a new one will be create for response part
  • Remove output action and replace with input with the output value
  • Make method one way
  • Add @Addressing modifier
  • Make the return value the only parameter, moving over any WebResult values to WebParam.

If would be really nice to have this done automatically when the usingAddressing tag is found in the WSDL, perhaps it would be possible to extend the web services generation tool to do this.

@WebService(targetNamespace = "http://project1/", name = "Hello")
@XmlSeeAlso( { ObjectFactory.class })
@Addressing
public class HelloCallback {
    @WebMethod
    @Action(input = "http://project1/Hello/sayHelloResponse")
    @RequestWrapper(localName = "sayHelloResponse",
                     targetNamespace = "http://project1/",
                     className = "project1.SayHelloResponse")
    @Oneway
    public void sayHello(@WebParam(targetNamespace = "", name = "return")
        String ret) {
        
        System.out.println(ret);
        
    }
}

And then the code is really quite simple, in this case we publish the endpoint and then invoke the service. Note we use the @OneWayFeature to populate the required WS-Addressing features, this is a bit naughty as it is part of the -RI and doesn't support faults. For the purposes of this blog though it makes the code a mite easier to read.

The other thing to note is that the call to sayHello returns a "null" object, you can use the MessageContext to check the response code if you want to confirm a 202; but otherwise the rest of the action is deferred to the callback class at this point.

public static void main(String[] args) {
    helloService = new HelloService();
    
    
    Endpoint e = Endpoint.publish(
        "http://localhost:7890/endpoint",
        new HelloCallback());
    

    WSEndpointReference replyTo = new WSEndpointReference(e.getEndpointReference());


    Hello hello = helloService.getHelloPort(new WebServiceFeature[] {
      new OneWayFeature(true, replyTo)});


    // This method will return null
    //
    
    Object ret = hello.sayHello("Bob");

    // Check the message response code
    //

    Integer responseCode = (Integer)     
        ((BindingProvider)hello).getReponseContext()
            .get(MessageContext.HTTP_RESPONSE_CODE);  
}

One final note is that you need to force the client to exit as Endpoint.publish create a non-daemon thread.