Just to note that I wont be moderating comments very frequently over the holidays. So don't worry if I don't post a comment right away, I will get around to them in the new year....
Happy Christmas and a Happy New Year for one and all.
Just to note that I wont be moderating comments very frequently over the holidays. So don't worry if I don't post a comment right away, I will get around to them in the new year....
Happy Christmas and a Happy New Year for one and all.
Well not 100% true, the first one did just do hello world to check it all worked. as you will have seen in previous posts; but this is the first time I have tried to use JAX-RS in anger.
So the problem I was trying to solve is that whilst our bug system allows you to do lots of static queries they don't give you a feel for what bugs have been updated when. So I wanted to put together some Atom feeds of recently updated bugs that I am associated with to give me a better overview. This seems like a job of JAX-RS.
Now the JAX-RS examples do use the Rome libraries; but I followed the example of this blog and simple generated a JAXB content model from the Atom Schema. This gave me a basis of my application; but I needed to work out what my resource URL looks like. My first guess had:
http://localhost:7101/bugfeed/reportedBy/gdavison
But to my eyes this didn't seem right as reportedBy is a property of the user, and I needed some room for expansion. Also it take some time to swap your thinking around from RPC/OO to REST style of thinking....
http://localhost:7101/bugfeed/user/gdavison/repotedBy
Having decided that I need to create my top level class. You will see that I also created a bug resource which is a simple redirection to the bug web site currently. It would also be possible to return computer readable xml of json objects depending on the content type asked for; but at the moment I am going to assume real human users.
Now you can match complex paths in one step with JAX-RS, for example "user/{username}/reportedBy" but I wanted more resource steps for later flexibility. So we just return a POJO which represents the user.
@Path("/") public class BugDBFeed { @Path("/bug/{bug_num}") @GET public Response getBug(@PathParam("bug_num") String bugNum) { return Response.temporaryRedirect( UriBuilder.fromUri("https://xxxx/query") .queryParam("bugNo",bugNum).build()).build(); } @Path("/user/{username}/") public UserResource getUser( @PathParam("username") String username) { return new UserResource(username); } }
There really isn't anything here, at the moment the user resource doesn't produce anything - it just represents part of the resource path in the URL. It does expose three sub paths which represent different ways I could be associated with a bug. Compare that with the "getBug" method previously where it has a @GET on it to say what HTTP method it accepts.
public class UserResource { private String username; public UserResource(String username) { super(); this.username = username; } @Path("/assigned") public UpdateResource getAssignedBugs() { return new UpdateResource(username, "programmer"); } @Path("/reportedBy") public UpdateResource getReportedByBugs() { return new UpdateResource(username,"rptd_by"); } @Path("/supportContact") public UpdateResource getSuuportedContactBugs() { return new UpdateResource(username,"support_contact"); } }
Now the next level of resource classes is where it becomes really interesting as we start to produce new content. The getBugsAtom method declares that it returns an xml type and JAX-RS will convert the JAXB classes in the background for you. You will notice that there are two parameters injected: the first in UriInfo which gives you a context to build uris that point to other resources; and the second is an optional query parameter on the feed to control the number of days you want the query to go back. You can inject quite a bit of information into parameters, even as far as details such as cookies.
UriInfo is a really powerful class for referencing other resources, in this case we are asking to the root of the application so that we can us that bug indirection used in the top level class. Notice like most object here they use the builder pattern to reduce the number of code steps required.
The rest of the code is straight forward SQL queries and creating the right POJO classes to later marshal into XML. JAX-RS does most of the work for you. I couldn't get the DataSource to inject properly, this might have been because I was only using Jersey 0.9; but most likely it required further integration into the server for this to work. No matter JNDI can do the same work.
public class UpdateResource { // @Resource(name = "jdbc/BugDS") // DataSource ds; private String username; private String property; public UpdateResource(String username, String property) { super(); this.property = property; this.username = username; } @GET @Produces("application/atom+xml") public Feed getBugsAtom( @javax.ws.rs.core.Context UriInfo info, @QueryParam(value = "days") @DefaultValue("10") int days) { try { ObjectFactory fact = new ObjectFactory(); Feed feed = fact.createFeed(); // List<Object> authorOrCategoryOrContributor = feed.getAuthorsAndCategoriesAndContributors(); PersonType personType = fact.createPersonType(); TextType name = fact.createTextType(); name.getContent().add(username); personType.getNamesAndUrisAndEmails().add(fact.createFeedTitle(name)); authorOrCategoryOrContributor.add(fact.createFeedAuthor(personType)); // Put in a title // TextType title = fact.createTextType(); title.getContent().add(info.getAbsolutePath().toASCIIString()); authorOrCategoryOrContributor.add(fact.createFeedTitle(title)); // Put some entries in // Connection otherConnection = getConnection("java:comp/env/jdbc/BugDS"); try { //Connection connection = dbConnection.getConnection(); OraclePreparedStatement prep = (OraclePreparedStatement)otherConnection.prepareCall("..."); try { prep.setStringAtName("nxuser", username.toUpperCase()); prep.setIntAtName("nxdays", days); // Last thirty days ResultSet rs = prep.executeQuery(); try { while (rs.next()) { // Entry entry = fact.createEntry(); authorOrCategoryOrContributor.add(entry); // Get data out of the columns String bugNumber = Integer.toString(rs.getInt(1)); String titleText = rs.getString(2); GregorianCalendar calendar = new GregorianCalendar(TimeZone.getTimeZone("PST")); Date date = rs.getTimestamp(3, calendar); DateTimeType updatedOn = fact.createDateTimeType(); String status = Integer.toString(rs.getInt(4)); // TextType entryTtitle = fact.createTextType(); entryTtitle.getContent().add(status + " " + titleText + " on " + date); entry.getAuthorsAndCategoriesAndContents() .add(fact.createEntryTitle(entryTtitle)); LinkType entryLink = fact.createLinkType(); UriBuilder toBuild = info.getBaseUriBuilder(); entryLink.setHref(toBuild.path("bug") .path(bugNumber).build().toASCIIString()); entry.getAuthorsAndCategoriesAndContents() .add(fact.createEntryLink(entryLink)); calendar.setTime(date); XMLGregorianCalendar updated = DatatypeFactory.newInstance().newXMLGregorianCalendar(calendar); updatedOn.setValue(updated); entry.getAuthorsAndCategoriesAndContents() .add(fact.createEntryUpdated(updatedOn)); } } finally { rs.close(); } } finally { prep.close(); } } finally { otherConnection.close(); } // return feed; } catch (Exception ex) { throw new RuntimeException(ex); } } private Connection getConnection(String dataSourceLocation) throws NamingException, SQLException { Connection conn = null; // Get a context for the JNDI look up Context ctx = new InitialContext(); // Look up a data source javax.sql.DataSource ds = (javax.sql.DataSource)ctx.lookup(dataSourceLocation); // Create a connection object conn = ds.getConnection(); return conn; } }
So after working with an playing with the service for a little while I was ready to try in Thunderbird where all my other feeds are. It turns out that at least 2.x doesn't like all Atom feed, so I decided that rather than try to find another reader that instead I would also produce a RSS stream. One of the cool things about JAX-RS is that you can very easily overload a give resource with different media types depending in the client accept headers.
I decided that I would much rather use XSL rather than write all that JAXB code again for RSS. So I dug up a link on the internet and started work. It turns out that the JAX-RS method can return a simple source object so all we really needed to do was to marshal the Atom class, transform and return. Thunderbird will now get a RSS feed which at least in my case it seemed to prefer.
@GET @Produces("application/rss+xml") public Source getBugsRSS( @javax.ws.rs.core.Context UriInfo info, @QueryParam(value = "days") @DefaultValue("10") int days) throws ... { // Get the atom version // Feed feed = getBugsAtom(info, days); // Jaxb it DOMResult atomModel = new DOMResult(); JAXBContext jc = JAXBContext.newInstance(ObjectFactory.class); jc.createMarshaller().marshal( feed, atomModel); // Transform it // Transformer t = TransformerFactory.newInstance().newTransformer( new StreamSource( UpdateResource.class.getResourceAsStream("atom2rss.xsl"))); DOMResult rssModel = new DOMResult(); t.transform( new DOMSource(atomModel.getNode()), rssModel); // Return RSS version // return new DOMSource(rssModel.getNode()); }
It is worth taking a quick look at the WADL view of the application one it was deployed. It am not sure why the last two paths are not correctly described but can get the general idea. I would be relatively easy to parse this and generate a client of some kind; but this isn't part of the JAX-RS project as yet.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <application xmlns="http://research.sun.com/wadl/2006/10"> <doc xmlns:jersey="http://jersey.dev.java.net/" jersey:generatedBy="Jersey: 0.9-ea 08/22/2008 04:48 PM"/> <resources base="http://xxxxx:7001/bugfeed/"> <resource path="/"> <resource path="/bug/{bug_num}"> <param xmlns:xs="http://www.w3.org/2001/XMLSchema" type="xs:string" style="template" name="bug_num"/> <method name="GET" id="getBug"> <response> <representation mediaType="*/*"/> </response> </method> </resource> <resource path="/user/{username}/"> <param xmlns:xs="http://www.w3.org/2001/XMLSchema" type="xs:string" style="template" name="username"/> <resource path="/assigned"> <method name="GET" id="getBugsRSS"> <request> <param xmlns:xs="http://www.w3.org/2001/XMLSchema" default="10" type="xs:int" style="query" name="days"/> </request> <response> <representation mediaType="application/xml+rss"/> </response> </method> <method name="GET" id="getBugsAtom"> <request> <param xmlns:xs="http://www.w3.org/2001/XMLSchema" default="10" type="xs:int" style="query" name="days"/> </request> <response> <representation mediaType="application/xml+atom"/> </response> </method> </resource> <resource path="/reportedBy"/> <resource path="/supportContact"/> </resource> </resource> </resources> </application>
Well that is the entire application, it is quite simple in that it doesn't put, update or delete resources; but I was trying to solve a problem not write a tutorial! (A nice tutorial is here.) I also haven't taken any care to map exception to anything more user friendly. This can be done easily with the use of @Provider/ExceptionMapper to convert you application specific error to a WebApplicationException instance. But for the moment the default 500 error will have to do.
Update 13 Feb '09 Whoops there was a typo in the @Produces annotation I had "xml+atom" rather than "atom+xml" and the same mix up for rss. I have fixed this typo now. The early version of Jersey I wrote this with didn't really mind; but 1.0.x onwards is more strict. In case you are interested the error message I got back from Jersey when requesting context type application/xml+atom was:
A message body writer for Java type, class org.w3._2005.atom.Feed, and MIME media type, application/xml+atom, was not found
The actual mime type wild card that would match JAXB elements is application/*+xml. Took a little while to work our the mistake; but it was something I have learnt from.
This blog represents the non-policy part of my demo at UKOUG'08 this year. In that scenario we had a simple loan application that made use of a "mocked" version of a credit rating service during developement and a more real service for production. (Mocked up using the HTTP Analyzer in this case) This following screen grab gives you an idea of the project structure:
If we ignore the deploy directory for now we can take a look at the code in LoanApprover. The key points to notice here is the @WebServiceRef which injects the endpoint from the web.xml and the callout method to provision security. The important thing here is that the new WSDL might have different security policy; but it is possible to write the code fairly generically to take this into account because of the way that the web logic client auto-configures. In this case the mock service had no security and the production service had WS-Security with plain username/password.
@WebService public class LoansApprover { /** * Credit rating service injected from web.xml **/ @WebServiceRef(name = "CreditRatingService") CreditRating creditRating; /** * @return Loan application with approval code if * approved. */ public LoanApprovalReponse approveLoan(LoanApplication la) { LoanApprovalReponse approvalReponse = new LoanApprovalReponse(); // Provision credentials // CredentialProvisioner.provisionPort(creditRating); // Start looking up credit rating ResponseratingReponse = creditRating.lookupRatingAsync( la.getSsid()); // Retrieve any customer records // ... // Process Credit rating try { int creditRating = ratingReponse.get(); if (creditRating > 30) { approvalReponse.setApprovalCode( UUID.randomUUID().toString()); } } catch (Exception e) { e.printStackTrace(); // Do nothing } return approvalReponse; } }
So the @WebServiceRef uses a name resource, defined some where in JNDI. In this case it is in web.xml for an EJB it would be in ejb-jar.xml. Lets take a quick look, note that because we specify the service class name the deployer runtime code will work out to inject the port to save you a little bit of typing.
<?xml version = '1.0' encoding = 'windows-1252'?> <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"> ... <service-ref> <service-ref-name>CreditRatingService</service-ref-name> <service-interface>com.somecreditrating.xmlns.rating.CreditRating_Service</service-interface> </service-ref> </web-app>
So in development the service class has all the hard coded references to the mock service deployed on localhost. For production we are going to change the WSDL that the proxy loads.
If you look at the project structure you will notice that there is an production.ear file in a rather novel directory structure. You will notice that the ear lives under the "app" sub dir and there is a "plan" directory. This structure will be created for you if you do something like change a policy on a deployed web service; but we need this all in place from the start. (Take a look at the weblogic documentation for more information on this structure)
Now you have to create a plan.xml, see this information on how to create one from the command line, but in the best Blue Peter tradition here is one I created earlier. The key parts are the variable definition and the assignment later on that uses xpath to create a new wsdl reference. Be carefull with the xpath expression as a space in the wrong place can cause the expression not to work. When deployed the proxy created for @WebServiceRef will read from this WSDL not the hard coded one.
<?xml version='1.0' encoding='UTF-8'?> <deployment-plan xmlns="http://www.bea.com/ns/weblogic/deployment-plan" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.bea.com/ns/weblogic/deployment-plan http://www.bea.com/ns/weblogic/deployment-plan/1.0/deployment-plan.xsd" global-variables="false"> <application-name>production</application-name> <variable-definition> <variable> <name>CreditRatingService</name> <value>http://www.somecreditrating.com/xmlns/rating?WSDL</value> </variable> </variable-definition> <module-override> <module-name>production.ear</module-name> <module-type>ear</module-type> <module-descriptor external="false"> <root-element>weblogic-application</root-element> <uri>META-INF/weblogic-application.xml</uri> </module-descriptor> <module-descriptor external="false"> <root-element>application</root-element> <uri>META-INF/application.xml</uri> </module-descriptor> <module-descriptor external="true"> <root-element>wldf-resource</root-element> <uri>META-INF/weblogic-diagnostics.xml</uri> </module-descriptor> </module-override> <module-override> <module-name>LoanApplication-LoanApprover-context-root.war</module-name> <module-type>war</module-type> <module-descriptor external="false"> <root-element>weblogic-web-app</root-element> <uri>WEB-INF/weblogic.xml</uri> </module-descriptor> <module-descriptor external="false"> <root-element>web-app</root-element> <uri>WEB-INF/web.xml</uri> <variable-assignment> <name>CreditRatingService</name> <xpath>/web-app/service-ref/[service-ref-name="CreditRatingService"]/wsdl-file</xpath> <operation>add</operation> </variable-assignment> </module-descriptor> <module-descriptor external="true"> <root-element>weblogic-webservices</root-element> <uri>WEB-INF/weblogic-webservices.xml</uri> </module-descriptor> <module-descriptor external="false"> <root-element>webservices</root-element> <uri>WEB-INF/webservices.xml</uri> </module-descriptor> <module-descriptor external="true"> <root-element>webservice-policy-ref</root-element> <uri>WEB-INF/weblogic-webservices-policy.xml</uri> </module-descriptor> </module-override> <config-root>D:\prom-demo\jdeveloper\mywork\LoanApplication\deploy\production\.\plan</config-root> </deployment-plan>
Now the deployment plan dir also allows you to add files to the classpath. In this case we have a simple properties file that provides a username and password to be later configured as a credential provider. (Do consider storing your password somewhere more safe) For completeness lets just check out the code that provisions the proxy with the username and password token. Nothing very special there.
private static Properties passwordStore; public static void provisionPort(Object port) { assert port instanceof BindingProvider; BindingProvider bp = (BindingProvider)port; // Get class and find the port name, for the moment // lets just cheat // Class proxyPort = port.getClass(); Class ifClass = proxyPort.getInterfaces()[0]; String portName = ifClass.getName(); // Do some clever lookup in a keystore // Pairusernamepassword = getCredentials(portName); if (usernamepassword!=null) { ClientUNTCredentialProvider credentialProvider = new ClientUNTCredentialProvider(usernamepassword.getLeft().getBytes(), usernamepassword.getRight().getBytes()); bp.getRequestContext().put( WSSecurityContext.CREDENTIAL_PROVIDER_LIST, Collections.singletonList(credentialProvider)); } } private static Pair getCredentials(String portName) { if (passwordStore==null) { passwordStore = new Properties(); URL password_properties = CredentialProvisioner.class.getResource( "password.properties"); if (password_properties!=null) { try { passwordStore.load(password_properties.openStream()); } catch (IOException e) { e.printStackTrace(); } } } // Get the packed version of the string // String packedVersion = passwordStore.getProperty(portName); if (packedVersion!=null) { String splitVersion[] = packedVersion.split("::"); assert splitVersion.length == 2; return new Pair (splitVersion[0],splitVersion[1]); } else { return null; } }
Unfortunatelly you cannot deploy this special directory structure from within JDeveloper, so you need to go into the weblogic console, Deployments->Install, but as you can see from the screen grab it will recognize the app/plan combo as a directory you can deploy:
And that is that, you application will deploy, read the new WSDL and start to use the external service.
This post builds on code in my previous blog so take a quick look at that for some context.
One of the problems with dealing with asynchronous responses is having to deal with correlating the responses. Now you could do this using the message ID from the original message; but this is a pain and not very application specific. Instead the WS-Addressing specification allows you to specify application specific reference parameters that are used in the response. This can be any XML element so you have a lot of control over what is sent back.
For the purposes of this blog lets create a simple header bean class to be used in the communication. It needs to be a JAXB bean with @XmlRootElement and a namespace. (If you leave the namespace blank you get a null pointer exception somewhere deep within JAX-WS-RI):
import javax.xml.bind.annotation.XmlRootElement; @XmlRootElement(namespace="http://www.example.org") public class HeaderBean { String property; public void setProperty(String property) { this.property = property; } public String getProperty() { return property; } }
Let modify the callback class from the previous blog to read the header value. In this case I am using @WebParam(header=true) for the convenience of mapping the header to java.
@WebService(targetNamespace = "http://project1/", name = "Hello") @XmlSeeAlso( { ObjectFactory.class }) 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, @WebParam(header = true, targetNamespace = "http://www.example.org", name = "headerBean") HeaderBean hb) { System.out.println(ret); } }
Now we modify the client to include the extra header. Unfortunately WSEndpointReference is in terms of Element so we have a little bit of extra code to marshal the bean in to XML. In this simple case it might have been easier to just write the XML directly; but I would see it is worth the extra work for more complicated constructs:
public static void main(String[] args) throws JAXBException { helloService = new HelloService(); Endpoint e = Endpoint.publish("http://localhost:7890/endpoint", new HelloCallback()); HeaderBean n = new HeaderBean(); n.setProperty("applicationModuleID"); DOMResult result = new DOMResult(); JAXBContext.newInstance(HeaderBean.class).createMarshaller().marshal(n, result); WSEndpointReference replyTo = new WSEndpointReference(e.getEndpointReference((Element)result.getNode().getFirstChild())); Hello hello = helloService.getHelloPort(new WebServiceFeature[] { new OneWayFeature(true, replyTo) }); // This method will return null // Object ret = hello.sayHello("Bob"); }
So here is the outgoing message, note that the extra header information is passed in the reference parameters field. You need to consider whether this information need to be encrypted in some way.
<?xml version = '1.0' encoding = 'UTF-8'?> <S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"> <S:Header> <To xmlns="http://www.w3.org/2005/08/addressing">http://localhost.localdomain:7101/Application1-Project1-context-root/HelloPort</To> <Action xmlns="http://www.w3.org/2005/08/addressing">http://project1/Hello/sayHelloRequest</Action> <ReplyTo xmlns="http://www.w3.org/2005/08/addressing"> <Address>http://localhost:7890/endpoint</Address> <ReferenceParameters> <headerBean xmlns:ns2="http://www.w3.org/2005/08/addressing" xmlns="http://www.example.org" xmlns:wsa="http://www.w3.org/2005/08/addressing"> <property>applicationModuleID</property> </headerBean> </ReferenceParameters> <Metadata/> </ReplyTo> <MessageID xmlns="http://www.w3.org/2005/08/addressing">uuid:3b9e7b20-3aa0-4a4a-9422-470fa7b9ada1</MessageID> </S:Header> <S:Body> <ns2:sayHello xmlns:ns2="http://project1/"> <arg0>Bob</arg0> </ns2:sayHello> </S:Body> </S:Envelope>
And here is the response:
<?xml version = '1.0' encoding = 'UTF-8'?> <S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"> <S:Header> <To xmlns="http://www.w3.org/2005/08/addressing">http://localhost:7890/endpoint</To> <Action xmlns="http://www.w3.org/2005/08/addressing">http://project1/Hello/sayHelloResponse</Action> <MessageID xmlns="http://www.w3.org/2005/08/addressing">uuid:9d0be951-79fc-4a56-b3e6-4775bde2bd82</MessageID> <RelatesTo xmlns="http://www.w3.org/2005/08/addressing">uuid:3b9e7b20-3aa0-4a4a-9422-470fa7b9ada1</RelatesTo> <headerBean xmlns:wsa="http://www.w3.org/2005/08/addressing" wsa:IsReferenceParameter="1" xmlns:ns2="http://www.w3.org/2005/08/addressing" xmlns="http://www.example.org"> <property>applicationModuleID</property> </headerBean> </S:Header> <S:Body> <ns2:sayHelloResponse xmlns:ns2="http://project1/"> <return>Bob</return> </ns2:sayHelloResponse> </S:Body> </S:Envelope>
The reason I am looking into this is that we were wondering what the best way would be for a ADF JSF page to invoke a BPEL service and properly channel the response back to the originating AM. Using the reference parameters makes this job much easier as you can more easily relate it to the domain.
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:
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.
So I little while ago someone asked me how to using a weblogic JAX-WS client to connect to a service that required plain text WS-Security username / token headers. It turns out that non of the default policies that come with weblogic will work in this configuration because they all have a sensible level of security applied. This doesn't help when trying to deal with an older system so in the end I modified the existing Https username token policy and came up with this file:
<?xml version="1.0"?> <wsp:Policy xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy" xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200512" > <sp:SupportingTokens> <wsp:Policy> <sp:UsernameToken sp:IncludeToken="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200512/IncludeToken/AlwaysToRecipient"> <wsp:Policy> <sp:WssUsernameToken10/> </wsp:Policy> </sp:UsernameToken> </wsp:Policy> </sp:SupportingTokens> </wsp:Policy>
Now there is a lot on the weblogic documentation site about configuring security; but it took a while to absorb into something that worked. (My fault I guess) The most convenient option is to place the policy file in the "policies" sub dir under WEB-INF. So you ended up with a project that looks kinda like this:
You web service can then just refer to the policy using the "policy:" url as you would the built in policies:
package project1; import javax.jws.WebService; import weblogic.jws.Policies; import weblogic.jws.Policy; @WebService @Policies( { @Policy(uri = "policy:usernametoken.xml") } ) public class Hello { public String sayHello(String name) { return "Hello " + name; } }
In JDeveloper at least you have to type this by hand because the custom policy button is very broken in the current released version of JDeveloper. This we hope to address at some point in the future. This is all that is required to set up the service to use the default credential providers. Check out the link above for more information on configuring this.
So if you need to actually invoke this service you can skip some steps if the policies are advertised in the WSDL. Fortunately they are by default so when the JAX-WS client starts it will parse the polices from the WSDL and set itself up to expect the correct configuration to work. Now you might expect BindingProvider username / password properties to be used here; but instead you need to configure a credential provider as follows. Note there is no reference to the policy here.
public static void main(String[] args) { helloService = new HelloService(); Hello hello = helloService.getHelloPort(); // Add your code to call the desired methods. List<CredentialProvider> credProviders = new ArrayList<CredentialProvider>(); String username = "weblogic"; String password = "weblogic"; CredentialProvider cp = new ClientUNTCredentialProvider(username.getBytes(), password.getBytes()); credProviders.add(cp); Map<String, Object> rc = ((BindingProvider)hello).getRequestContext(); rc.put(WSSecurityContext.CREDENTIAL_PROVIDER_LIST, credProviders); hello.sayHello("Bob"); }
In some cases you might have a WSDL that for various reasons doesn't publish the policies as you get them through some other side channel. This is fortunately relatively easy to fix with the use of the ClientPolicyFeature as shown in the next code snippet:
public static void main(String[] args) { helloService = new HelloService(); ClientPolicyFeature cpf = new ClientPolicyFeature(); InputStream asStream = HelloPortClient.class.getResourceAsStream("usernametoken.xml"); cpf.setEffectivePolicy(new InputStreamPolicySource(asStream)); Hello hello = helloService.getHelloPort( new WebServiceFeature[] {cpf}); // Add your code to call the desired methods. List<CredentialProvider> credProviders = new ArrayList<CredentialProvider>(); String username = "weblogic"; String password = "weblogic"; CredentialProvider cp = new ClientUNTCredentialProvider(username.getBytes(), password.getBytes()); credProviders.add(cp); Map<String, Object> rc = ((BindingProvider)hello).getRequestContext(); rc.put(WSSecurityContext.CREDENTIAL_PROVIDER_LIST, credProviders); hello.sayHello("Bob"); }
And that is it, quite simple once I got used to the weblogic way of working.
Really strange thing, every time I re-open my VNC session I find that characters have appeared while I was disconnected. Today I found:
9,.2abdhimnpqstuxy
Not quite sure that it is trying to tell me though...
Unfortunately the Maven extension for JDeveloper still hasn't seen the light of day; but I was wanting to work with the source for Jersey so I need to get something working otherwise getting all the dependencies by hand would be a nightmare. For Maven 1.x you used to be able to just run "jdev:jdev" to generate projects and workspaces in much the same way "eclipse:eclipse" does the same for erm, well Eclipse.
For 2.x there is no easy to use command in the central repositories, but you can use a plugin from the "myfaces" to generate JDeveloper 10.1.3 project. For of all you need to check out the myfaces build extensions from:
svn checkout http://svn.apache.org/repos/asf/myfaces/trinidad-maven/\ trunk/maven-jdev-plugin maven-jdev-plugin -r 711393
If you don't use the revision modifier then you might get the tip which in my case didn't have all the dependencies published yet. You can try a more recent version if you like, let me know how you got on. Then when you have the source just perform the "mvn install" step to put this plugin in your local repository. (Under ~/.m2/repository on my Linux box)
You can now run the command "mvn -Djdev.release=11.1.1.0.0 org.apache.myfaces.trinidadbuild:maven-jdev-plugin:jdev" in the root of your project and you get a project file for each sub pom and a workspace to contain them all.
I couple of items of note:
Having said that this is a good first step to get working. You can use "Tools->External Tools" so you can run Maven commands from inside of JDeveloper.
I have uploaded the slide set from UKOUG'08 for a service called SlideShare which is useful as blogspot wont let you just upload those files yourself. Not so much detail in the testing presentation, that was mostly demos, but there are some code snippets in the promotion presentation that could be useful.
If you want to store a password in your deployed application, perhaps for an external service you need to make sure it is saved somewhere safe. Internally at oracle we are never allowed to store a password in plain text, ideally it has to be stored somewhere encoded to prevent a idle HD search from finding it. This made me wonder how I could store a simple character password in a keystore for later use to invoke a remote web service.
Keys are complicated beasties as you can see from the number of sub classes in java. In the most part they are going to be public/private key pair which are the mainstay of the internet. But if you just want to store a simple password you need to create a SecretKey.
Now the complicated this is that you have to create a key spec, which is transparent and create new opaque secret key from it for storage in the keystore. In case you are wonder PBE stands for "Password Based Encryption" which is the simplest type I could find in the security spec that would suit my needs.
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBE"); SecretKey generatedSecret = factory.generateSecret(new PBEKeySpec( password));
It turns out that the default "JKS" KeyStore type will not allow you to store a SecretKey. So instead you need the code to create the right kind. It might look something like this:
KeyStore ks = KeyStore.getInstance("JCEKS"); ks.load(null, keyStorePassword); PasswordProtection keyStorePP = new PasswordProtection(keyStorePassword); SecretKeyFactory factory = SecretKeyFactory.getInstance("PBE"); SecretKey generatedSecret = factory.generateSecret(new PBEKeySpec( password)); ks.setEntry(key, new SecretKeyEntry( generatedSecret), keyStorePP); ks.save(…, // Output stream for keystore keyStorePassword);
Note that you can't just add PBEKeySpec to a keystore because while it is a "Key" it is not of type "SecretKey" so you have to convert. When loading the password out from the keystore you need the SecretKeyFactory again to convert the SecretKey back into the PBEKeySpec that you can extract the password from.
KeyStore ks = KeyStore.getInstance("JCEKS"); ks.load(…, // InputStrweam to keystore keyStorePassword); SecretKeyFactory factory = SecretKeyFactory.getInstance("PBE"); SecretKeyEntry ske = (SecretKeyEntry)ks.getEntry(key, keyStorePP); PBEKeySpec keySpec = (PBEKeySpec)factory.getKeySpec( ske.getSecretKey(), PBEKeySpec.class); char[] password = keySpec.getPassword();
Now in general use the JCEKS keystore is far more secure than the default JKS format. The reason it is not the default is back in the old days they encryption was treated as a munition. I think this is only a problem for a few black listed countries now - although this restriction seems a bit pointless given the reach of the internet.
One of the more useful things about web services endpoint is normally you can stick "?WSDL" on the end and get a description of the service. Now for rest full services you find that WSDL does work to described resource based services so sun came up with the WADL language.
The Jersey implementation will automatically generate this description for you simply GET application.wadl from the root of your service, here is an example for a simple hello world example: (More complicated example)
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <application xmlns="http://research.sun.com/wadl/2006/10"> <doc xmlns:jersey="http://jersey.dev.java.net/" jersey:generatedBy="Jersey: 0.9-ea 08/22/2008 04:48 PM"/> <resources base="http://localhost:7101/Application1-Project1-context-root/"> <resource path="/helloworld"> <method name="GET" id="getClichedMessage"> <response> <representation mediaType="text/plain"/> </response> </method> </resource> </resources> </application>
You can also request the WADL for a particular resource; but you have to use the OPTIONS method rather than a GET, here is a quite screen grab in the HTTP Analyzer. Annoyingly enough it doesn't use pretty formatting:
It is possible to use this description to generate a java client; but this is part of Jersey rather than part of the JAX-RS standard to be aware it is more subject to change.
Update: For those in the MS world the WCF REST framework uses a /help postfix to generate similar information. In a human rather than computer readable format from what I can see.
So far today I appear to have a demo Jinx. In the SOA presentation I managed to pick a broken BPEL service so was unable to get a response back. In the JDeveloper SCM presentation there was no network connection for me in the room so I ended up having to sit in the stairwell some distance from the room. Then it went a bit Pete Tong in the room and I was left outside as a confused bunny. Still people came out of that one smiling!
Still I have the mulit-teir webservice deployment in my presentation this afternoon, what could possible go wrong...
Hiding out in the Speakers lounge now, the trade floor was full or lots of very bored sales people, many having Wii tournaments. Feel bad when they find out I work for Oracle so perhaps am not the best person to talk to. I did notice that the RittmanMead stand has the best beer fridge on the floor. Most Sci-Fi technology goes to KeyWay who were showing a Flash card with R/W bandwidths of 500Mbit +. Parrallel writes to 22 flash chips whifch is very nice.
Right well no presentation for a little while, better delete some email. I am not on until 17:30 so goodness knows how many people that will net given the general feeling that everybody has already gone home. But then I have an hour or so in the Xmas market until I get the train home.
So first day at UKOUG'08, just in the speakers loung waiting for the entertainment to start. The main impression of the day is just how quiet it seems compared to last year. Although I can say for sure there were also quite a few more cancellations from speakers. Still those that have made it here seem to be in good spirits.
Our testing presentation went well today, relatively full room and people seem to be really interested in what we had to say. No takers to come and talk to us in the Oracle lounge afterwards, I don't think anybody has taken this up for any of the bg O speakers, but otherwise the feedback was good. One demo failure; but we considered this a sucess when testing so many bits and bobs. We overan a bit but that was because both mysef and Goeff were really quite relaxed and therefore took more time than we really had to talk.
Prep for tommorrows presentation is going well, although we are havig to rely on a machine back in the office for the SOA demo which has some risks. Still should be okay.
Last thing the RittmanMead chaps have been nice enough to leave some beer in the speakers lounge. Unforunately it is a little bit flowery for my liking. See we have a free beer token for the 25th aniversary of UKOUG that starts soon. There is some Jazz laid on and if Susan Duncan had anyting to to with it we should be in for a treat.
Right I hear the ents being anounced....
So being a busy developer I wont get up to the conference until Wednesday morning and the train wont be there until 11ish; but then I have a pretty hectic schedule of presentations:
Wednesday 13:20-14:20 : How the Oracle JDeveloper team test JDeveloper. Co-presenting this with Geoff Waymark and will cover our usage of both Abbot and Selenium to test the "hard bits". I reckon this is the one mostly likely to have a big fat demo failure in it!
Thursday 10:45-11:30 : Integrating your J2EE Application with SOA. Only doing the demo here, a little bit of asynchronous web service consumption.
Thursday 11:55-12:40 : Who moved my Code? Team Development in JDeveloper. I am being the "Developer" in a demo, should be good as Susan is a lot of fun.
Thursday 17:30-18:15 : From developer to production, promoting your webservices. This presentation will look at how you take the different parts of a webservice application and move it from the developers machine to a production environment. Will cover policies and deployment plans.
Hopefully I will get to see some other talks, although I suspect that I might be busy in the speakers lounge putting the finishing touches to the last presentation.
I was noodling around with deployment plans in weblogic and wanted to just it was doing what I expected by making it fail. I was a little supprised when I saw this error message....
Until I remembered that I had change the URL for an outgoing web service to "http://idiot". The full text of the error message was java.net.UnknownHostException : idiot. But it did make me smile in this form.
This isn't a problem necessarily with JDeveloper as such, but running JDeveloper makes this much more likely to happen because of the number of files it deals with. The "gam_server" which monitors file system changes can sometimes go, and I am sorry to use a technical term, "go mental" an eat up at nearly 100% of a CPU. (Luckily it is single threaded so it doesn't got to the dreaded 200%)
Turn out you can ask gam to be a little less aggressive by creating a .gaminrc file in your home directory. Mine looks like:
fsset nfs poll 10 fsset ext3 kernel
This tells gam_server to poll at 10 second intervals for nfs mounts but use the kernel for the main file system. This seems to help a lot on my machine as most of our source control system is via nfs mounts. I very rarely get the gam of death.
More information on this configuration file can be found here.
In JDeveloper the web service proxy wizard for JAX-WS will recognize when a service is asynchronous and offer to generate both the invoking proxy and a service implementation to deal with the response. The problems is that the proxy generator in JDeveloper assumes that version of WS-Addressing is going to be Final (2005) or Member Submission (2004) and that the headers are implicit. By default BPEL 10.1.3 uses the 2003 Draft version of WS-Addressing and has explicit headers. This means you are going to have to do a bit more work to make it work with JAX-WS.
This examples makes use of the AsyncBPELService that comes with the Oracle BPEL server. All it does is echo a loan request back to the user. The same steps would work with any other asynchronous example - AmericanLoan for example.
So if you already know the WSDL url use that. I cheated a little bit and created a WSIL connection in the "Resource Palette" to "http://xxxxx:8889/inspection.wsil" and navigated to the right BPEL service from there. Either way bring up the web service proxy wizard and accept all of the defaults.
An inviting client source file example will show first, but lets clean up the callback service before we do this. In this case it is "AsyncBPELServiceCallbackImpl.java". You of course need to put some code in there; but the println we generate by default is enough for this example. As I said before the WS-Addressing header is an explicit parameter so in the two methods that get generated you need to replace:
// get the messageId to correlate this reply with the original request HeaderList headerList = (HeaderList)wsContext.getMessageContext().get(JAXWSProperties.INBOUND_HEADER_LIST_PROPERTY); Header realtesToheader = headerList.get(WS_ADDR_VER.relatesToTag, true); String relatesToMessageId = realtesToheader.getStringContent(); System.out.println("RelatesTo message id: " + relatesToMessageId); With: System.out.println("RelatesTo message id: " + RelatesTo.getValue());
Right lets go back to the client class "AsyncBPELServicePortClient.java" and for a change lets do a JSE client for the service and publish and endpoint as the first thing we do in the main method:
// publish the endpoint // Endpoint e = Endpoint.publish( "http://xxxx:7001/response", new AsyncBPELServiceCallbackImpl());
Although not the most direct way for this example you can use the endpoint to create the WSEndpoint object directly, so you can make this minor fix. This is more useful in the normal '2005 and '2004 cases.
WSEndpointReference replyTo = new WSEndpointReference("http://......", WS_ADDR_VER); With: WSEndpointReference replyTo = new WSEndpointReference(e.getEndpointReference(), WS_ADDR_VER);
We then comment out the next block because it generates headers in the wrong WS-Addressing version, then we put in place the right code to invoke the service:
// Add your code to call the desired methods. LoanApplicationType applicationType = new LoanApplicationType(); applicationType.setCarModel("207"); applicationType.setCarYear("1"); applicationType.setCreditRating(2); applicationType.setCustomerName("Bob"); applicationType.setEmail("bob@bobo.com"); applicationType.setLoanAmount(1000d); applicationType.setSSN("143134-134-135--1345"); // Set the reply to address to match that of the service // EndpointReferenceType replyToType = new EndpointReferenceType(); AttributedURI replyToTypeURI = new AttributedURI(); replyToTypeURI.setValue(replyTo.getAddress()); replyToType.setAddress(replyToTypeURI); // Set the message id AttributedURI messageId = new AttributedURI(); messageId.setValue(uuid); asyncBPELService.initiate(applicationType, replyToType, messageId);
And that is it really, the only thing to note is that you need to do System.exit(...) at some point as the Endpoint method starts on a non daemon thread. Just don't do it in the onResult method otherwise the BPEL service might record an error if the communication is not completed properly.
As UKOUG'08 approaches I have finally reached the point where fear of not starting on my presentation(s) has override the loathing I feel when I open power point. Still spend a good few tens of minutes sketching out the testing presentation with Geoff this afternoon so onto a good start.
Looks like I could be taking part in anything from 1-4 presentations include the one about testing I have previously talked about. More information on those as they become more clear.
Now since I have started one presentation, time to procrastinate... I wonder if they have any more cakes in the restaurant......
Having come from the world of OC4J where enabling https required the sacrifices of your first born: it is nice to know that in weblogic you can start working with just a tick of a box. (For the internal JDeveloper instance http://localhost:7101/console -> Servers -> DefaultServer(Admin) -> SSL Listen Port Enabled)
It is worth knowing that to make this trick happen weblogic generates a new DemoIdentity on install that is unique to your machine. The key in this is then used to set up of the https channel.
If you are connecting to the server you need to know where the key stores live, so here is a table with all the default values in:
Property | Value |
Trust store location | %ORACLE_HOME%/weblogic/wlserver_10.3/ server/lib/DemoTrust.jks |
Trust store password | DemoTrustKeyStorePassPhrase |
Key store location | %ORACLE_HOME%/weblogic/wlserver_10.3/ server/lib/DemoIdentity.jks |
Key store password | DemoIdentityKeyStorePassPhrase |
Private key password | DemoIdentityPassPhrase |
Most of the time you will be using the trust store to talk to the server. (Generally by passing in -Djavax.net.ssl.trustStore=.../DemoTrust.jks to the java process is enough)
If you are trying to configure the http analyzer in JDeveloper to run with https you will run into a problem as it assumes that the keystore and private key password are the same. (This should be fixed in a future version of JDeveloper if all goes well) This is not the case with the weblogic DemoIdentity.jks store. You workaround this is to use the "keytool -importkeystore" command to import the DemoIdentity in a keystore where both password are the same. This would look something like:
keytool -importkeystore -srckeystore .../DefaultIdentity.jks -srcstorepass DemoIdentityKeyStorePassPhrase -srcalias demoidentity -srckeypass DemoIdentityPassPhrase -destkeystore .../server.jks -deststorepass -deststorepass welcome -destalias demoidentity -destkeypass welcome
Worth mentioning again now 11 is final production as this is a really useful feature that many people don't find for a long time.
Basically it allow the user to control what is displayed by the debugger, up to and including the invocation of accessor methods.
A really nasty bug that made it in to the boxer release is the total failure if you mix ADF and web services in the same application. Fortunately we have a patch for this now; but it is not obvious how you might go about applying this if your are not familiar with the bea smart update tool.
First of all you need a bea account, you can get this from support.bea.com. I am told that you wont need a support license for this particular patch. Then you need to go to %ORACLE_HOME%/utils/bsu and run either bsu.sh or bsu.cmd depending on your platform.
When you are logged in make select the "JDeveloper" installation that you want to patch. Don't confuse this other plain "WebLogic" installs you might have hanging around.
Then make sure the "Get Patches" tab is selected and select the menu item Patches -> Retrieve Patch. In this case the patch ID is "RFRS" and the passcode "VGJ16G15". Download the patch, just agree to any dialogs that come up.
Now you can go to the "Manage Patches" tab to find the patch you have just downloaded and press the apply icon.
The patch should now move to the "Default" table and is now ready to test. Restart WebLogic | JDeveloper and hopefully the problem will go away.
In case google is watching you expect the following stack trace when deploying a web service if this patch is not applied:
Target state: deploy failed on Server srg [java] java.lang.NullPointerException [java] at weblogic.wsee.wsdl.WsdlTypes.collectNamespaces(WsdlTypes.java:213) [java] at weblogic.wsee.wsdl.WsdlTypes.collectNamespaces(WsdlTypes.java:229) [java] at weblogic.wsee.wsdl.WsdlTypes.collectNamespaces(WsdlTypes.java:229) [java] at weblogic.wsee.wsdl.WsdlTypes.parse(WsdlTypes.java:151) [java] at weblogic.wsee.wsdl.WsdlDefinitions.parseChild(WsdlDefinitions.java:520)
The first production release of the reference implementation of Jersey in now out there to play with. Previous notes on weblogic / jdeveloper still apply as far as I can tell.
Warning, this is a workaround and I give absolutely no warranty as to the results of trying this. Only use this if you really need a particular extension in order to move forward. It might may your JDeveloper to become unstable and cause your pets to come out in a rash. Right health and safety over.....
Those of you how are moving up to JDeveloper from 10.1.3 will notice that some of the extensions you know and love are not yet available in 11. This is mostly likely because the extension developer has been conservative in there bundle definition to restrict the usage in future versions. The good news is that that APIs for JDeveloper were frozen in 10.1.3 and for most part well written extensions should work. (You will note that the ones I have worked on will turn up in the center already as I tend to be more pragmatic about such things.)
You can work around this though with a little bit of work. First of all we need to capture the traffic between JDeveloper and the update center. To do this start the the HTTP Analyzer in JDeveloper. (View->HTTP Analyzer, Then press the green start start button) Then run the update tool Help->Check for Updates and proceed to the page you would normally see you extensions on. You can press cancel now as we have captured all the important information.
In my build the most important request goes to "http://www.oracle.com/technology/products/jdev/101/update/exchange.xml". So do a quick search of the response text, and yes you do have to copy the text to another editor to do this I have logged a bug for this, to find the extension you want. In my case I needed the spring extension and found this:
<u:update id="oracle.jdeveloper.spring2"> <u:name>JDeveloper Spring 2.5 Support</u:name> <u:version>08.01.09.01</u:version> <u:author>Oracle</u:author> <u:author-url>http://www.oracle.com/technology/products/jdev/htdocs/partners/addins/exchange/spring/spring-extension.html</u:author-url> <u:description> Adds support for creating and editing Spring 2.5 bean defintions. This addin will create the Spring 2.5.1 library and register the relevant XSDs and DTDs with the IDE to provide a productive editing experience for Spring definitions. </u:description> <!--u:post-startup-hook>oracle.jdevimpl.spring.addin.SpringAddinInstaller</u:post-startup-hook--> <u:requirements> <u:requires-extension id="oracle.jdeveloper" minVersion="10.1.3.36.73" maxVersion="10.1.3.9999" /> </u:requirements> <u:bundle-url>http://download.oracle.com/otn/java/jdeveloper/10131/extensions/JDeveloperSpring2.zip</u:bundle-url> </u:update>
Note the "requires" entry is the one that is causing problem. So we ignore this and just download the related zip file and place the contents in .../jdeveloper/jdev/extensions/ directory. This will not run any installer that comes with the extension; but should work in most cases.
In the spring case though you need to modify the name of the install directories. It appears that the long version numbers aren't liked by the more recent build of JDeveloper. You can discover this by looking at the libraries defined by the extension, Tools->Manage Libraries->Spring 2.5, and seeing where they are trying to find the jar files. In this case it seems we have to rename the extension jar and directory from "oracle.jdeveloper.spring2.08.01.09.01" to "oracle.jdeveloper.spring2.8". Now in theory you shouldn't need the version numbers at all; but but removing them didn't work in this case.
Next time you start JDeveloper you should find that in most cases the extension will work without problems. Even if you have an issue this should tide you over until the extension developer gets time to put another release out. When they do you may have to manually delete the extension before trying to install the new true extension from the update center. Also please disable this extension before reporting bugs on the forum to work out whether they are causing problems.
Right now I need to go back and figure out what I was working on and try to remember why I wanted the spring extension!
Good overview of JAX-RS 1.0 and hopefully in a few days one for those extensions that are part of Jersey and not part of the specification yet.
Finally you can see what we have been up to all these years in a full production version. It appears that the issues with the download page that were seen earlier today have been fixed.
This version is built on top of the very new Weblogic 10.3 so you get more toys for your money. Collateral for 11 should start to pop up on OTN in due course.
One final point, JDeveloper now comes as an installer. So run the jar file, don't try to unzip it.
Based on a quote from Paul Sandoz JAX-RS / Jersey goes production on the 13th. As noted in my previous post there are still some work aroundable issues; but it will be nice to have a library that is claimed to be production quality.
Please check the bottom of this post for the latest information.
Now that JSR311 has been approved I thought it was time to have a quick go at running a simple hello world service using JDeveloper / Weblogic. These instructions should give you enough of a hint though if you are using Ant/Eclipse or some other tool to build your services. Turns out for weblogic there a few issues to be work around first before you will get a running service.
First of all you need to download some jar files, not quite sure why but the jersey folks seem to go about of there way to make things harder for you if you are not using Maven. You need to follow the "Download" link from the main jersey page. (I can't provide a hyperlink as this page moves with each release, this instructions are based on 0.9). The minimum list of jars you need to run simple services is:
In JDeveloper create a new library against a project with these three jar files in them. Make sure you mark the library as "Deploy Always". For the moment you wont find these jars in a j2ee server.
In the project you want to create you RESTful services in you need to create a web.xml that contains a reference to the root JAX-RS servlet. This wont be required when we get JEE6; but it doesn't take too much effort to put the extra entries in place. Once you have finished your web.xml will look like:
<?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> <session-config> <session-timeout>35</session-timeout> </session-config> <mime-mapping> <extension>html</extension> <mime-type>text/html</mime-type> </mime-mapping> <mime-mapping> <extension>txt</extension> <mime-type>text/plain</mime-type> </mime-mapping> <servlet> <display-name>JAX-RS Servlet</display-name> <servlet-name>jersey</servlet-name> <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class> <init-param> <param-name>com.sun.jersey.config.property.resourceConfigClass</param-name> <param-value>com.sun.jersey.api.core.PackagesResourceConfig</param-value> </init-param> <init-param> <param-name>com.sun.jersey.config.property.packages</param-name> <param-value>project1</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>jersey</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> </web-app>
You need to edit the value for "com.sun.jersey.config.property.packages" to contain a ';' separated list of package names to search. The default configuration class that scans the entire classpath doesn't work on weblogic. Hopefully they will fix this before 1.0 comes out.
So let quickly create a hello world service so we can test things out:
1 package project1; 2 3 import javax.ws.rs.Path; 4 import javax.ws.rs.GET; 5 import javax.ws.rs.Produces; 6 7 8 @Path("/helloworld") 9 public class HelloRS { 10 11 // The Java method will process HTTP GET requests 12 @GET 13 // The Java method will produce content identified by the MIME Media 14 // type "text/plain" 15 @Produces("text/plain") 16 public String getClichedMessage() { 17 // Return some cliched textual content 18 return "Hello World"; 19 } 20 }
You have a little bit more work to do to get this working, since JDeveloper doesn't "know" about JAX-RS you have to deploy rather than just run this java file So create a WAR file deployment profile for the project and a EAR file deployment profile that contains the WAR at the application level. Finally create a weblogic-application.xml deployment descriptor for the EAR and add the following text. (In the application navigator go to the "Application Resource Section", then Descriptors->META-INF and the context menu "Create Weblogic Descriptor".
<?xml version = '1.0' encoding = 'ISO-8859-1'?> <weblogic-application xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.bea.com/ns/weblogic/weblogic-application.xsd" xmlns="http://www.bea.com/ns/weblogic/weblogic-application"> <prefer-application-packages> <package-name>org.objectweb.*</package-name> </prefer-application-packages> </weblogic-application>
This makes sure that the version of asm that comes with JAX-RS overrides the one that is built into weblogic. Otherwise you get a method not found error when the service deploys. You should now be able to deploy the service and test using a URL such as "http://localhost:7101/Application1-Project1-context-root/helloworld". The context root and machine/port will of course vary depending on your environment.
You can test this in a web browser, or in JDeveloper you can test using the HTTP Analyzer. View->HTTP Analyzer, Create New Message toolbar button and navigate to the "HTTP Content" tab. Put your URL in at the top and change the method to "GET" rather than the default "POST". Send the message as you should get back "Hello World" in plain text.
Right no time left to play today, back to fixing bugs.
Update 9 Dec '08:
There is an alternative to creating the weblogic-application.xml file that I discovered recently. I wasn't happy with this solution as it affected the entire EAR file. Instead you can create a "weblogic.xml" under WEB-INF that looks like this:
<?xml version = '1.0' encoding = 'ISO-8859-1'?> <weblogic-web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.bea.com/ns/weblogic/weblogic-web-app.xsd" xmlns="http://www.bea.com/ns/weblogic/weblogic-web-app"> <container-descriptor> <prefer-web-inf-classes>true</prefer-web-inf-classes> </container-descriptor> </weblogic-web-app>
This has the advantage of only affecting the .war file you are deploying the restful services in. Also I have found out that this problem with the clashing jar files appears to have gone away in the "next" big release of weblogic, but I can't promise you when or even if you will see this fix.
Update 13 Feb 13 '09: With the release of Jersery 1.0.2 you can now download with the basic dependencies, the json libraries and javadoc from the download page. This makes it much easier for non maven users to pick up and use it. Make sure you include both the -server and -core libraries as the -bundle version is not present in the zip.
Update 24 July '09: You can now download all the core dependencies in one big lump in the jersey-archive take a look at the download page for details.
Update 24 July '09: For weblogic server 10.3.1 aka the R1 release you no longer have to use the "prefer-web-inf-classes" modifier as the namespace in the internal copy of asm has been changed.
Update 12th August '09: (We think this only work for the yet unreleased R11PS1/10.3.2 version) For weblogic server 10.x you can can fix the annotation scanner issue so you no longer have to specify the package location. The failure is caused because ServletContext.getRealPath(...) returns null when you deploy an application as a jar file. You can work around this with the fix for weblogic issue CR299135.
In the simplest case this means your web.xml simplifies to:
<?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"> <servlet> <display-name>JAX-RS Servlet</display-name> <servlet-name>jersey</servlet-name> <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class> </servlet> <servlet-mapping> <servlet-name>jersey</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> </web-app>
You could create a weblogic.xml under META-INF, see similar instructions before as to how to create weblogic-webservices.xml:
<?xml version = '1.0' encoding = 'ISO-8859-1'?> <weblogic-web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.bea.com/ns/weblogic/weblogic-web-app http://www.bea.com/ns/weblogic/weblogic-web-app/1.0/weblogic-web-app.xsd" xmlns="http://www.bea.com/ns/weblogic/weblogic-web-app"> <container-descriptor> <show-archived-real-path-enabled>true</show-archived-real-path-enabled> </container-descriptor> </weblogic-web-app>
Or make the changes to config.xml mentioned in the CR' above.
Update 1st December '09: Jersey issue 32 has been resolved, if you pull the latest 1.5ea build you should be able to deploy with no workaround. JDeveloper R11 PS1 will unfortunately still create the weblogic.xml automatically; but we can fix this in a future release once 1.5 is production.
Update 11th of March '10: Some final issues, resolved in Jersey 1.1.5.1 specifically for weblogic. Resolved the null pointer in web scanner and some problems with package level filtering that has been reported here.
Update 25 of May '11: Some problems running Jersey with the Oracle XDK in the latest versions of JDeveloper.
Ran into an interesting bug today where code would run one way on the plain xml parser that comes with Java and fail when running with the Oracle XML Parser (XDK).:
public void collectNamespaces(Element element, Map collection) { NamedNodeMap nam = element.getAttributes(); assert nam!=null : "Attribute map can't be null"; // Do something with the attributes // Node parent = element.getParentNode(); if (parent!=null && parent instanceof Element) { collectNamespaces((Element)parent, collection); } }
If you ran with the XDK, the last turn of the recursion would have element being an instance of XMLDocument, the XDK implementation of Document, which returns null for getAttributes. Now getAttributes should never return null for an Element and a quick look at org.w3c.dom.Document suggests it extends "org.w3c.dom.Node". So how did we get to element being assigned with a document instance?
It turns out that internally XMLDocument extends XMLElement which implements "org.w3c.dom.Element". It should be free to do this internally and only the instanceof is showing up this problem. But how would the code have been written to not have seen this problem? Well it turns on that Node.getNodeType provide you with enough information to write this code without "instanceof" or casting. (Turns out that getAttributes() is on the Node interface).
public void collectNamespaces(Node element, Map collection) { assert element.getNodeType() == Node.ELEMENT_NODE : "Should only accept Element for namespace lookup" NamedNodeMap nam = element.getAttributes(); assert nam!=null : "Attribute map can't be null"; // Do something with the attributes // Node parent = element.getParentNode(); if (parent!=null && parent.getNodeType()==Node.ELEMENT_NODE) { collectNamespaces(parent, collection); } }
In general the lesson here is to use instanceof only where there isn't another way of doing things. It can tell you a little bit too much about the class you are working with. Sometimes you really don't need to know it's ancestry in that much detail.
As the JAX-WS tools build on top of the JAX-B tools it is sometimes nice to be able to apply some of those customizations when building web services. This is quite easy it seems. Can't think of a use for it today; but is probably one of those things used to get out of a hole later on.
As I discovered whilst writing my presentation for Jazoon due to a programming error it was not possible to consume a SOAP Fault message using JAX-WS. You have to write a servlet instead. It looks like a fix for this is going into JAX-WS 2.1.5. This is good news although I can't seem to see the revision in the source code browser to check it will work.
I spent the end of last week on a Patterns course given by Dr Heinz of JavaSpecialists fame. Although I have used patterns for nearly all of my career it seem like a good oppotunity to revise what I though I already new. Not something you really get a chance to do day to day.
In general the course was well presented and Dr Heinz is a good speaker. I suspect that I could personally have done with more detail on different ways on implementing patterns; but we were in a mixed group so he could only go into so much detail. It was also useful to talk over the motivation for each pattern and it put a few things straight in my head.
We did the entire course using JDeveloper, and although I did log some bugs things are looking pretty stable these days. Dr Heinz did say he liked the expanded layout that is unique to the JDeveloper modelling tools. I wrote the original version of those, although the design idea is credit to Duncan Gittins, but the concept is still the same in the "new" generation modelers in JDeveloper 11.
Just to prove we were using JDeveloper in anger here is a nice picture of the Mememto pattern that we drew in class, in this case modelling virtual experimental rats:
A nice relaxing time at the Quinta Da Bella Vista in Funchal, then a bit of gardening and a weekend at the UK National Bat Conference. (Lots of nice high end geeky ecology toys to play with)
Now to delete read the last of the 1k4 odd emails I had in my inbox this morning and then back to bugs. Although it has to be said that me team mates did a very good job indeed of making sure we are on top of things.
Although this post focuses on the HTTP Analyzer in JDeveloper, well because I work on it, this also contains information that is relavent to other testing tools such a tcpmon and SOAPUI.
Normally in a development situation where you have everything on the same box you can simply use "localhost" as the domain name. This has a lot of advantages when working in a source controlled environment as the same strings can be used on different machines. So in this scenario you have the client, some kind of HTTP monitoring proxy, and the server running on the same machine.
So when you start the client from inside of JDeveloper we set the right proxy environment settings; but you can expect them to look something like:
-Dhttp.proxyHost=localhost -Dhttp.proxyPort=8988 -Dhttp.nonProxHosts= -Dhttps.proxyHost=localhost -Dhttps.proxyPort=8988 -Dhttps.nonProxHosts=
Now you would expect this mean that any request to any host will be sent via the proxy. But as noted in sun bug 6737819 the default ProxySelector will never proxy any requests to "localhost" even if you explicitly set "nonProxyHosts". This is of course a major pain for tools developers and users like.
Fortunately there is a workaround and that is to use "localhost.localdomain" instead. This is just a synomn for localhost and the proxy selector in java is not clever enough to recognize this as being the same as localhost. (Also I suspect it doesn't recognize :::1 or any of the other ipv6 localhost options)
When users get there sweaty hands on the next version of JDeveloper this is why we have moved to defaulting to "localhost.localdomain" rather than just plain old "localhost". Not because we are trying to be difficult or obtuse. :-)
Update:
Turns out this doesn't work so well on OS X. This is because the "/etc/hosts" file is missing localhost.localdomain entry. You can fix this by editing this file as root. ("sudo vi /etc/hosts") You need to alter the line:
127.0.0.1 localhost
to be
127.0.0.1 localhost localhost.localdomain
Please be carefull with this file, if you mess it up you machine may not boot properly. I have logged apple bug 6158676 to track this issue.
So my wife bought a VW Polo earlier on this year from the bluemotion range. We wanted something really quite fuel efficent I have have to say we are really quite pleased with how it lives up to the hype.
Based on the data provided by the onboard computer we average over 70 mpg, on longer motor way journeys it is not unheard of to average over 80 mpg. (Note these are UK not US MPG. Use a converter such as this one).
Remember this is the model with air-con and leather trims so it is not some eco-hay waggon. The variable speed turbo charger also means that is performs much better than you would expect from a diesel. Indeed I enjoy driving it more than my other sportier car in many ways.
And yes you could quite happily drive from London to Edinburgh on one tank of fuel, and avoid the congestion charge in the future due to its very low CO2 emissions.
The views expressed on this blog are my own and do not necessarily reflect the views of Oracle.