Showing posts with label Java. Show all posts
Showing posts with label Java. Show all posts

Sunday, 27 December 2015

BeforeClass in Parameterized JUnit Test

Recently I was faced with an old problem again: JUnit does not run the @BeforeClass method before the @Parameters method. So I had to apply a workaround – to call the initialisation method within the parameters method.
@RunWith(Parameterized.class)
public class TestAddress2 {
 static Address addr, addr2, addr3;
 ...
 // for parameterized tests
 private Boolean equalResult, identicalResult;
 private Address a1, a2;

 //@BeforeClass
 public static  void init() {
  // set up addr, addr2, addr3
 }
 public TestAddress2(Boolean eqResult, Boolean idResult, Address a1, Address a2) {
  this.equalResult=eqResult;
  this.identicalResult=idResult;
  this.a1=a1;
  this.a2=a2;
 }
 @Parameters
 public static Collection parameters() {
  init();
  // equalResult, identicalResult, address 1, address 2
  return Arrays.asList(new Object[][] {
   { false, true, addr, addr3 },
   { false, false, addr, addr2 },
   { false, true, addr3, addr },
   { false, false, addr2, addr3 },
   { false, false, addr3, addr2 },
  });
 }
 @Test
 public void testEquals() {
  assertEquals(equalResult, a1.equals(a2));
 }
 @Test
 public void testIdentical() {
  assertEquals(identicalResult, a1.identical(a2));
 }
}

Sunday, 26 September 2010

JAAS with Tomcat

As JAAS has been included as part of JRE core since Java 1.4, it should be quite prevalent by now. However, I cannot seem to find many online resources that detail the  steps involved to set up JAAS with Tomcat. Here, I outline these steps from a beginner’s perspective.

Step 1 – Implement the JASS interfaces.

The basic interfaces to be implemented are

  1. Principal – there should be two implementations of this interface: One representing the user; another one (or more) represent the roles. The important method of this interface is getName() which returns the name of the user or role that it represents.
  2. LoginModule – this is the main class that usually serves as the wrapper for customised authentication logic. The login() method is where the customised login logic should be invoked.

Once implemented, JAR up these classes including the customised login classes and put it in $CATALINA_BASE/lib.

See the code segments at the end for example implementations.

Step 2 – Configure Tomcat Server

Edit $CATALINA_BASE/conf/server.xml to put in the JAASRealm configuration. As instructed by Tomcat’s Realm-HOW-TO documentation, this can be done at various levels - <Engine>, <Host> or <Context>. I added it inside the <Host> tag:



	
	
...

Notice the userClassNames and roleClassNames are the classes that were created in step 1.

Step 3 – The JRE login configuration

Create a JAAS Login Configuration file. I called it jaas.config and put it under $CATALINA_BASE/conf:
acczk {
  com.laws.acc.jaas.AccLoginModule required;
};

There are two ways to configure the JRE to load the login ocnfiguration file. I prefer to specify it in JAVA_OPTS. For Tomcat, this means to create a setenv.sh or setenv.bat in the $CATALINA_BASE/bin directory. Here is my setenv.bat:

set JAVA_OPTS=-Djava.security.auth.login.config==C:/Tools/apache-tomcat-7.0.2/conf/jaas.config

Step 4 – The Login Form

The login method can be BASIC or FORM-based. I prefer FORM. A vanilla HTML form should do. I happen to be using ZK so my HTML login form is in the file login.zul:






Welcome to ${c:l('client_name')}!

import com.laws.acc.ws.*; String errMsg = null; LoginResponse loginResponse=AccSoapClientProxy.getLoginResponse(); if(loginResponse!=null) { switch(loginResponse.getResultCode()) { case ResultCode.NOT_LOGGED_IN: errMsg="Not logged in."; break; case ResultCode.CONNECTION_ERROR: errMsg="Cannot login to server:\n"+loginResponse.getErrorDescription(); break; default: errMsg="Login failure:\n"+loginResponse.getErrorDescription(); } } Username : Password :
${errMsg}
j_username.focus();
The important part here is the HTML form employing j_username, j_password and j_security_check.

Step 5 - Configure web.xml

This is standard configuration to enable security constraints.

      ACC Security Constraint
      
         Protected Area
         
         /index.zul
	 /secure/*
         
         DELETE
         GET
         POST
         PUT
      
      
         
         Acc User
      
    
	
	  Generic authenticated ACC user
	  Acc User
	
    
    
      FORM
      Form-Based Authentication Area
      
        /login.zul
        /login.zul
      
    
  	
  	120	
  

My JAAS implementation files:

AccLoginModule.java:
package com.laws.acc.jaas;

import java.io.IOException;
import java.util.Map;

import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;

import com.laws.acc.ws.AccSoapClientProxy;
import com.laws.acc.ws.LoginResponse;

public class AccLoginModule implements LoginModule {
	// initial state
    private Subject subject;
    private CallbackHandler callbackHandler;
    private Map sharedState;
    private Map options;
    
    private AccPrincipal principal;
    private boolean committed = false;

	@Override
	public boolean abort() throws LoginException {
		if(!committed)
			return false;
		if(principal!=null) {
			logout();
			principal=null;
		}
		return true;
	}

	@Override
	public boolean commit() throws LoginException {
		if (principal == null){
            return false;
        }
        if (!subject.getPrincipals().contains(principal))
            subject.getPrincipals().add(principal);
	// hardcoded role assignment.
        AccRole role=new AccRole();
        if(!subject.getPrincipals().contains(role))
        	subject.getPrincipals().add(role);
        committed = true;
        return (true);
	}

	@Override
	public void initialize(Subject subject, CallbackHandler callbackHandler,
			Map sharedState, Map options) {
		this.subject = subject;
		this.callbackHandler = callbackHandler;
		this.sharedState = sharedState;
		this.options = options;
	}

	@Override
	public boolean login() throws LoginException {
		if (callbackHandler == null)
            throw new LoginException("No CallbackHandler specified");
        Callback callbacks[] = new Callback[2];
        callbacks[0] = new NameCallback("Username: ");
        callbacks[1] = new PasswordCallback("Password: ", false);
 
        // Interact with the user to retrieve the username and password
        String username = null;
        String password = null;
        try {
            callbackHandler.handle(callbacks);
            username = ((NameCallback) callbacks[0]).getName();
            password = new String(((PasswordCallback) callbacks[1]).getPassword());
            LoginResponse response = AccSoapClientProxy.login(username,password);
            if (response.getResultCode()!=0)
                return false;
            principal  = new AccPrincipal(response);
            return true;
        } catch (IOException e) {
            throw new LoginException(e.toString());
        } catch (UnsupportedCallbackException e) {
            throw new LoginException(e.toString());
        }
	}

	@Override
	public boolean logout() throws LoginException {
		committed=false;
		subject.getPrincipals().remove(principal);

		return false;
	}

}
AccPrincipal.java:
package com.laws.acc.jaas;

import java.io.Serializable;
import java.security.Principal;
import com.laws.acc.ws.*;

public class AccPrincipal implements Principal, Serializable {

	/**
	 * 
	 */
	private static final long serialVersionUID = 5002820876845306935L;
	private LoginResponse loginResponse=null;
	
	public AccPrincipal(LoginResponse lr) {
		this.loginResponse=lr;
	}
	
	@Override
	public String getName() {
		return loginResponse.getUserName();
	}

	@Override
	public boolean equals(Object other) {
		if(other==null) return false;
		if(other==this) return true;
		if(other instanceof AccPrincipal) {
			AccPrincipal that=(AccPrincipal) other;
			return loginResponse.getUserId()== that.getLoginResponse().getUserId();
		} else
			return false;
	}
	
	public LoginResponse getLoginResponse() {
		return loginResponse;
	}

	public void setLoginResponse(LoginResponse loginResponse) {
		this.loginResponse = loginResponse;
	}

	@Override
	public int hashCode() {
		return loginResponse.getUserId();
	}
}
AccRole.java:
package com.laws.acc.jaas;

import java.security.Principal;

public class AccRole implements Principal {

	@Override
	public String getName() {
		return "Acc User";
	}
	
	@Override
	public boolean equals(Object other) {
		if (other==null) return false;
		if(other==this) return true;
		if(other instanceof AccRole) {
			return this.getName().equals(((AccRole) other).getName());
		} else
			return false;
	}

	@Override
	public int hashCode() {
		return getName().hashCode();
	}
}

Tuesday, 21 September 2010

Java Logging & Properties File

I prefer to have the properties file at the application level or JAR level if I am writing a library, with as many configuration entries in the same file as logically possible so that I end up with as few files as possible. So my properties file is at the root level of the JAR file.

To load the config.properties file at the root of the JAR:

// ResourceBundle expectes the file extension to be .properties by default.
ResourceBundle bundle = ResourceBundle.getBundle("config",
    Locale.getDefault(),
    Thread.currentThread().getContextClassLoader());
String dmsAddress = bundle.getString("dmsAddress");
Similarly when I use Java Logging I put the logging.properties file at the same location and load it upon application start up using LogManager so that I don't have to tamper with the properties file at JRE/lib.

The logging.properties file:

handlers = java.util.logging.ConsoleHandler
.level=INFO

java.util.logging.ConsoleHandler.level = INFO
com.laws.acc.level = ALL
com.laws.acc.ws.level = ALL
It is better to copy the JRE's logging.properties file and modify it. To load the logging configuration:
LogManager logMan=LogManager.getLogManager();
logMan.readConfiguration(Thread.currentThread().getClass().getResourceAsStream("/logging.properties"));

Resources: Smartly Load Your Properties

Monday, 22 June 2009

JAXB Custom Data Binding

In a previous post I experimented consuming WCF web services using various Java WS frameworks and tools. As pointed out by Alex, the one that I missed out was wsimport which is bundled as part of JSE6.

Like many other tools, it supports both Ant task and command line interface (CLI). The CLI for wsimport is quite simple - in my case I generated the source code and client stub library like so:

D:\Program Files\Java\jdk1.6.0_11\bin>wsimport -d /temp/generated -s /temp/gensrc -keep http://localhost/PromoService.svc?wsdl
parsing WSDL...


generating code...

D:\Program Files\Java\jdk1.6.0_11\bin>
All the rest is similar to the results of IntelliJ shown in my previous post. There are two problems with the generated PromoInfo.java which is a data/value object:
  1. string fields are generated as JAXBElement<String>
  2. dateTime fields are generated as XMLGregorianCalendar
I want to use core java data types on the data objects so that they can be easily integrated with other frameworks without having to do conversion. Examining my schema (on http://localhost/PromoService.svc?xsd=xsd2) the PromoInfo complex type is defined as


  
    
      
      
      
      
    
  


It is obvious that the xs:string and xs:dateTime were not converted into the desired java types. To solve my problems I specified customised JAXB binding rules in an external file - custombinding.xml like so

 
     
 

The attribute generateElementProperty="false" on line 2 tells wsimport not to generate JAXBElement but to generate native java data types instead.

The javaType element on line 3 defines the binding between "xs:dateTime" and "java.util.Date" because by default xml dateTime binds to javax.xml.datatype.XMLGregorianCalendar as shown here.

Once the binding is defined, rerunning the wsimport tool with the -b switch will produce the desired output:
D:\Program Files\Java\jdk1.6.0_11\bin>wsimport -d /temp/generated -s /temp/gensrc -b /temp/custombinding.xml http://localhost/PromoService.svc?wsdl
parsing WSDL...


generating code...
Note: D:\temp\gensrc\org\w3\_2001\xmlschema\Adapter1.java uses or overrides a deprecated API.
Note: Recompile with -Xlint:deprecation for details.

This time the generated PromoInfo.java looks much better:
...
public class PromoInfo {

    @XmlElement(name = "PromoDateTime", type = String.class)
    @XmlJavaTypeAdapter(Adapter1 .class)
    @XmlSchemaType(name = "dateTime")
    protected Date promoDateTime;
    @XmlElement(name = "PromoDescription", nillable = true)
    protected String promoDescription;
    @XmlElement(name = "PromoName", nillable = true)
    protected String promoName;
    @XmlElement(name = "PromoVenue", nillable = true)
    protected String promoVenue;
...


Tuesday, 19 August 2008

Consuming Web Services By JavaME

The J2ME was designed for very resource-constrained hardware. As the devices get more and more powerful (N95, iPhone, HTC Android phones), the JavaME's Mobile Information Device Profiles (MIDP) desperately need an upgrade. Before JSR-271 (MIDP 3.0) is finalised Sun has recognised the poor UI capability of JavaME and released the Light Weight UI Toolkit (LWUIT) to stay competitive. Another good 3rd-party framework is J2ME Polish.

Here I attempt to call the same web services that I developed as part of a demo using JavaME and then display the results using LWUIT.

I first tried to consume the web services developed using WCF. The Sun's Wireless Toolkit (WTK2.5.2) comes with a utility to generate SOAP client proxy from WSDL. (An alternative is to use KSOAP2). However, JavaME only supports a subset of JAXP and JAX-RPC as defined in JSR-172. My web service data contract contains a DateTime field, which violates JSR-172. Also it is mapped to java.util.Calendar, which is not supported by JavaME. So I could not call the SOAP web service as it is from JavaME. I guess I will have to wait for MIDP 3.0 or even later. In any case, full SOAP web services are too much of a heavy weight for mobile devices. It is better to use RESTful services.

Fortunately, I have also developed a RESTful version of the services using NetBeans 6.1 under the project SvdemoRestful. So I created a MIDP 2.0 project named SvdemoWUIT in NetBeans and included the LWUIT JAR in the project.

There are two quick and easy ways to consume RESTful services:

The first way is to use one of NetBeans MIDP wizards to create "Mobile Client to Web Application". This wizard will take a Web Application (or a Web App project in NB) to discover the RESTful services and generates a pair of servlet (in the web app project) and mobile client (in the MIDP project) code to wrap the service. The screenshot shows the generated files highlighted in red. Using the generated mobile client in the MIDlet:

// create an instance of the mobile client proxy
WebToMobileClient client=new WebToMobileClient();

// consume the web service via the client
String keywords = client.getText("http://localhost/MySpace_Com_John.htm");
System.out.println("keywords:"+keywords);
String promoXml = client.getXml(keywords);
System.out.println("promo:"+promoXml);           
The corresponding stdout shows:
keywords:folk songs, pop music, chinese music ,battle, action, comedy
promo:801Batman The Dark Knight
                        Meet stars in Batman in person - Chritian Bale, Michael Caine.
                Star City2008-7-30 10:00:00Batman, action

There seemed to be a bug in the wizard: it only generated methods from one web service resource of the SvdemoRestful project although I specified both resources. Therefore, I had to modify the Utility.java and WebToMobileClient.java by hand to add the missing methods.

The second way is to use the javax.microedition.io.HttpConnection and deal with the low level stuff myself. I needed to parse the Promotion XML and to populate the PromoInfo object. JavaME only offers SAX for XML parsing, so I had to get the InputStream from the HttpConnection and pass it to the SAXParser. SAX uses callback mechanism. Therefore, a XmlHandler class had to be defined.

The MIDlet code snippet:

public class SvdemoWUITMidlet extends MIDlet implements ActionListener {
    static String promoUrl="http://localhost:8080/SvdemoRestful/resources/promo";
    // data object to hold the XML contents in its corresponding fields
    PromoInfo promo=null;
    public PromoInfo getPromo() {
        return this.promo;
    }
    public void setPromo(PromoInfo promo) {
        this.promo=promo;
    }
    public void startApp() {
        Display.init(this);
        HttpConnection hc=null;

        WebToMobileClient client=new WebToMobileClient();
        try {
            String keywords = client.getText("http://localhost/MySpace_Com_John.htm");
            System.out.println("keywords:"+keywords);
            String promoXml = client.getXml(keywords);
            System.out.println("promo:"+promoXml);
           
            hc=(HttpConnection)Connector.open(promoUrl + StringUtil.replace(keywords," ", "%20"));
            parse(hc.openInputStream());

            initialiseForm();
        } catch (IOException ex) {
            ex.printStackTrace();
        } finally {
            try { if (hc!=null) hc.close(); }
            catch(IOException ignored) {}
        }
    }

    private void parse(InputStream is) {
        SAXParserFactory factory = SAXParserFactory.newInstance();
        try {
            SAXParser saxParser = factory.newSAXParser();
            InputSource inputSource = new InputSource(is);
            saxParser.parse(is,new XmlHandler(this));

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

The XmlHandler.java file:

/*
 * To change this template, choose Tools  Templates
 * and open the template in the editor.
 */

package svdemo;

import java.util.Stack;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

/**
 *
 * @author ROMENL
 */
public class XmlHandler extends DefaultHandler {
    static String TAG_PROMOTION="Promotion";
    static String TAG_NAME="Name";
    static String TAG_DESCRIPTION="Description";
    static String TAG_VENUE="Venue";
    static String TAG_DATETIME="DateTime";
   
    private SvdemoWUITMidlet midlet;
    private PromoInfo promo=null;
    private Stack tagStack=new Stack();
    public XmlHandler(SvdemoWUITMidlet midlet) {
        this.midlet=midlet;
    }

    public void startDocument() throws SAXException {}
    public void startElement(String uri, String localName, String qName,
    Attributes attributes) throws SAXException {
        if(TAG_PROMOTION.equals(qName)) {
            promo=new PromoInfo();
        }
        tagStack.push(qName);
    }
    public void characters(char[] ch, int start, int length)
            throws SAXException  {
        String chars = new String(ch, start, length).trim();

        if(chars.length() > 0) {
            String qName = (String)tagStack.peek();
     
            if (TAG_NAME.equals(qName)) {
                promo.setName(chars);
            } else if(TAG_DESCRIPTION.equals(qName)){
                promo.setDescription(chars);
            } else if(TAG_VENUE.equals(qName)) {
                promo.setVenue(chars);
            } else if(TAG_DATETIME.equals(qName)) {
                promo.setDateTime(chars);
            }
        }
    }

    public void endElement(String uri, String localName, String qName,
    Attributes attributes) throws SAXException  {
        tagStack.pop();
    }

    public void endDocument() throws SAXException {
        midlet.setPromo(promo);
    }
}

Finally, presenting the retrieved data on the mobile device - the initialiseForm() method in the MIDlet:

    private void initialiseForm() {
        Form f = new Form("JME LWUIT WS Demo!");
        
        f.setTransitionInAnimator(Transition3D.createCube(2000, false));
        f.setTransitionOutAnimator(Transition3D.createCube(2000, true));
        f.getStyle().setBgColor(0x0000ff);
        
        if(promo!=null) {
            f.addComponent(createLabel("Name:"));
            f.addComponent(createTextArea(1, promo.getName()));

            f.addComponent(createLabel("Description:"));
            f.addComponent(createTextArea(2, promo.getDescription()));

            f.addComponent(createLabel("Venue:"));
            f.addComponent(createTextArea(1, promo.getVenue()));

            f.addComponent(createLabel("Date:"));
            f.addComponent(createTextArea(1, promo.getDateTime()));
        }
        f.show();
      
        Command exitCommand = new Command("Exit");
        f.addCommand(exitCommand);
        f.setCommandListener(this);
    }
    
    private Label createLabel(String labelText) {
        Label label=new Label(labelText);
        Font font=Font.createSystemFont(Font.FACE_SYSTEM, 
                Font.STYLE_BOLD, Font.SIZE_MEDIUM);
        label.getStyle().setFont(font);
        return label;
    }
    
    private TextArea createTextArea(int rows, String text) {
        TextArea ta=new TextArea(rows ,20, TextArea.ANY);
        ta.setEditable(false);
        ta.setText(text);
        return ta;
    }

The following screenshots show the LWUIT 3D cube transition effect and the MIDlet output form respectively.

Related Posts:

Tuesday, 12 August 2008

The Life and Death of OC4J

Once upon a time, two smart Swedes wrote a J2EE server called Orion and formed a company Ironflare to sell it. Orion server was totally different from all the other J2EE containers out there: it's light weight, blazing fast and dirt cheap (only a couple of grand USD for a commercial license, which was unheard of in the heyday of IT and internet)! It soon gained traction and almost gained a cult status in the J2EE world.

Then came the 21st century. In 2001, Oracle licensed Orion and created OC4J. The two smart Swedes from Ironflare moved with it and worked on OC4J for Oracle. Therefore, the Orion server and the website was virtually dead. However, Orion's heart still beats inside OC4J's body - you could even see the words 'orion' inside some of OC4J's configuration files. OC4J then became the core of Oracle Application Server for over half a decade, during which time the Oracle AS had grown to a humongous beast.

In January 2008, Oracle announced the merger with BEA, which created great FUD among OC4J and Weblogic communities - they were speculating whether Oracle will kill off Weblogic or OC4J. Yesterday, Oracle finally sounded the death knell for OC4J by announcing the release of Oracle Weblogic Server 10g R3 - it threw its weight behind WLS and is abandoning OC4J. This will no doubt create panick among OC4J user communities. I worked for a company whose core technology was Oracle Forms (yes, you heard that right!) and they used to be relatively calm about it because Oracle offered a migration path from Oracle Forms to its JSF framework, and for the meantime you can still run the Oracle Forms on Oracle AS 9i as Java applets (no longer supported in 10g though). Now that OC4J will disappear, that will certainly motivate them to migrate off the Oracle Forms technology sooner.

I don't believe the two smart Swedes will go back to resurrect Orion server because there are several free JavaEE servers nowadays, which take away the incentive for people to use Orion. Besides, Orion has been disconnected with the Java community for so long, it's fan base has all but evaporated.

Orion & OC4J

Tuesday, 5 August 2008

Why Not Five-teen?

My four-year-old son has just learnt how to count. As a newbie, he has not memorised all the numbers. So sometimes, he improvises and derives the number words that he needed. For example, when he sees '15', he would read it as 'five-teen'. He is OK with the numbers that are 'regular', such as 14, 16, 17... But he could not readily read the irregular ones all the time, such as 11 and 12.

At this young age, he has not been 'tainted' by all the variations and irregularities of the English spelling and pronunciation rules. It is natural that he follows the 'logical' way to come up what he thinks and seemingly to be correct.

The English language is full of exceptions and variations in its rules of spelling, pronunciation and grammar. These inconsistencies make English not an easylanguage to learn comparing to many others.

When it comes to computing languages and APIs, consistency also determines whether the language or API is easy to learn and use (although now people are talking about more advanced topics, such as fluency). Let's take GUI frameworks as an example.

When building graphical user interfaces, we need to put some widgets/components onto the canvas or another widget. So there is a parent-child relationship: the parent is the container, the child is the object being put inside the container. There are two ways of saying this relationship:

  1. parent.addChild(child)
  2. child.setParent(parent) or specify the parent in the child's constructor

Most GUI frameworks use the first syntax - such as Swing, Windows Forms, WPF, ASP.NET, GWT, Echo2. For example, in Swing:

JButton button=new JButton("Clear");
button.setActionCommand(command);
button.setToolTipText(tooltip);
button.addActionListener(this);
...
JPanel buttonPanel=new JPanel(new FlowLayout());
buttonPanel.add(button);

In these GUI frameworks, the same widget cannot be added to more than one container. If you did, then it either complains at runtime or gives undesired odd results. But the API allows you to make these mistakes and your code compiles without error. So this is an inconsistency in those framework APIs.

To fix this problem, SWT/JFace takes the second approach by using the constructor:

Composite buttons=new Composite(this, SWT.NONE);
...
Button clearButton=new Button(buttons, SWT.PUSH);

This way, you cannot mistakenly set more than one parent for the widget.

There are other languages and frameworks that take a third approach by creating the child widget inside the code block of the parent. For example, in Wicket:

public abstract class BaseAddressForm extends Form {
...
  void initForm() {
  ...
    add(new Button("clear", new Model("Clear")) {
      @Override
      public void onSubmit() {
        log.info(getModelObject());
        resetModel();
      }
    });
  }
...
}
Similarly, in JavaFX script:
...
bottom: FlowPanel {
  content: [
    Button {
      text: "Clear"
      action: operation() {
        model.selectedAddressIndex=-1;
      }
    },
...

The above are exmaples within a single framework. When we have to deal with multiple frameworks the problem becomes more obvious. Normally, we don't expect different frameworks/languages to follow the same rules. However, when one framework is an add-on/extension to another, our expectation changes. For example, GWT-Ext is an add-on to GWT. However, its API naming conventions are not consistent with that of GWT: in GWT the method name getText() is used to get the text label of a widget (e.g. a label, button, etc.); but in GWT-Ext the method is called getValue(). Also, in GWT, the Command is widely used (in a similar way to the Action class in JFace). However, in GWT-Ext event listeners have to be explicitly created...

Don't get me wrong, I love using GWT and GWT-Ext. But these little inconsistencies hinder productivity when using this mix.

Friday, 25 July 2008

Consuming WCF Web Service Using Java Client

[Updated on 2009-06-22] The JSE's native wsimport tool has been added along with custom binding in a more recent post - JAXB Custom Data Binding.

From the previous post I showed how to create a simple web service using WCF and consume it with a windows console application written in C#.

To have the same web services consumed by a Java client, I needed to make the web service WS-I Basic Profile 1.1 compliant so that it becomes portable cross-platforms. To do this, it is simply a matter of following instructions on MSDN. In my case, I modified Web.Config file: in <services> section I changed the wsHttpBinding into basicHttpBinding and removed the identity tags. The resulting snippet:


    
        
        
        
        
    

IntelliJ IDEA

To generate the Java web service client, I created a Java project in IDEA 7.0.2. Then right-click on the src tree to select New -> Web Service Client, then paste in the wsdl url (http://localhost/PromoService.svc?wsdl). Bingo, IDEA generates all the interface and class files, together with the client test code. I guess it is using JAXB under the hood. Adding the bits into the client test code resulted:

import svdemo.PromoService;
import svdemo.PromoInfo;

public class SvdemoClient {
  public static void main(String[] argv) {
      svdemo.IPromoService service = (new PromoService()).getBasicHttpBindingIPromoService();
      //invoke business method
      String keywords = "action";
      if(service.hasPromo(keywords)) {
          PromoInfo promo = service.getFirstPromo(keywords);
          System.out.println("promo:"+promo.getPromoName().getValue()
                  +","+promo.getPromoDateTime());
      }   else {
          System.out.println("nothing");
      }
  }
}

Notice how the service is instantiated (code not as nice as in C# version). Also, the PromoInfo class's members data type are of JAXBElement and XMLGregorianCalender..., which are not as nice as native primitive types. But it works - here is the output:

promo:Batman The Dark Knight,2008-07-30T10:00:00

NetBeans 6.0

I created a Java application project in NetBeans. From the project's source branch, right-clicking will bring up the New -> Web Service Client option. Providing the WSDL URI and selecting JAX-WS style will generate the WS client classes. The results are similar to IDEA, the exact same client code (see above) can be used to test the client. However, it is extremely slow so that I thought NetBeans froze. This happens to almost every step onwards. I eventually gave up on NetBeans. Maybe it is time to upgrade.

CXF

Apache CXF is the 2nd generation XFire. By default, it generates JAXB style binding. It came with wsdl2java utility. To generate the client code from wsdl, I used the following commands:

mkdir d:\projects\svdemocxf
wsdl2java -d d:\projects\svdemocxf -client -ant http://localhost/PromoService.svc?wsdl
I then moved all the source files into a src directory. Then build the project.
D:\projects\svdemocxf>ant build
D:\projects\svdemocxf>ant IPromoServiceClient
Buildfile: build.xml

compile:
    [javac] Compiling 15 source files to D:\projects\romen\build\classes

IPromoServiceClient:
     [java] Invoking hasPromo...
     [java] hasPromo.result=false
     [java] Invoking getFirstPromo...
     [java] getFirstPromo.result=null
     [java] Invoking getKeywordsFromMySpace...
     [java] getKeywordsFromMySpace.result=

BUILD SUCCESSFUL
Total time: 4 seconds

Note that the environment variable CXF_HOME had to be defined and point to the CXF installation directory for the ant build.xml file to work.

I added the client testing code similar to the one above:

package svdemo;
import org.datacontract.schemas._2004._07.svdemo.PromoInfo;
import org.tempuri.*;

public class Program {

 /**
  * @param args
  */
 public static void main(String[] args) {
  IPromoService service=(new PromoService()).getBasicHttpBindingIPromoService();
  String keywords="action";
  if(service.hasPromo(keywords)) {
   PromoInfo promo=service.getFirstPromo(keywords);
   System.out.println("promo:"+promo.getPromoName().getValue()
     +","+promo.getPromoDateTime());
  } else {
   System.out.println("nothing");
  }
 }
}
To run the test, I added the following target to the build.xml file:

    
 

Running the RunTest target yields:

D:\projects\svdemocxf>ant RunTest
Buildfile: build.xml

compile:
    [javac] Compiling 16 source files to D:\projects\svdemocxf\build\classes

RunTest:
     [java] promo:Batman The Dark Knight,2008-07-30T10:00:00

BUILD SUCCESSFUL
Total time: 4 seconds

Axis2

Apache Axis2 (v1.4) comes with the wsdl2java utility which generates java source code from WSDL file. Using the following command:

wsdl2java.bat -o \projects\svdemoAxis -p svdemoClient -uri http://localhost/PromoService.svc?wsdl

This generates the client stub class PromoServiceStub and dozens of inner classes - one for each input/output parameter and data type! The client test code ends up like this:

package svdemoClient;

import java.rmi.RemoteException;
import org.apache.axis2.AxisFault;
import svdemoClient.PromoServiceStub.PromoInfo;

public class Program {

 /**
  * @param args
  */
 public static void main(String[] args) {
  try {
   PromoServiceStub service=new PromoServiceStub();
   String keywords = "action";
   
   PromoServiceStub.HasPromo hasPromoPara=new PromoServiceStub.HasPromo();
   hasPromoPara.setKeyword(keywords);
   PromoServiceStub.HasPromoResponse hasPromoResp = service.HasPromo(hasPromoPara);
   if(hasPromoResp.getHasPromoResult()) {
    PromoServiceStub.GetFirstPromo firstPromoPara=new PromoServiceStub.GetFirstPromo();
    firstPromoPara.setKeyword(keywords);
    PromoServiceStub.GetFirstPromoResponse firstPromoResponse = service.GetFirstPromo(firstPromoPara);
    PromoInfo promo = firstPromoResponse.getGetFirstPromoResult();
    System.out.println("promo:"+promo.getPromoName()
      +","+promo.getPromoDateTime().getTime());
   } else {
    System.out.println("nothing");
   }
  } catch (Exception e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
  
 }

}
Compiling and running it in Eclipse 3.4 yields:
promo:Batman The Dark Knight,Wed Jul 30 10:00:00 EST 2008
As shown above, the code is very UGLY! For each input parameter and output response, a class has to be instantiated (even when the parameter is simply a string). This not only makes the code long, but also very hard to read. This is the ADB binding used by Axis2 by default. Changing the binding style into xmlbeans or jibx slightly improves the esthetic and readability of the code, but not much. The following test client code snippet is written against XMLBeans binding style from Axis:
String keywords = "action";
HasPromoDocument hasPromoDoc = HasPromoDocument.Factory.newInstance();
hasPromoDoc.getHasPromo().setKeyword(keywords);
HasPromoResponseDocument hasPromoResp;
hasPromoResp = service.HasPromo(hasPromoDoc);

if(hasPromoResp!=null && hasPromoResp.getHasPromoResponse().getHasPromoResult()) {
 GetFirstPromoDocument firstPromoDoc=GetFirstPromoDocument.Factory.newInstance();
 firstPromoDoc.getGetFirstPromo().setKeyword(keywords);
 GetFirstPromoResponseDocument firstPromoResp=
  service.GetFirstPromo(firstPromoDoc);
 if(firstPromoResp!=null) {
  PromoInfo promo=firstPromoResp.getGetFirstPromoResponse().getGetFirstPromoResult();
  System.out.println("promo:"+promo.getPromoName()
    +","+promo.getPromoDateTime().getTime());
 }
}
This article from DeveloperWorks gives more insights and examples on the different types of Axis2 bindings. As of Axis2 v1.4, it came with a JAXB experimental support. The above is just my feeble attempt on Axis2. However, Axis2's wsdl2java is quite capable of generating esthetic code - Eclipse 3.4 uses Axis under the hood (using JAXB I guess) to generate web service clients.

Eclipse Ganymede JEE Distribution

Eclipse Ganymede (v3.4) has an option to add new Web Service Client. However, it requires you to specify a Server - this is totally wrong, there is absolutely no need to have a server definition to generate WS client code. However, I just played along and defined a Tomcat 6.0 server. It allows me to continue with the WS Client wizard. Then, after I specified the WSDL URI, it gave me the error:

IWAB0503E Unable to update Java build path. Please check your system environment.
    Java Model Exception: Java Model Status [Build path contains duplicate entry: 'D:tools/eclipse/plugins/org.apache.axis_1.4.0.v200806030120/lib/axis.jar' for project 'svdemoAxis']
    ...
Days later, I upgraded one of the Eclipse plugins (which was auto-detected and suggested by Eclipse), then things were better. Retrying generating the web service client resulted in a new WebServiceProject project being created. I then added the testing client:
package svdemoClient;
import java.rmi.RemoteException;

import org.datacontract.schemas._2004._07.svdemo.PromoInfo;
import org.tempuri.*;

public class Program {

 /**
  * @param args
  */
 public static void main(String[] args) {
  String keywords="action";
  IPromoService service=(new IPromoServiceProxy()).getIPromoService();
  try {
   if(service.hasPromo(keywords)) {
    PromoInfo promo=service.getFirstPromo(keywords);
    System.out.println("promo:"+promo.getPromoName()
      +","+promo.getPromoDateTime().getTime());
   }
  } catch (Exception e) {
   e.printStackTrace();
  }
 }

}
Note that the promotDateTime field generated is of java.util.Calendar type, not XMLCalendar.

Related Posts:

Thursday, 10 July 2008

Xenophobia

I have travelled to many countries and met people from many different backgrounds and points of views due to the nature of my work. Some people are more open-minded and tollerant than others; and of course, there are the occasional xenophoics that you can find in any country.

Xenophobics are extremely easy to identify: if you have expressed yourself which did not agree with his/her beliefs, then he/she will throw you the tell-tale line of "Go back to your own country!" I am sure many frequent overseas travellers or immigrants have experienced it at least once or twice.

In the computing world, such xenophobic behaviour also exists. In a recent online article on Java Lobby, the subject of whether Java should have closure was brought up again. As usual, it attracted many readers' comments. I took the bait and posted a comment that reads:

I reckon Java should have closure. In terms of style, just make it the same as .NET (e.g. C#, closest to BGGA I guess). A great use case of closure is LINQ. This way, the argument about complexity/learning curve goes away since you can benefit in both worlds now. cheers romen

It was met with a reply which bears the typical trademark of 'go back to your own country' tone:

if you want .NET style closures, use .NET.

The full comment is available here.

In the real world, many people can speak more than one language, especially in this globalisation day and age. So it should be natural for developers to know more than one programming language and understand the differences of each computing world because each has its pros and cons, and each can learn from the other. Closure is one such example - it exists in many other languages and programming environments and now the Java community is debating its introduction to the Java land.

James Gosling, the creator of Java language expressed his support on embracing closure in Java. Introducing closure inevitably means introducing new language syntax, which are closer to functional languages. To me, functional languages are not so strange as we were taught Miranda in UNSW and I was exposed to Lisp as I was an Emacs user when programming in the Unix environment many years ago. Even to programmers who had never seen functional languages, it should not be such an alien concept - after all, mathimatical formulae are expressed in functional language, e.g. f(x,y) = 2x + y should look pretty straight forward to anyone who have passed high school.

So, just like in real life, with an open mind and some tollerance to differences, it is not hard to accept a 'foreign' concept to the Java world.