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
Response ratingReponse = 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
//
Pair usernamepassword =
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.


