Prototyping think project! SOAP clients with Jython

Though I mostly use (server-sided) Java technology in my day-to-day work, I am then and now intrigued by Python as a straightforward, accessible scripting language which is easy to learn, easy to teach and immensely powerful in many situations. However, there are situations in which I just wished to have frameworks from the Java world available in Python… SOAP, to start with. Fortunately, Jython has been around for quite a while now, being a (re-)implementation of Python on top of the Java virtual machine, allowing for doing just that. And as we’re already using this in our internal development for certain purposes (Jython scripts to extend existing Java applications), I finally wanted to see how things work the other way ’round (using Java modules to enhance Python apps running in Jython). Here we go…

Scenario at hand

For quite a while I have been prototyping an application to interface with the think project! online collaboration platform. This is a very powerful, highly configurable online platform mainly aiming at customers in the construction industry, and it comes with what seems a well-crafted, stable SOAP API, which is just logical: In an age of “cross-enterprise collaboration”, enabling people to communicate and interact with each other, ideally on top of a consistent shared database, seems essential, and making such things possible requires portable interfaces that allow for easy, fully-fledged integrations of applications into existing technology stacks and workflows. The think project! SOAP API does just that, even though (just natural, I guess, given the overall nature of the platform it self) it is anything but trivial so there’s a learning curve.

I have been successfully working with the API from within Java but repeatedly failed to do the same using Python. Python, at least to me, feels way faster than Java when exploring how to work wit a new API. However, problems seem to be all the same all the time: When it comes to decent SOAP support, especially using WSDL which goes a bit beyond the plain standard functionality, most Python frameworks aren’t much fun. Unlike Java where there’s JAX-WS with a whole load of different implementations, where most of this technology already comes bundled with the usual application runtime platforms. So searching for a way to make the best of both worlds seamlessly work together seems a worthy approach, and Jython neatly fits in here.

Setting the stage

Throughout the next set of paragraphs, we will use Java tools and frameworks for building a (Java) SOAP client package which, by then, we will be deploying to a Jython runtime environment. We will try using these, in turns, to integrate this SOAP client package into Python/Jython scripts in order to explore functionality using the Jython runtime console.

So, let’s get started. What is required to move forth?

  • A reasonably up-to-date version of the Java Development Kit (JDK), to be downloaded here. I am still running a 1.6 version, though the examples outlined here should work with 1.7 too. If you’re not into Java development too much, be sure to have the JDK installed, not just the JRE. Difference between the both of them, very simply put: The JRE is just a runtime to, well, run existing Java applications, whereas the JDK is the piece of software required to build Java applications of your own.
  • Apache Maven, which, in its 3.0.x version, can be downloaded here. Roughly speaking, maven is a (Java) source project management tool, its purpose is to make any Java (SE/EE) project reliably build independent of any installation, development environment and the like. This includes things such as managing dependencies (internal projects or external third-party libraries needed by your application), handling source code generation (in example using WSDL descriptions, as you will see in a few moments) or managing automated unit testing. This is an astoundingly powerful tool which comes with quite a learning curve; fortunately you won’t need much of the complexity here.
  • Jython in its recent stable version, to be downloaded here. The jython-installer-XX.YY.ZZ.jar, as the name says, is an executable jar installing Jython to an arbitrary folder on your drive, which we will just try later. Mind to use the -installer not the -standalone binary which is apparently unable to use additional external libraries.
  • A programmers text editor. I am using Geany on a Linux system throughout this, Kate (KDE, open-source) or Sublime Text 2 (GTK, proprietary, paid, yet astoundingly cool) also might be interesting choices. Whatever you use, it should be capable of effectively working with project folders containing subfolders and source files as well as providing basic editing support for XML, Java and Python files. In the end, notepad and Windows Explorer also would do, but perhaps it won’t be much fun. ;)

Get these things installed according to its individual installation instructions. To follow this write-up, you should be able to open a Terminal session (Linux/Unix) or a command prompt (Windows) and have both javac and maven up and running just like that:

kr@kaleid:~$ mvn --version
Apache Maven 3.0 (r1004208; 2010-10-04 13:50:56+0200)
Java version: 1.6.0_33
Java home: /home/kr/JavaRuntime/jdk1.6.0_33/x86_64/jre
Default locale: de_DE, platform encoding: UTF-8
OS name: "linux" version: "3.8.0-11-generic" arch: "amd64" Family: "unix"
 
kr@kaleid:~$ javac -version
javac 1.6.0_33

On both platforms, this will require setting your PATH right. One additional thing here for users of Ubuntu or Debian GNU/Linux variants: Feel free to install at least the JDK using your distribution package repository. This might give you OpenJDK instead of the Sun/Oracle JDK but that possibly should be fine. However, even though it’s available, I firmly object against installing maven this way – this immediately will pull a whole load of packages including Debian/Ubuntu packaged versions of maven plugin artifacts. As maven itself provides a well thought-out package management right for these things, using the Debian one on top of it won’t make this more fun. So, simply put: Use vanilla maven off apache.org. ;)

Running with maven (I): Generating the stub artifact

If you want to quick-start this, you might want to download the skeleton file and start right away: tpsoapclient.tar Otherwise, we will start out building an empty project using the maven-archetype-quickstart:

mvn archetype:generate -DgroupId=net.z428 -DartifactId=tpsoapclient \
-DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false

Depending upon your machine, your internet connectivity and whether or not you used maven before, some time might pass while maven downloads a bunch of plugins and creates an empty project structure for you. If you’re a first-time maven user, you right here might for the first time encounter the feeling of maven downloading “half the internet” for doing its tasks – don’t be scared, though it’s a bit painful at times, this is how things are supposed to work. Most of the plugins are pretty small, and, being a plugin to support code re-use and small, interdependent modules, maven itself of course pretty much lives up to that idea. ;) While working offline with maven, mvn -o will force maven to not try downloading anything – which might fail with a new installation of maven but perfectly work out once your local maven repository has grown a bit and includes sufficiently up-to-date versions of most of the plugins and dependencies you use.

Anyway: As soon as the mvn archetype:generate command has finished, there will be an empty project folder tpsoapclient looking similar to this (names of folders depending upon your environment of course):

01-empty-struct

I’ll keep myself from explaining what actually is in there because. For what we do here, we won’t need much of this. By now, you give it a first shot and use maven to build this project. To do so, enter the tpsoapclient folder (the one that pom.xml lives in) and try mvn clean install. You should be seeing something like that:

kr@kaleid:~/workspace/tpsoapclient$ mvn clean install
[INFO] Scanning for projects...
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building tpsoapclient 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO] 
[INFO] --- maven-clean-plugin:2.4.1:clean (default-clean) @ tpsoapclient ---
[INFO] Deleting /home/kr/workspace/tpsoapclient/target
[.......................]
[INFO] --- maven-jar-plugin:2.3.1:jar (default-jar) @ tpsoapclient ---
[INFO] Building jar: /home/kr/workspace/tpsoapclient/target/tpsoapclient-1.0-SNAPSHOT.jar
[INFO] 
[INFO] --- maven-install-plugin:2.3.1:install (default-install) @ tpsoapclient ---
[INFO] Installing /home/kr/workspace/tpsoapclient/target/tpsoapclient-1.0-SNAPSHOT.jar to /home/kr/.m2/repository/net/z428/tpsoapclient/1.0-SNAPSHOT/tpsoapclient-1.0-SNAPSHOT.jar
[INFO] Installing /home/kr/workspace/tpsoapclient/pom.xml to /home/kr/.m2/repository/net/z428/tpsoapclient/1.0-SNAPSHOT/tpsoapclient-1.0-SNAPSHOT.pom
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------

Some of the output might differ on your machine, same as the time this build takes, but eventually it will comme to a successful end and, by then, make your tpsoapclient folder look like this:

02-target

Notice two things in there:

  • A target subfolder has been created. In maven projects, this usually is the place that contains anything automatically generated by maven and its plugins. This each and every time is removed during running mvn clean install. so modifying any files in there usually ain’t a good idea.
  • There’s a tpsoapclient-1.0-SNAPSHOT.jar in that folder, too, amongst a bunch of subfolders. In the end, if you were to include this client into an existing Java application, this is the jar that contains any resource (compiled .java files, images, configuration, …) coming from this project. The “1.0-SNAPSHOT” part of the name corresponds with the version specified in the tpsoapclient/pom.xml.

If either your maven run fails or your folder looks somewhat different after this, you might double check your installation, especially ensure you have internet access and actually a working JDK installed. Otherwise: Congratulations, you just used maven to build and package your first Java jar module. So now to move on.

Running with maven (II): Building and packaging WSDL stubs

As outlined above, maven at its core aims at making project builds reproducible. This includes keeping track of any dependencies a project has, and it includes (re-)generating Java stub classes for web services, XML mappings and the like, including (of course) any specialities required to do so for given services. In the Java EE world, there’s a bunch of different frameworks and standards dealing with SOAP web services. I’ll choose Apache CXF here for now, a mature, powerful, open source web service stack covering most of the aspects relevant for this particular scenario. Our client will use it, so the maven way of doing so is declaring a dependency in tpsoapclients pom.xml. So, open this file in the editor of your choice and add the following snippet right into its dependencies element:

    <dependency>
      <groupid>org.apache.cxf</groupid>
      <artifactid>cxf-rt-frontend-jaxws</artifactid>
      <version>2.7.1</version>
    </dependency>
    <dependency>
      <groupid>org.apache.cxf</groupid>
      <artifactid>cxf-rt-transports-http</artifactid>
      <version>2.7.1</version>
    </dependency>
    <dependency>
       <groupid>org.apache.cxf</groupid>
       <artifactid>cxf-rt-bindings-soap</artifactid>
       <version>2.7.1</version>
    </dependency>

From a maven declarative point of view, this pins down that this project (tpsoapclient), in order to correctly build and run, requires these three Apache CXF libraries, version exactly 2.7.1 each. This includes any transitive dependencies, in other words libraries any of the three of these modules in turns require. From a technical point of view, this definition implies that, whenever one tries to build this project, maven will try to find and download these libraries and its dependencies and make sure they’re around before trying to build the sources that belong to that project. This, in most maven projects, is the most basic configuration you’ll eventually be doing, and it’s the foundation for the SOAP client we’re about to build.

Next, however, is actually building SOAP stub classes to use. For these purposes, we know most of the Java SOAP frameworks provide some sort of tool to render WSDL definitions into Java classes. In the maven world, these things are provided as “plug-ins” integrated into the maven build process and used all along building the project. In case of Apache CXF, this is the cxf-codegen-plugin which, by now, we will include into our tpsoapclient/pom.xml just like that, right after the closing dependencies tag:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
  <build>
   <plugins><a href="http://dm.zimmer428.net/wp-content/uploads/2013/03/tpsoapclient.tar.gz">tpsoapclient.tar</a>
    <plugin>
     <groupid>org.apache.cxf</groupid>
     <artifactid>cxf-codegen-plugin</artifactid>
     <version>2.7.1</version>
     <executions>
      <execution>
       <id>generate-sources</id>
       <phase>generate-sources</phase>
       <configuration>
        <sourceroot>${project.build.directory}/generated-sources/cxf-codegen-plugin</sourceroot>
        <wsdloptions>
         <wsdloption>
          <wsdl>http://url/that/points/to/your/thinkProject.wsdl</wsdl>
          <extraargs>
           <extraarg>-exsh</extraarg>
           <extraarg>true</extraarg>
          </extraargs>
         </wsdloption>
        </wsdloptions>
       </configuration>
       <goals>
        <goal>wsdl2java</goal>
       </goals>
      </execution>
     </executions>
     <dependencies>
      <dependency>
       <groupid>org.apache.cxf</groupid>
       <artifactid>cxf-rt-bindings-soap</artifactid>
       <version>2.7.1</version>
      </dependency>
     </dependencies>
    </plugin>
   </plugins>
  </build>

Tough stuff, eh?

Well, don’t let the load of XML scare you away here. For now, there’s just a few things you need to know in order to get going:

  • Line 15, first and foremosts, points the plugin to the WSDL to be used. As seen, HTTP(S) URIs are fine for that, here, so this shouldn’t be much of a problem. Using think project! SOAP API, you might want to consult the SOAP API Reference Manual to see where this one’s to be found for your project instance.
  • Line 16 through 19 provide the option to add “additional arguments” to the process of SOAP stub generation. In the end, these things are passed directly to the actual stub generator invoked by this plugin and, in this case (-exsh), enforce the processing of implicit SOAP headers which by default is disabled yet required for the think project! SOAP API to work.
  • Line 12 defines where the generated Java stubs (.java files) will be stored. project.build.directory is a standard maven property pointing to, in this case, tpsoapclient/target/ which we have seen before already.

By now we have all it needs in order to have maven do some more substantial work for us, so let’s build this, again using mvn clean install:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
kr@kaleid:~/blog/tpsoapclient$ mvn clean install
[INFO] Scanning for projects...
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building tpsoapclient 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[..............]
[..............]
[..............]
[INFO] 
[INFO] --- cxf-codegen-plugin:2.7.1:wsdl2java (generate-sources) @ tpsoapclient ---
[INFO] 
[..............]
[..............]
[..............]
[INFO] --- maven-install-plugin:2.3.1:install (default-install) @ tpsoapclient ---
[INFO] Installing /home/kr/blog/tpsoapclient/target/tpsoapclient-1.0-SNAPSHOT.jar to /home/kr/.m2/repository/net/z428/tpsoapclient/1.0-SNAPSHOT/tpsoapclient-1.0-SNAPSHOT.jar
[INFO] Installing /home/kr/blog/tpsoapclient/pom.xml to /home/kr/.m2/repository/net/z428/tpsoapclient/1.0-SNAPSHOT/tpsoapclient-1.0-SNAPSHOT.pom
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 13.435s

Do this for the first time, and it’ll possibly take a bit longer, including downloading a bunch of Apache CXF related jar files. At some point however, you will see the output printed in line 11 (telling that cxf-codegen-plugin gets working), and in the end, looking at your tpsoapclient folder, you’ll see something like this:

03-target-stubs

You should notice that tpsoapclient/target/ now contains a subfolder generated-sources/cxf-codegen-plugin. This is where the cxf-codegen-plugin dumps its generated classes (as you saw earlier in the plugin definition, in sourceRoot). In there, now, you’ll find a subfolder com/thinkproject/ which houses all the stub classes representing the structures and services declared by the think project! SOAP API WSDL. You might, further, notice the tpsoapclient-1.0-SNAPSHOT.jar is slightly bigger than before, and if you feel like opening this with your favorite zip application (jars are just zip files), you’ll see a bunch of com/thinkproject/classes included. That’s how it should be.

Java developers familiar with maven possibly might object against the idea of using a dedicated jar artifact just for the reason of packaging automatically generated WSDL stubs. There are two reasons to consider this idea anyhow:

  • You get a clear definition of which SOAP framework your client stubs require. In a larger project environment, you eventually will have more dependencies than just this very few Apache CXF jars, and you’ll have a bunch of different plugins and components as well. This way, you very well know the SOAP client requires Apache CXF 2.7.1 and that one plugin configured in order to work.
  • Right now, our artifact is at version “1.0-SNAPSHOT”, which is the default value in pom.xml> projects generated this way, and obviously pointless. Using the SOAP stubs and assuming there might be different versions of the server-sided WSDL, setting the pom.xml version to something more meaningful that “1.0-SNAPSHOT” introduces a pretty straightforward, portable way of creating (and managing) stubs to different server versions that can be included into custom Java applications without requiring regeneration of the actual stubs (and eventually breaking things all along this path). All along with this, it is rather easy to pin down the fact that different versions of the server-sided WSDL might require different versions of the SOAP runtime, different extra arguments to the wsdl2java generator and the like.

Running with maven (III): Running a sample app

So far we built and packaged a client library to be included into an existing application. But we’d like to actually see any code running, wouldn’t we? You bet. So let’s give it a try. If you followed this write-up closely, in tpsoapclient/src/main/java/net/z428/ you should find a file App.java. Open it and modify its content so that it looks just like that:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package net.z428;
 
import com.thinkproject.*;
 
/**
 * Hello world!
 *
 */
public class App 
{
    public static void main( String[] args )
    {
        final Thinkproject_Service tp = new Thinkproject_Service();
        System.out.println( "Hello World. Running tp: " + tp.getThinkproject().getServerVersion(null,null) );
    }
}

In here, we simply added a global import telling the Java environment to make all classes within the package com.thinkproject available to this class (line 3) – this are the classes generated by cxf-codegen-plugin, and we added two lines instantiating and trying to use the local SOAP client framework with these stubs.

Right now, you might try building this using mvn clean install again – which will work, but won’t do anything. No real surprise here – it’s just building and packaging the sources, not actually running this application. You also can use maven in order to run this: Use mvn exec:java -Dexec.mainClass="net.z428.App" to produce output similar to this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
kr@kaleid:~/blog/tpsoapclient$ mvn exec:java -Dexec.mainClass="net.z428.App"
[INFO] Scanning for projects...
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building tpsoapclient 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO] 
[INFO] >>> exec-maven-plugin:1.2.1:java (default-cli) @ tpsoapclient >>>
[INFO] 
[INFO] < << exec-maven-plugin:1.2.1:java (default-cli) @ tpsoapclient <<<
[INFO] 
[INFO] --- exec-maven-plugin:1.2.1:java (default-cli) @ tpsoapclient ---
11.03.2013 21:02:57 org.apache.cxf.service.factory.ReflectionServiceFactoryBean buildServiceFromWSDL
INFO: Creating Service {http://www.thinkproject.com}thinkproject from WSDL: http://url-of-your/thinkproject/service1DS
11.03.2013 21:03:01 org.apache.cxf.phase.PhaseInterceptorChain doDefaultLogging
WARNUNG: Interceptor for {http://www.thinkproject.com}thinkproject#{http://www.thinkproject.com}getServerVersion has thrown exception, unwinding now
org.apache.cxf.interceptor.Fault: Could not send Message.
	at org.apache.cxf.interceptor.MessageSenderInterceptor$MessageSenderEndingInterceptor.handleMessage(MessageSenderInterceptor.java:64)
	at org.apache.cxf.phase.PhaseInterceptorChain.doIntercept(PhaseInterceptorChain.java:271)
[..........................]
[ .. various long stack traces included ...]
[..........................]	
    ... 8 more
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 5.935s
[ERROR] Failed to execute goal org.codehaus.mojo:exec-maven-plugin:1.2.1:java (default-cli) on project tpsoapclient: An exception occured while executing the Java class. null: InvocationTargetException: Could not send Message. HTTP response '401: Authorization Required' when communicating with http://url-of-your/thinkproject/service1DS -> [Help 1]
[ERROR] 
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR] 
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoExecutionException

This is how the outcome should look like. It obviously failed. But it failed the way it should fail. In the net.z428.App Java example above, we used to invoke the getServerVersion(null,null) method which, on a think project! SOAP service, requires the user to authenticate to the system first, so 401 Unauthorized is just what one would expect to see here.

Now, to end the maven part of this, all left to do is to make a distributable package of this application. If we were to include tpsoapclient-1.0-SNAPSHOT into any other existing maven project, right now we simply would add it as a dependency and be sure this would also include the Apache CXF dependencies. However, making this module available to Jython requires to also add all the dependencies (and its transitive dependencies, too), to the Jython environment prior to using this. Fortunately, maven offers facilities for this process, too. Two things to do to get there:

(a) At first, within your tpsoapclient/src/main/ folder, create a new subfolder called assembly. In there, create an empty new file named assembly.xml to be filled with this content:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<assembly>
        <id>bin</id>
        <!-- Generates a zip package containing the needed files -->
        <formats>
                <format>zip</format>
        </formats>
 
        <!-- Adds dependencies to zip package under lib directory -->
        <dependencysets>
                <dependencyset>
                        <!-- Project artifact is not copied under library directory since it is added to the root 
                                directory of the zip package. -->
                        <useprojectartifact>false</useprojectartifact>
                        <outputdirectory>.</outputdirectory>
                        <unpack>false</unpack>
                </dependencyset>
        </dependencysets>
 
        <filesets>
                <!-- Adds startup scripts to the root directory of zip package. The startup scripts are located 
                        to src/main/scripts directory as stated by Maven conventions. -->
                <fileset>
                        <directory>${project.build.scriptSourceDirectory}</directory>
                        <outputdirectory></outputdirectory>
                        <includessentially is a large Java application file including everything needed to run Python scripts on top of the Java platformes>
                                <include>startup.*</include>
 
                </includessentially></fileset>
                <!-- adds jar package to the root directory of zip package -->
                <fileset>
                        <directory>${project.build.directory}</directory>
                        <outputdirectory></outputdirectory>
                        <includes>
                                <include>*.jar</include>
                        </includes>
                </fileset>
        </filesets>
</assembly>

I will not explain the content of this file except for a very few words: The tool used here is mavens assembly:assembly plugin, which requires a so-called “assembly descriptor” in order to know what to do. All along using maven, you’ll sooner or later end up with a bunch of standard files for these purposes, and this is my default assembly.xml used whenever needed without really touching it.

(b) Same mostly goes for the next part of XML, to be added to tpsoapclient/pom.xml into the build/plugins section just below the cxf-codegen-plugin part we added earlier:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
 
            <!-- The configuration of maven-assembly-plugin -->
            <plugin>
                <groupid>org.apache.maven.plugins</groupid>
                <artifactid>maven-assembly-plugin</artifactid>
                <version>2.2.2</version>
                <!-- The configuration of the plugin -->
                <configuration>
                    <!-- Specifies the configuration file of the assembly plugin -->
                    <descriptors>
                        <descriptor>src/main/assembly/assembly.xml</descriptor>
                    </descriptors>
                </configuration>
            </plugin>
            <!-- The configuration of maven-jar-plugin -->
            <plugin>
                <groupid>org.apache.maven.plugins</groupid>
                <artifactid>maven-jar-plugin</artifactid>
                <version>2.3.1</version>
                <!-- The configuration of the plugin -->
                <configuration>
                    <!-- Configuration of the archiver -->
                    <archive>
                        <!-- Manifest specific configuration -->
                        <manifest>
                            <!-- Classpath is added to the manifest of the created jar file. -->
                            <addclasspath>true</addclasspath>
                            <!-- Configures the classpath prefix. This configuration option is used to specify that 
                    								all needed libraries are found under ./ directory. -->
                            <classpathprefix>./</classpathprefix>
                            <!-- Specifies the main class of the application -->
                            <mainclass>net.z428.App</mainclass>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>

Actually I did a bit of cheating in here inasmuch as that this actually ain’t one but two different plugins: There’s the maven-assembly-plugin, lines 3 to 14, which doesn’t include much more configuration than just a pointer to the assembly.xml file we just created. And there’s another plugin, maven-jar-plugin, lines 16 to 36. Again, this configuration is something you’ll most likely copy-and-paste between different projects whenever needed, mainclass (line 32) being the only on thing of real interest: If you want to build an “executable jar” that can be directly launched using the java -jar command, this option tells maven to build a jar file with this very class (in our example net.z428.App) to be started by default.

All these things in place, within tpsoapclient invoke mvn clean install assembly:assembly. Output, by now a bit familiar already, should look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
kr@kaleid:~/blog/tpsoapclient$ mvn clean install assembly:assembly
[INFO] Scanning for projects...
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building tpsoapclient 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO] 
[INFO] --- maven-clean-plugin:2.4.1:clean (default-clean) @ tpsoapclient ---
[INFO] Deleting /home/kr/blog/tpsoapclient/target
[INFO] 
[.....................]
[.....................]
[.....................]
[INFO] 
[INFO] --- maven-jar-plugin:2.3.1:jar (default-jar) @ tpsoapclient ---
[INFO] Building jar: /home/kr/blog/tpsoapclient/target/tpsoapclient-1.0-SNAPSHOT.jar
[INFO] 
[INFO] < << maven-assembly-plugin:2.2.2:assembly (default-cli) @ tpsoapclient <<<
[INFO] 
[INFO] --- maven-assembly-plugin:2.2.2:assembly (default-cli) @ tpsoapclient ---
[INFO] Reading assembly descriptor: src/main/assembly/assembly.xml
[INFO] Building zip: /home/kr/blog/tpsoapclient/target/tpsoapclient-1.0-SNAPSHOT-bin.zip
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 19.828s

You hopefully see (starting with line 18) that maven-assembly-plugin started doing its work, resulting (line 22) in building a .zip file living in tpsoapclient/target/, resulting in this folder structure in tpsoapclient by now:

04-assembly

If anything failed during the last maven run, most likely you should double-check whether your pom.xml is well-formed. Typos in XML, such as double start or end tags, are most likely the cause of problems all along the lines up to now. If there’s a tpsoapclient-1.0-SNAPSHOT-bin.zip, one last shot to see it’s working the way it is supposed to. Simply unzip this thing anywhere, in example to your /tmp folder, which should result in a tpsoapclient-1.0-SNAPSHOT folder containing roughly 20 jars including the tpsoapclient-1.0-SNAPSHOT.jar containing the think project! SOAP stubs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
kr@kaleid:/tmp/tpsoapclient-1.0-SNAPSHOT$ ls -l
insgesamt 5160
-rw-r--r-- 1 kr kr   43581 Mär 11 08:46 asm-3.3.1.jar
-rw-rw-r-- 1 kr kr 1053764 Jan 22 11:47 cxf-api-2.7.1.jar
-rw-rw-r-- 1 kr kr  198631 Jan 22 11:47 cxf-rt-bindings-soap-2.7.1.jar
-rw-rw-r-- 1 kr kr   38532 Jan 22 11:47 cxf-rt-bindings-xml-2.7.1.jar
-rw-rw-r-- 1 kr kr  389576 Jan 22 11:47 cxf-rt-core-2.7.1.jar
-rw-rw-r-- 1 kr kr  117690 Jan 22 11:47 cxf-rt-databinding-jaxb-2.7.1.jar
-rw-rw-r-- 1 kr kr  378204 Jan 22 11:47 cxf-rt-frontend-jaxws-2.7.1.jar
-rw-rw-r-- 1 kr kr   65901 Jan 22 11:47 cxf-rt-frontend-simple-2.7.1.jar
-rw-rw-r-- 1 kr kr  236486 Jan 22 11:47 cxf-rt-transports-http-2.7.1.jar
-rw-rw-r-- 1 kr kr   79006 Jan 22 11:47 cxf-rt-ws-addr-2.7.1.jar
-rw-rw-r-- 1 kr kr  204106 Jan 22 11:47 cxf-rt-ws-policy-2.7.1.jar
-rw-rw-r-- 1 kr kr  223298 Jan 22 11:25 geronimo-javamail_1.4_spec-1.7.1.jar
-rw-rw-r-- 1 kr kr  876610 Jan 22 11:25 jaxb-impl-2.1.13.jar
-rw-rw-r-- 1 kr kr   71320 Jan 22 11:47 neethi-3.0.2.jar
-rw-rw-r-- 1 kr kr  182112 Jan 22 11:25 stax2-api-3.1.1.jar
-rw-r--r-- 1 kr kr  206133 Mär 11 14:02 tpsoapclient-1.0-SNAPSHOT.jar
-rw-rw-r-- 1 kr kr  479104 Jan 22 11:47 woodstox-core-asl-4.1.4.jar
-rw-rw-r-- 1 kr kr  148429 Jan 22 11:25 wsdl4j-1.6.2.jar
-rw-rw-r-- 1 kr kr   84091 Jan 22 11:25 xml-resolver-1.2.jar
-rw-rw-r-- 1 kr kr  162818 Jan 22 11:47 xmlschema-core-2.0.3.jar

By now, you should be able to run this using java -jar tpsoapclient-1.0-SNAPSHOT.jar in this folder and, again, see the 401 you just saw earlier:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
kr@kaleid:/tmp/tpsoapclient-1.0-SNAPSHOT$ java -jar tpsoapclient-1.0-SNAPSHOT.jar 
11.03.2013 21:03:58 org.apache.cxf.service.factory.ReflectionServiceFactoryBean buildServiceFromWSDL
INFO: Creating Service {http://www.thinkproject.com}thinkproject from WSDL: http://url-of-your/thinkproject/service1DS.wsdl
11.03.2013 21:04:01 org.apache.cxf.phase.PhaseInterceptorChain doDefaultLogging
WARNUNG: Interceptor for {http://www.thinkproject.com}thinkproject#{http://www.thinkproject.com}getServerVersion has thrown exception, unwinding now
org.apache.cxf.interceptor.Fault: Could not send Message.
[.....]
Caused by: org.apache.cxf.transport.http.HTTPException: HTTP response '401: Authorization Required' when communicating with http://url-of-your/thinkproject/service1DS
	at org.apache.cxf.transport.http.HTTPConduit$WrappedOutputStream.handleResponseInternal(HTTPConduit.java:1528)
	at org.apache.cxf.transport.http.HTTPConduit$WrappedOutputStream.handleResponse(HTTPConduit.java:1488)
	at org.apache.cxf.transport.http.HTTPConduit$WrappedOutputStream.close(HTTPConduit.java:1307)
	at org.apache.cxf.transport.AbstractConduit.close(AbstractConduit.java:56)
	at org.apache.cxf.transport.http.HTTPConduit.close(HTTPConduit.java:622)
	at org.apache.cxf.interceptor.MessageSenderInterceptor$MessageSenderEndingInterceptor.handleMessage(MessageSenderInterceptor.java:62)
	at org.apache.cxf.phase.PhaseInterceptorChain.doIntercept(PhaseInterceptorChain.java:271)
	at org.apache.cxf.endpoint.ClientImpl.doInvoke(ClientImpl.java:530)
	at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:463)
	at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:366)
	at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:319)
	at org.apache.cxf.frontend.ClientProxy.invokeSync(ClientProxy.java:96)
	at org.apache.cxf.jaxws.JaxWsClientProxy.invoke(JaxWsClientProxy.java:133)
	... 2 more

So now you just packaged and ran that client thing outside your maven environment. By now, you could deal with the net.z428.App class a bit more in-depth, add authentication credentials and actually explore the think project! SOAP API according to the reference manual, if you’re a Java developer. You also might want to build a Java EE web application / maven war artifact, add net.z42:tpsoapclient:1.0-SNAPSHOT as a dependency and use it from within other Java classes. That’s a good partial result but not what we actually wanted, …

Running with Jython (I): Starting and classpath considerations

… since we actually intended to use this stuff all along with Python scripts in Jython. So there’s more work ahead. Earlier, we (hopefully) downloaded the jython-installer-2.5.3.jar which now should be used to actually install and run Jython. All along the lines, it should be no surprise seeing java -jar jython-installer-2.5.3.jar is the way to go here. This will guide you through a GUI driven installer and should be rather straightforward; only care to be taken is to not choose “standalone” as the installation type. “Standard” (selected by default) should possibly do. By default, this will install to your home folder (i.e. /home/kr/jython2.5.3), you might leave it at that or remember where you actually installed it. ;) This will become your JYTHON_HOME folder and include everything related to Jython, same as a script to launch the interpreter and see what can be done:

kr@kaleid:~$ /home/kr/JavaRuntime/jython2.5.3/jython 
Jython 2.5.3 (2.5:c56500f08d34+, Aug 13 2012, 14:48:36) 
[Java HotSpot(TM) 64-Bit Server VM (Sun Microsystems Inc.)] on java1.6.0_33
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.version
'2.5.3 (2.5:c56500f08d34+, Aug 13 2012, 14:48:36) \n[Java HotSpot(TM) 64-Bit Server VM (Sun Microsystems Inc.)]'
>>>

If you’re feeling home in the Python world, chances are you’ll by now feel much more comfortable with things than before, following through all this Java stuff. Right here, you can feel free to do some explorations of what can be done with Jython out of the box. Recent Jython versions strive for compatibility with (C)Python versions of the same version numbers, so Jython 2.5.x should be compatible with Python 2.5.x, same about Jython 2.7.x / Python 2.7.x. So far there’s no Jython 3.x. There are (of course) differences between Jython and Python, and some seem more painful than others, but generally, some larger and more prominent frameworks (Django, to mention one) are proven to work with Jython reliably enough for productive use.

So, we now can figure out how to add our tpsoapclient-1.0-SNAPSHOT, all its content and all its dependencies (in other words: all the jar files our /tmp/tpsoapclient-1.0-SNAPSHOT folder contained before) to Jython and merrily code on. In Java, the infamous CLASSPATH is the place where to declare which classes (or jars or folders containing classes) the Java runtime knows about and, so, is capable of using. For our current purpose, we need to get the content of all these jars into the classpath used while running Jython.

However, right now, it gets a bit messy at the moment, mainy because in Jython there doesn’t seem to be a real clean way how to add additional jar files to the classpath without messing with the Java runtime itself. There seem some more or less hackish ways of getting this done, and I decided for the most “simple” one: The JYTHON_HOME/jython script has sort of a “development version detection” which adds any jars from JYTHON_HOME/javalib to the classpath. The corresponding section of JYTHON_HOME/jython reads like this:

CP=$JYTHON_HOME/jython_dev.jar
 
if [ -f "$CP" ] ; then
  # add necessary jars for command-line execution
  for j in "$JYTHON_HOME"/javalib/*.jar; do
    if [ "$CP" ]; then
      CP="$CP$CP_DELIMITER$j"
    else
      CP="$j"
    fi
  done
elif [ ! -f "$JYTHON_HOME"/jython.jar ] ; then
  echo "$0: $JYTHON_HOME contains neither jython-dev.jar nor jython.jar." >&2
  echo "Try running this script from the 'bin' directory of an installed Jython or " >&2
  echo 'setting $JYTHON_HOME.' >&2
  exit 1
else
  CP=$JYTHON_HOME/jython.jar
fi

So you’re left with two options to either change CP=$JYTHON_HOME/jython_dev.jar to CP=$JYTHON_HOME/jython.jar in this, well, disputable structure or, in JYTHON_HOME, rename jython.jar to jython_dev.jar, whichever seems easier. Done so, you should create a new folder JYTHON_HOME/javalib and dump all the jars from /tmp/tpsoapclient-1.0-SNAPSHOT right into.

Running with Jython (II): SOAP after all.

The rest’s just as much as launching Jython again and importing the right classes:

kr@kaleid:~$ ./JavaRuntime/jython2.5.3/jython 
Jython 2.5.3 (2.5:c56500f08d34+, Aug 13 2012, 14:48:36) 
[Java HotSpot(TM) 64-Bit Server VM (Sun Microsystems Inc.)] on java1.6.0_33
Type "help", "copyright", "credits" or "license" for more information.
>>> import com.thinkproject
>>> service = com.thinkproject.Thinkproject_Service()
>>> dir(service)
['Mode', 'SERVICE', 'Thinkproject', 'WSDLDocumentLocation', 'WSDL_LOCATION', '__class__', '__copy__', 
'__deepcopy__', '__delattr__', '__doc__', '__eq__', '__getattribute__', '__hash__', '__init__', '__ne__', 
'__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__str__', '__unicode__', 'addPort', 
'class', 'create', 'createDispatch', 'equals', 'executor', 'getClass', 'getExecutor', 'getHandlerResolver',
 'getPort', 'getPorts', 'getServiceName', 'getThinkproject', 'getWSDLDocumentLocation', 'handlerResolver', 
'hashCode', 'notify', 'notifyAll', 'ports', 'serviceName', 'setExecutor', 'setHandlerResolver', 'thinkproject', 
'toString', 'wait']
>>> tp = service.getThinkproject()
11.03.2013 21:08:53 org.apache.cxf.service.factory.ReflectionServiceFactoryBean buildServiceFromWSDL
INFO: Creating Service {http://www.thinkproject.com}thinkproject from WSDL: http://www.baulogis.com/v30/wsdl/tpws11DS.wsdl
>>> dir(tp)
['ENDPOINT_ADDRESS_PROPERTY', 'PASSWORD_PROPERTY', 'SESSION_MAINTAIN_PROPERTY', 'SOAPACTION_URI_PROPERTY',
 'SOAPACTION_USE_PROPERTY', 'USERNAME_PROPERTY', '__class__', '__copy__', '__deepcopy__', '__delattr__',
 '__doc__', '__eq__', '__getattribute__', '__hash__', '__init__', '__ne__', '__new__', '__reduce__',
 '__reduce_ex__', '__repr__', '__setattr__', '__str__', '__unicode__', 'binding', 'class', 'close',
 'createMember', 'deleteDraft', 'deleteUC', 'endpointReference', 'equals', 'freeSession', 'getAccountingData', 
'getAddress', 'getAddressIDs', 'getAddressTypes', 'getAllDraftIDs', 'getAllDrafts', 'getAttributeSpec', 
'getBinding', 'getBox', 'getClass', 'getClipboardFile', 'getClipboardFiles', 'getClipboardFilesUC', 'getCommunities',
 'getCommunity', 'getCommunityIDs', 'getDefaultLanguage', 'getDocFile', 'getDocument', 'getDocumentIDs',
 'getDocumentLink', 'getDocumentQueryIDs', 'getDocumentSpec', 'getDocumentSpecIDs', 'getDownloadLink', 'getDraft',
 'getEndpointReference', 'getEnvelope', 'getInvocationHandler', 'getMember', 'getMemberIDs', 'getMemberWithOption',
 'getProxyClass', 'getRequestContext', 'getResponseContext', 'getRole', 'getSatelliteLink', 'getServerVersion',
 'getSessionCommunity', 'getSessionID', 'getSessionIDUC', 'getSessionLanguage', 'getSessionMember', 'getUploadURL',
 'hashCode', 'isProxyClass', 'markBoxRead', 'mkDirUC', 'newProxyInstance', 'notify', 'notifyAll', 'renameUC',
'requestContext', 'responseContext', 'saveDraft', 'sendEnvelope', 'setSessionCommunity', 'setSessionLanguage',
 'toString', 'updateMember', 'validateFiles', 'wait']

At this point, we have sort of made it: What you see here is think project! SOAP API, used from within Jython, made available through Apache CXF and a set of Java runtime below the Jython/Python layer. Right now, you would be ready to log in, get yourself a session and do whatever you need to do. For other SOAP services, this might be a bit yet not completely different. Though in some ways interacting with Java objects in Jython does differ a bit from how you would use plain Python objects, it works. Plus, if you are (like me) using both Java and Jython, you are in the comfortable situation of re-using the same infrastructure across two different platforms which definitely is a Good Thing(TM).

Wrap-up and other considerations

So, let’s rush through things once more: By now you should have a clear idea how to use the JDK / maven / Apache CXF tool chain to generate, build, test-run client stubs for an existing SOAP service (described in an available WSDL). You should have an understanding how to dump these artifacts and its mandatory dependencies into a Jython environment, and you should have seen (or, best, experienced yourself) how to do some live coding in this setup. Asides this, you peeked into how to reproducibly build, package, distribute Java SE applications with a bunch of external dependencies.

But, what to do with this now? Well, the first obvious reason for trying this is re-using such code in an application based upon something like Django. Although in times of server-sided JavaScript the dust/hype has settled a bit, these frameworks still have an active, committed user base, and especially talking Django, I know quite a few who use this in a productive environment on top of the Java EE / Jython stack for several reasons, including easier interoperability with server sided Java components (EJBs, JMS and the like) or, as illustrated here, re-using some of the open-source Java frameworks provided by communities at Apache, Codehaus, Eclipse or ObjectWeb, most of them having matured to being robust, reliable open source stacks backed both by communities and commercial vendors. Following this approach, integrating services such as the think project! SOAP API with an application built atop a framework like Django becomes easier at least technically.

The second reason for trying this is day-to-day hacking. On most of the recent Linux distributions, Python seems to have become quite widespread talking administrative stuff, Ubuntu comes with a whole load of Python modules pre-installed, and, at least also thanks to platforms such as the Google Application Engine (Python, Java), more and more people are getting used to Python. If your devs are experienced using Python, maybe providing them with a Jython environment to work services like this drastically might ease things.

Then again, and this is where we’re talking about the drawbacks of this approach: It’s, at the core, Jython. Not Python. It’s mostly compatible, with an emphasis on “mostly”. It lacks a bit behind Python in terms of language versions in the 2.x, and though it has a Roadmap, it’s quite a bit more behind in terms of 3.x. If you’re into Python, there always will be things that won’t work in Jython. This, most obviously and most painfully, is important for any Python modules which are implemented in native C libraries not in Python – these will be completely useless in Jython. And, another thing: If you’re so far successfully running applications built on top of Django or Turbogears on (C)Python, moving to Jython at least will be a major change in your infrastructure, it will require you to touch new technology and, most important, it will introduce Java to your stack. You might or might not want that.

Personal conclusion: This approach makes perfect sense if you’re a bit experienced with server-sided Java or Jython or into prototyping something new, and it makes sense if you have Java frameworks at hand addressing issues you are missing in some of the Python frameworks.

In any other case, you might give it a try and see how far it gets you. It’s a bit of learning, but the whole Java/maven track can really well be automated away so it doesn’t get into your way too much. It possibly will be a matter of following this route (and accepting some additional effort) or hacking Python libraries to do as they should, or possibly completely finding another way leaving out the parts that won’t work as desired (in example going for XML-RPC or REST instead of SOAP where possible).

But that’s up to you and your requirements by then. Personally, by now I am curious to see a local Django installment working with think project! data on top of a Jython platform, and I also am looking forward to possibly using this runtime (Jython with “included” think project! client) in order to quickly teach other internal developers how to make use of the SOAP API. More to come, I guess…

Leave a Reply

Your email address will not be published. Required fields are marked *

CAPTCHA Image

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>