Sunday, December 19, 2010

Cannot Open Workspace on Remote Volume? (Mac OS X)

Hi

I've been using Eclipse for quite some time, and never experienced this problem!

For technical reasons I will not go into here, I have to have my workspace on a linux server network share/remote volume. This has never been a problem when using Eclipse on Windows. I simply mounted the network share, and used it as a normal file system.

However, I've recently switched my Windows PC for a beautiful iMac G5. So far I've had no trouble with the switch, accept for Eclipse!

My Eclipse workspace is on smb://persephone/alasdair/Eclipse. So, I have connected my iMac to the share, and Mac OS X mounts it at /Volumes/alasdair. This is fine, as in theory my Eclipse workspace should now be /Volumes/alasdair/Eclipse. (The share is mouned read/write with the correct privileges, as only the IP address of my iMac can access the share.)

Every time I try to use the workspace, I simply get a "Workspace in use" error. I've removed any stale .lock files, and even removed the ENTIRE workspace and attempted to recreate it. Eclipse will create the workspace folder, but inside the .metadata file is a single .lock file that is created as soon as I try to use the workspace. Even if I keep deleting it, it comes back with each attempt. It's as if it's creating the workspace, locking it, and then trying to use it and seeing the lock!

Has anyone else experienced this issue?

Does anyone have a fix/workaround? :) I can't have the workspace on my Macintosh HD because it has to be compiled on Linux. I've always developed via the remote share so I can just use a terminal session to type 'make.'

I'm using Mac OS X Panther v10.3.8, but the problem was also around on v10.3.7 and v10.3.6.

Any response would be greatly appreciated. :)


==================================================

AlasdairM wrote:

Has anyone else experienced this issue?

Oh, yeah... long story short: Eclipse uses Java 1.4 file locking capabilities to prevent users from accidently opening the same workspace twice, what can cause data corruption. But file locking does not work well yet on a variety of VM/OS/file system combinations. The workaround is to disable file locking when running Eclipse by passing the following system property as VM arg:

-Dosgi.locking=none

More information on this:

https://bugs.eclipse.org/bugs/show_bug.cgi?id=44735

Monday, December 13, 2010

Chicken of the VNC

Chicken of the VNC

Share

Chicken of the VNC A few people have written in, asking how to remote control a Mac. It couldn’t be much easier, especially if you’ll be doing the controlling from a computer on the same network as the machine you want to control. The server component is built into OS X. The client component (on the computer you will do the controlling from) can be downloaded here.

Click here to skip ahead to the screencast.

If you plan on controlling a machine from across the Internet there’s an extra step. Let’s look at both scenarios:

Both computers on same network.

  1. Configure the machine you want to control (the server) to allow VNC access. See the screencast for details.
  2. Open Chicken of the VNC and connect to the ip address of the server. Instead of the ip address you can have Chicken of the VNC use Bonjour to show you servers on your network.

Controlling a machine from across the Internet.

  1. Configure the machine you want to control (the server) to allow VNC access. See the screencast for details.
  2. From the server, go to the web site http://whatismyip.com and make a note of the ip address. This is how your home network is seen on the Internet.
  3. Go to the router of the network your server is on. Configure port forwarding to allow port 5900 through to the server. See the screencast for details.
  4. At the coffee shop, Open Chicken of the VNC on the client and connect to the ip address noted in step 2. Bonjour won’t help you across the Internet - you may as well turn it off. And you can’t use the server’s actual address. You have to use the address of the router. The router will pass the request to the server, based on the port forwarding you set up in the previous step.

If you don’t have a router at home your Mac is probably connected directly to the DSL/Cable Modem. In that case, the server’s address and the http://whatismyip.com address are likely the same. (Some modems act as firewalls, in which case they could be different!) Use that address from the coffee shop.

Consider getting a router. For forty dollars you can add an important layer of security and the convenience of wireless access.

More to think about:

  • VNC is not notably secure. You’re relying on a single password to keep intruders out.
  • Consider running third party VNC server software, like OSXvnc. It allows you to run the server on a port other than 5900, helping to hide your service from hackers.
  • You can use RealVNC on Windows computers to remote control your Mac instead of Chicken of the VNC. There’s a free Personal Edition.
  • There are other ways, using far more secure tools, to remotely control your Mac. Murphy will cover tools like ssh next week. In the meantime, brush up on your Terminal skills!

More on ports: A port number is what an application uses to identify itself to the network. There is all kinds of traffic flowing to your networked computer. The port directs the traffic to the proper application. Traffic finds the computer by its ip address, then goes on to find the right application by its port address.

Thursday, December 9, 2010

Moving your cursor in a terminal with bash shell

Moving your cursor in a terminal with bash shell (Mac OS X, and Ubuntu Linux compatible)

Tired of pressing delete 1,000 times to move around in your terminal window? Use these handy shortcuts and spare yourself the repetitve stress inducing, carpal-tunnel-flaring effects of using the command line in Macs and Linux.

My current favorites:

  • ctrl-a: move to front of line
  • ctrl-e: move to end of line
  • ctrl-w: delete word before cursor
  • ctrl-r: search past command history
  • up/down arrow: page through previous commands
  • alt-b / alt-f: move backward/forward one word (without deleting)

Monday, September 6, 2010

Java|对代理模式与Java动态代理类的理解

对代理模式与Java动态代理类的理解

1.      代理模式

代理模式的作用是:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个客户不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

代理模式一般涉及到的角色有:

抽象角色:声明真实对象和代理对象的共同接口;

代理角色:代理对象角色内部含有对真实对象的引用,从而可以操作真实对象,同时代理对象提供与真实对象相同的接口以便在任何时刻都能代替真实对象。同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装。

真实角色:代理角色所代表的真实对象,是我们最终要引用的对象。(参见文献1)

以下以《Java与模式》中的示例为例:

抽象角色:

abstract public class Subject

{

    abstract public void request();

}

真实角色:实现了Subjectrequest()方法。

public class RealSubject extends Subject

{

       public RealSubject()

       {

       }

      

       public void request()

       {

              System.out.println("From real subject.");

       }

}

代理角色:

public class ProxySubject extends Subject

{

    private RealSubject realSubject;  //以真实角色作为代理角色的属性

      

       public ProxySubject()

       {

       }

 

       public void request()  //该方法封装了真实对象的request方法

       {

        preRequest(); 

              if( realSubject == null )

        {

                     realSubject = new RealSubject();

              }

        realSubject.request();  //此处执行真实对象的request方法

        postRequest();

       }

 

    private void preRequest()

    {

        //something you want to do before requesting

    }

 

    private void postRequest()

    {

        //something you want to do after requesting

    }

}

客户端调用:

Subject sub=new ProxySubject();

Sub.request();

       由以上代码可以看出,客户实际需要调用的是RealSubject类的request()方法,现在用ProxySubject来代理RealSubject类,同样达到目的,同时还封装了其他方法(preRequest(),postRequest()),可以处理一些其他问题。

       另外,如果要按照上述的方法使用代理模式,那么真实角色必须是事先已经存在的,并将其作为代理对象的内部属性。但是实际使用时,一个真实角色必须对应一个代理角色,如果大量使用会导致类的急剧膨胀;此外,如果事先并不知道真实角色,该如何使用代理呢?这个问题可以通过Java的动态代理类来解决。

 

2.动态代理类

       Java动态代理类位于Java.lang.reflect包下,一般主要涉及到以下两个类:

(1). Interface InvocationHandler:该接口中仅定义了一个方法Objectinvoke(Object obj,Method method, J2EEjava语言JDK1.4APIjavalangObject.html">Object[] args)。在实际使用时,第一个参数obj一般是指代理类,method是被代理的方法,如上例中的request()args为该方法的参数数组。这个抽象方法在代理类中动态实现。


(2).Proxy
:该类即为动态代理类,作用类似于上例中的ProxySubject,其中主要包含以下内容:

Protected Proxy(InvocationHandler h):构造函数,估计用于给内部的h赋值。

Static Class getProxyClass (ClassLoader loader, Class[] interfaces):获得一个代理类,其中loader是类装载器,interfaces是真实类所拥有的全部接口的数组。

Static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h):返回代理类的一个实例,返回后的代理类可以当作被代理类使用(可使用被代理类的在Subject接口中声明过的方法)。


      
所谓Dynamic Proxy是这样一种class:它是在运行时生成的class,在生成它时你必须提供一组interface给它,然后该class就宣称它实现了这些 interface。你当然可以把该class的实例当作这些interface中的任何一个来用。当然啦,这个Dynamic Proxy其实就是一个Proxy,它不会替你作实质性的工作,在生成它的实例时你必须提供一个handler,由它接管实际的工作。(参见文献3)

    在使用动态代理类时,我们必须实现InvocationHandler接口,以第一节中的示例为例:

抽象角色(之前是抽象类,此处应改为接口):

public interface Subject

{

    abstract public void request();

}

具体角色RealSubject:同上;

 

代理角色:

import java.lang.reflect.Method;

import java.lang.reflect.InvocationHandler;

 

public class DynamicSubject implements InvocationHandler {

  private Object sub;

 

  public DynamicSubject() {

  }

 

  public DynamicSubject(Object obj) {

    sub = obj;

  }

 

 

  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

    System.out.println("before calling " + method);

 

    method.invoke(sub,args);

 

    System.out.println("after calling " + method);

    return null;

  }

 

}

 

       该代理类的内部属性为Object类,实际使用时通过该类的构造函数DynamicSubject(Object obj)对其赋值;此外,在该类还实现了invoke方法,该方法中的

method.invoke(sub,args);

其实就是调用被代理对象的将要被执行的方法,方法参数sub是实际的被代理对象,args为执行被代理对象相应操作所需的参数。通过动态代理类,我们可以在调用之前或之后执行一些相关操作。

客户端

import java.lang.reflect.InvocationHandler;

import java.lang.reflect.Proxy;

import java.lang.reflect.Constructor;

import java.lang.reflect.Method;

 

public class Client

{

 

    static public void main(String[] args) throws Throwable

       {

      RealSubject rs = new RealSubject();  //在这里指定被代理类

      InvocationHandler ds = new DynamicSubject(rs);  //初始化代理类

         Class cls = rs.getClass();

      //以下是分解步骤

      /*

      Class c = Proxy.getProxyClass(cls.getClassLoader(),cls.getInterfaces()) ;

      Constructor ct=c.getConstructor(new Class[]{InvocationHandler.class});

      Subject subject =(Subject) ct.newInstance(new Object[]{ds});

     */

     //以下是一次性生成

      Subject subject = (Subject) Proxy.newProxyInstance(cls.getClassLoader(),

                                 cls.getInterfaces(),ds );


     
subject.request();

}

       通过这种方式,被代理的对象(RealSubject)可以在运行时动态改变,需要控制的接口(Subject接口)可以在运行时改变,控制的方式(DynamicSubject)也可以动态改变,从而实现了非常灵活的动态代理关系(参见文献2)。

 

 

参考文献:

1.      阎宏,《Java 与模式》

2.          透明,《动态代理的前世今生》

3.   Forest Hou,Dynamic Proxy Java RMI 中的应用》

Thursday, June 10, 2010

CXF|How to create a WSDL-first SOAP client in Java with CXF and Maven

How to create a WSDL-first SOAP client in Java with CXF and Maven

Posted on October 19, 2008. Filed under: Java Programming |

Background

About a week ago I needed to write a SOAP-based client for work. The SOAP framework I'm using is Apache CXF. I'm a total noob when it comes to SOAP services, and so I was a little apprehensive about this at first. My apprehension sprung from hearing horror stories a few years ago from coworkers who were writing Axis SOAP applications, and they were basically tearing their hair out over Axis.

However, word has it that CXF is much easier to use. Well, it took me a while to get it working correctly. In an effort to save other folks the same grief, I've posted my code here. If you're reading this, I'm assuming you're acquainted with Java and Maven, but fairly new to SOAP, WSDL, etc.

To keep things simple, I decided to write a "Hello World" type of application first to make sure I could get the technology stack working correctly. To keep things really simple, I decided to create a trivial Java "main" function that calls a SOAP service, logs the result to the console, and exits (no fancy web interface or anything like that).

The SOAP Service Provider

First I had to select an appropriate web service to test against. There are a bunch of free SOAP-based web services out there, and I chose the CDyne weather service. You can go read all about it if you want.

Obtain the Service's WSDL

When you're writing a new client in CXF for an existing web service, you start with the WSDL and work from there. This means you need to get a copy of the WSDL from the service provider. The WSDL for the CDyne weather service can be downloaded from their site. You can simply right-click that link and save the WSDL on your hard drive.

Once you have the WSDL in hand, you can build your client around it. Basically, you'll use a CXF tool called wsdl2java to turn the WSDL into Java stub code that you then compile along with your application.

Create the Maven Project

As a recent convert to Maven, I set up a new Maven project. I created a new project directory called weather-client, which is the ${basedir}. Also, I put the WSDL file in ${basedir}/src/main/wsdl/weather.wsdl.

Yeah, I know Maven has its fancy archetype creator thingie to emit the initial POM file, but like most pragmatic programmers I simply copy and paste a similar POM from somewhere else and modify it to suit my needs. Here's the project file I came up with.

weather-client/pom.xml

002    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
003 
005 
006    <modelVersion>4.0.0</modelVersion>
007    <groupId>com.logicsector</groupId>
008    <artifactId>weather-client</artifactId>
009    <version>1.0</version>
010    <name>SOAP weather client</name>
011    <packaging>jar</packaging>
012 
013    <dependencies>
014        <dependency>
015             <groupId>org.apache.cxf</groupId>
016            <artifactId>cxf-rt-frontend-jaxws</artifactId>
017            <version>2.1.2</version>
018        </dependency>
019         <dependency>
020             <groupId>org.apache.cxf</groupId>
021            <artifactId>cxf-rt-transports-http</artifactId>
022            <version>2.1.2</version>
023        </dependency>
024         <dependency>
025             <groupId>org.slf4j</groupId>
026            <artifactId>slf4j-api</artifactId>
027            <version>1.5.2</version>
028        </dependency>
029         <dependency>
030             <groupId>org.slf4j</groupId>
031            <artifactId>slf4j-log4j12</artifactId>
032            <version>1.5.2</version>
033        </dependency>
034     </dependencies>
035 
036    <build>
037        <finalName>weather-client</finalName>
038        <plugins>
039             <!-- Generate Java classes from WSDL during build -->
040            <plugin>
041                 <groupId>org.apache.cxf</groupId>
042                <artifactId>cxf-codegen-plugin</artifactId>
043                <version>2.1.2</version>
044                <executions>
045                    <execution>
046                        <id>generate-sources</id>
047                        <phase>generate-sources</phase>
048                        <configuration>
049                            <sourceRoot>${basedir}/target/generated/src/main/java</sourceRoot>
050                            <wsdlOptions>
051                                <wsdlOption>
052                                    <wsdl>${basedir}/src/main/wsdl/weather.wsdl</wsdl>
053                                    <extraargs>
054                                        <extraarg>-client</extraarg>
055                                    </extraargs>
056                                </wsdlOption>
057                            </wsdlOptions>
058                        </configuration>
059                        <goals>
060                            <goal>wsdl2java</goal>
061                        </goals>
062                    </execution>
063                </executions>
064            </plugin>
065            <!-- Add generated sources - avoids having to copy generated sources to build location -->
066            <plugin>
067                <groupId>org.codehaus.mojo</groupId>
068                <artifactId>build-helper-maven-plugin</artifactId>
069                <executions>
070                    <execution>
071                        <id>add-source</id>
072                        <phase>generate-sources</phase>
073                        <goals>
074                            <goal>add-source</goal>
075                        </goals>
076                        <configuration>
077                            <sources>
078                                <source>${basedir}/target/generated/src/main/java</source>
079                            </sources>
080                        </configuration>
081                    </execution>
082                </executions>
083            </plugin>
084            <!-- Build the JAR with dependencies -->
085            <plugin>
086                <artifactId>maven-assembly-plugin</artifactId>
087                <configuration>
088                    <descriptorRefs>
089                        <descriptorRef>jar-with-dependencies</descriptorRef>
090                    </descriptorRefs>
091                </configuration>
092            </plugin>
093        </plugins>
094         <!-- Build with Java 1.5 -->
095        <pluginManagement>
096             <plugins>
097                <plugin>
098                    <groupId>org.apache.maven.plugins</groupId>
099                    <artifactId>maven-compiler-plugin</artifactId>
100                    <configuration>
101                        <source>1.5</source>
102                        <target>1.5</target>
103                    </configuration>
104                </plugin>
105            </plugins>
106        </pluginManagement>
107     </build>
108 
109</project>

The only items of interest in the POM are:

  • We depend on the CXF v2.1.2 client libraries and the (most excellent) SLF4J logging system
  • We invoke the cxf-codegen-plugin to run wsdl2java to generate our Java stub code into ${basedir}/target/generated/src/main/java
  • We use the build-helper-maven-plugin so that Maven can compile from two source directories (normally Maven just compiles what's in ${basedir}/src/main and not ${basedir}/target/generated/src, so we tell Maven to go compile the generated stub code too)
  • We use the maven-assembly-plugin to create a final JAR containing all necessary dependencies, which Maven will create as weather-client-jar-with-dependencies.jar when we perform a mvn assembly:assembly

At this point, even though I had no code in the project, I ran mvn assembly:assembly to build the Java stubs from the WSDL file. The output is in the generated source directory mentioned earlier, in case you want to go poke at it.

The Client Code

Once we have the autogenerated stubs we can use them in a real Java program. Before you can use the stubs, you have to identify what the actual service object is. You can find out by looking at the generated stub code and see which Java class extends Service. That will be the service interface that you call in your client. In this case, the service is called simply "Weather".

Without further ado, here's the code I wrote to invoke the SOAP service:

weather-client/src/main/java/com/logicsector/soapclient/SoapClient.java

01package com.logicsector.soapclient;
02 
03import java.text.SimpleDateFormat;
04import java.util.Date;
05import java.util.List;
06 
07 import org.slf4j.Logger;
08 import org.slf4j.LoggerFactory;
09  
10import com.cdyne.ws.weatherws.Forecast;
11import com.cdyne.ws.weatherws.ForecastReturn;
12import com.cdyne.ws.weatherws.POP;
13import com.cdyne.ws.weatherws.Temp;
14import com.cdyne.ws.weatherws.Weather;
15import com.cdyne.ws.weatherws.WeatherSoap;
16 
17 public class SoapClient {
18    private static final Logger           LOGGER      = LoggerFactory.getLogger(SoapClient.class);
19    private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("EEEE, MMMM d yyyy");
20 
21    public static void main(String[] args) {
22        try {
23            LOGGER.debug("Creating weather service instance (Note: Weather = Service subclass)...");
24             long start = new Date().getTime();
25            // Get a reference to the SOAP service interface.
26            Weather weatherService = new Weather();
27            WeatherSoap weatherSoap = weatherService.getWeatherSoap();
28            // An alternate way to get the SOAP service interface; includes logging interceptors.
29            // JaxWsProxyFactoryBean factory = new org.apache.cxf.jaxws.JaxWsProxyFactoryBean();
30            // factory.setServiceClass(WeatherSoap.class);
31            // factory.setAddress("http://ws.cdyne.com/WeatherWS/Weather.asmx");
32            // factory.getInInterceptors().add(new org.apache.cxf.interceptor.LoggingInInterceptor());
33            // factory.getOutInterceptors().add(new org.apache.cxf.interceptor.LoggingOutInterceptor());
34            // WeatherSoap weatherSoap = (WeatherSoap) factory.create();
35            long end = new Date().getTime();
36            LOGGER.debug("...Done! weatherService instance: {}", weatherService);
37            LOGGER.debug("Time required to initialize weather service interface: {} seconds", (end - start) / 1000f);
38 
39            // Send a SOAP weather request for zip code 94025 (Menlo Park, CA, USA).
40            LOGGER.debug("weatherSoap instance: {}", weatherSoap);
41            start = new Date().getTime();
42            ForecastReturn forecastReturn = weatherSoap.getCityForecastByZIP("94025");
43             end = new Date().getTime();
44            LOGGER.debug("Time required to invoke 'getCityForecastByZIP': {} seconds", (end - start) / 1000f);
45            LOGGER.debug("forecastReturn: {}", forecastReturn);
46            LOGGER.debug("forecastReturn city: {}", forecastReturn.getCity());
47            LOGGER.debug("forecastReturn state: {}", forecastReturn.getState());
48            LOGGER.debug("forecastReturn result: {}", forecastReturn.getForecastResult());
49            LOGGER.debug("forecastReturn response text: {}", forecastReturn.getResponseText());
50            LOGGER.debug("");
51            List<Forecast> forecasts = forecastReturn.getForecastResult().getForecast();
52            for (Forecast forecast : forecasts) {
53                LOGGER.debug("  forecast date: {}", DATE_FORMAT.format(forecast.getDate().toGregorianCalendar().getTime()));
54                LOGGER.debug("  forecast description: {}", forecast.getDesciption());
55                Temp temps = forecast.getTemperatures();
56                LOGGER.debug("  forecast temperature high: {}", temps.getDaytimeHigh());
57                LOGGER.debug("  forecast temperature low: {}", temps.getMorningLow());
58                POP pop = forecast.getProbabilityOfPrecipiation();
59                LOGGER.debug("  forecast precipitation day: {}%", pop.getDaytime());
60                LOGGER.debug("  forecast precipitation night: {}%", pop.getNighttime());
61                LOGGER.debug("");
62            }
63            LOGGER.debug("Program complete, exiting");
64        }
65        catch (Exception e) {
66            LOGGER.error("An exception occurred, exiting", e);
67        }
68    }
69 
70 }

Note that we're importing the stubs as import com.cdyne.ws.weatherws.Forecast, etc, within the client program. The client is also hard-coded to get the weather report from the 94025 zip code, although you could easily alter the client to take the zip code as a command-line argument.

The All-Important CXF Client Configuration File

This is the part of the development process that threw me for a loop. I didn't see any CXF documentation that indicated a cxf.xml file needs to be in the classpath of the client, so I hadn't included one in the project. My client program kept failing with a (very cryptic, very unhelpful) CXF BusException (the complete message was org.apache.cxf.BusException: No binding factory for namespace http://schemas.xmlsoap.org/soap/ registered, which I'm mentioning here in case anyone else is Googling with the same problem).

Sure, there are plenty of CXF tutorials on the Internet, but they mostly seem to build a client and a service in the same project (sharing a cxf.xml file) and I had assumed the configuration file was for configuring only the server. Silly me.

It took me several days of Googling, trying different JAR dependencies, Googling again, testing various source code modifications, Googling some more, asking for help on the cxf-user mail list — all to no avail.

Eventually, while reading the solution to an unrelated problem, I finally discovered the cause. On start-up for a server OR A CLIENT, the CXF system looks for a cxf.xml file, and fails without it. Just for the record, the BusException message is incredibly unhelpful. Grrr!! I think it should read something like org.apache.cxf.BusException: No binding factory for namespace http://schemas.xmlsoap.org/soap/ registered (did you include a cxf.xml file somewhere in the classpath?), or some such.

Anyhoo, here's the CXF configuration file I used. Not much to it. It's basically just a trivial Spring configuration with three lines of imports.

weather-client/src/main/resources/cxf.xml

04 
06 
08  
10 
11    <import resource="classpath:META-INF/cxf/cxf.xml"/>
12    <import resource="classpath:META-INF/cxf/cxf-extension-soap.xml"/>
13    <import resource="classpath:META-INF/cxf/cxf-servlet.xml"/>
14 
15 </beans>

Good thing this is easier than Axis.

Logging Configuration

For completeness, I've included the logging file I used. Since we're using LOG4J as the logging layer under SLF4J, we need to supply a log4j.properties file.

weather-client/src/main/resources/log4j.properties

1log4j.rootCategory=WARN, CONSOLE
2log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
3log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
4log4j.appender.CONSOLE.layout.ConversionPattern=[%d{ABSOLUTE} %-5p %c{1}]: %m%n
5log4j.logger.com.logicsector=DEBUG

Building and Running the Client

Once you have all the code in order, it's time to build it. From within the weather-client directory, just use the previously-mentioned Maven build command to create a JAR file with dependencies:

mvn assembly:assembly

Now we can run the client. As mentioned previously, the client is hard-coded to get the weather report for the 94025 zip code (Menlo Park, California). From within the weather-client directory, the following command will start the client and invoke the service:

java -cp target/weather-client-jar-with-dependencies.jar com.logicsector.soapclient.SoapClient

If everything went smoothly you should see output something like this:

01[17:31:06,278 DEBUG SoapClient]: Creating weather service instance (Note: Weather = Service subclass)...
02Oct 18, 2008 5:31:08 PM org.apache.cxf.service.factory.ReflectionServiceFactoryBean buildServiceFromWSDL
03INFO: Creating Service {http://ws.cdyne.com/WeatherWS/}Weather from WSDL: file:/c:/Projects/weather-client/src/main/wsdl/weat
04her.wsdl
05[17:31:08,325 DEBUG SoapClient]: ...Done! weatherService instance: com.cdyne.ws.weatherws.Weather@754fc
06 [17:31:08,325 DEBUG SoapClient]: Time required to initialize weather service interface: 2.047 seconds
07[17:31:08,325 DEBUG SoapClient]: weatherSoap instance: org.apache.cxf.jaxws.JaxWsClientProxy@6458a6
08 [17:31:08,825 DEBUG SoapClient]: Time required to invoke 'getCityForecastByZIP': 0.5 seconds
09[17:31:08,825 DEBUG SoapClient]: forecastReturn: com.cdyne.ws.weatherws.ForecastReturn@aea710
10 [17:31:08,825 DEBUG SoapClient]: forecastReturn city: Menlo Park
11[17:31:08,825 DEBUG SoapClient]: forecastReturn state: CA
12[17:31:08,825 DEBUG SoapClient]: forecastReturn result: com.cdyne.ws.weatherws.ArrayOfForecast@5a2eaa
13 [17:31:08,825 DEBUG SoapClient]: forecastReturn response text: City Found
14[17:31:08,825 DEBUG SoapClient]:
15[17:31:08,825 DEBUG SoapClient]:   forecast date: Friday, October 17 2008
16 [17:31:08,825 DEBUG SoapClient]:   forecast description: Sunny
17[17:31:08,825 DEBUG SoapClient]:   forecast temperature high: 82
18[17:31:08,825 DEBUG SoapClient]:   forecast temperature low: 58
19[17:31:08,825 DEBUG SoapClient]:   forecast precipitation day: 00%
20 [17:31:08,825 DEBUG SoapClient]:   forecast precipitation night: 00%
21 [17:31:08,825 DEBUG SoapClient]:
22[17:31:08,825 DEBUG SoapClient]:   forecast date: Saturday, October 18 2008
23 [17:31:08,825 DEBUG SoapClient]:   forecast description: Sunny
24[17:31:08,841 DEBUG SoapClient]:   forecast temperature high: 73
25[17:31:08,841 DEBUG SoapClient]:   forecast temperature low: 55
26[17:31:08,841 DEBUG SoapClient]:   forecast precipitation day: 00%
27 [17:31:08,841 DEBUG SoapClient]:   forecast precipitation night: 00%
28 [17:31:08,841 DEBUG SoapClient]:
29[17:31:08,841 DEBUG SoapClient]:   forecast date: Sunday, October 19 2008
30 [17:31:08,841 DEBUG SoapClient]:   forecast description: Partly Cloudy
31[17:31:08,841 DEBUG SoapClient]:   forecast temperature high: 70
32[17:31:08,841 DEBUG SoapClient]:   forecast temperature low: 55
33[17:31:08,841 DEBUG SoapClient]:   forecast precipitation day: 00%
34 [17:31:08,841 DEBUG SoapClient]:   forecast precipitation night: 00%
35 [17:31:08,841 DEBUG SoapClient]:
36[17:31:08,841 DEBUG SoapClient]:   forecast date: Monday, October 20 2008
37 [17:31:08,841 DEBUG SoapClient]:   forecast description: Partly Cloudy
38[17:31:08,841 DEBUG SoapClient]:   forecast temperature high: 70
39[17:31:08,841 DEBUG SoapClient]:   forecast temperature low: 53
40[17:31:08,841 DEBUG SoapClient]:   forecast precipitation day: 00%
41 [17:31:08,841 DEBUG SoapClient]:   forecast precipitation night: 00%
42 [17:31:08,841 DEBUG SoapClient]:
43[17:31:08,841 DEBUG SoapClient]:   forecast date: Tuesday, October 21 2008
44 [17:31:08,856 DEBUG SoapClient]:   forecast description: Sunny
45[17:31:08,856 DEBUG SoapClient]:   forecast temperature high: 73
46[17:31:08,856 DEBUG SoapClient]:   forecast temperature low: 54
47[17:31:08,856 DEBUG SoapClient]:   forecast precipitation day: 00%
48 [17:31:08,856 DEBUG SoapClient]:   forecast precipitation night: 10%
49 [17:31:08,856 DEBUG SoapClient]:
50[17:31:08,856 DEBUG SoapClient]:   forecast date: Wednesday, October 22 2008
51 [17:31:08,856 DEBUG SoapClient]:   forecast description: Sunny
52[17:31:08,856 DEBUG SoapClient]:   forecast temperature high: 74
53[17:31:08,856 DEBUG SoapClient]:   forecast temperature low: 55
54[17:31:08,856 DEBUG SoapClient]:   forecast precipitation day: 00%
55 [17:31:08,856 DEBUG SoapClient]:   forecast precipitation night: 00%
56 [17:31:08,856 DEBUG SoapClient]:
57[17:31:08,856 DEBUG SoapClient]:   forecast date: Thursday, October 23 2008
58 [17:31:08,856 DEBUG SoapClient]:   forecast description: Sunny
59[17:31:08,856 DEBUG SoapClient]:   forecast temperature high: 73
60[17:31:08,856 DEBUG SoapClient]:   forecast temperature low: 55
61[17:31:08,856 DEBUG SoapClient]:   forecast precipitation day: 00%
62 [17:31:08,856 DEBUG SoapClient]:   forecast precipitation night: 00%
63 [17:31:08,856 DEBUG SoapClient]:
64[17:31:08,856 DEBUG SoapClient]: Program complete, exiting

Interestingly, it takes 2 seconds on my machine to initialize the interface, which seems like a really long time. CXF is probably doing a lot of stuff under the covers, but still, 2 seconds is forever in computer time.

The call to the weather service interface, once initialized, takes about half a second every time, which includes marshalling a SOAP request, sending it over the internet, receiving the response, and unmarshalling its contents. Not too bad I guess.

Concluding Thoughts

Hopefully this example will form the basis of your next SOAP client. It really is pretty easy once you see a complete and working example.

If you were ambitious, parts of this code could easily be incorporated into a web application that provides a weather report for the user. You'd simply create a servlet that takes the zip code as a parameter, invokes the SOAP service, and shows the weather report in the response. In other words, the technique of calling the SOAP service would be the same even if this was a web application.

Well, that's the end of my post about creating a CXF client with Maven. I'd love to read your comments if you found this post helpful.