Monday, October 24, 2005

Creating a dotnet plugin for Maven

This blog charts my quest to create a dotnet plugin for maven 2 which can be used to compile/distribute/unit-test and build-ndoc-docco for dotnet projects.

I'm using maven 2 (the first formal release)

Creating the plugin from scratch

The command I used to create the plugin was:


C:\SVN\...\..\trunk>mvn archetype:create -DgroupId=engn -DartifactId=engn-maven-plugin-dotnet -DarchetypeArtifactId=maven-archetype-mojo \n
-Duser.home=C:\Work\SCM\CVS\CPDS\trunk\m2.user.home -DarchetypeVersion=1.0-alpha-2


Phew that was a bit of a mouthful. I initially struggled with this as maven started throwing an exception about searching for the RELEASE version of the maven-archetype-mojo archetype.
After some digging I found the only version of this archetype currently published was 1.0 alpha-2 so appended this to command line -DarchetypeVersion=1.0-alpha-2 and everything was hunky dory.

I then used mvn eclipse:eclipse to get my maven eclipse project in order and was ready to start developing......

The First Plugin

To get up to speed with the development cycle of Maven plugins I created a trivial sample which was:


package uk.co.engn.maven.plugin.test;

import org.apache.maven.model.Dependency;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.project.MavenProject;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Iterator;
import java.util.List;

/**
* Goal which touches a timestamp file.
*
* @goal testmymojo
*
* @phase process-sources
*/
public class TestMojo
extends AbstractMojo
{
/**
* Location of the file.
* @parameter expression="${project.build.directory}"
* @required
*/
private File outputDirectory;


/**
* @parameter expression="${project}"
* @required
* @readonly
*/
protected MavenProject project;

public void execute()
throws MojoExecutionException
{
this.getLog().info("Beginning Test Plugin....");

this.getLog().info("Inspecting project....");

List dependencies = project.getDependencies();

Iterator iter = dependencies.iterator();

while(iter.hasNext()){
Dependency d = (Dependency) iter.next();
this.getLog().info("Dependency:" + d.getArtifactId() + " " + d.getGroupId() + " v=" + d.getVersion() + "");
}

this.getLog().info("Done.");
}
}


All this does really is write out the names of the dependencies in my pom.

To be able to use this plugin I needed to install it in my local repository like so:


c:\project-dir>mvn install -Duser.home=C:\Work\SCM\CVS\CPDS\trunk\m2.user.home -e


I'm specifying my user.home on the command line as I've always had problems with maven picking up the settings for the proxy server I am behind. This way forces Maven to pick up my settings.xml.

If this succeeds then you should be able to get hold of the plugin in this manner:


c:\sample-project-dir-using-plugin>mvn engn:engn-maven-plugin-dotnet:testmymojo


And there we have it, the (important) output was:


[INFO] [engndotnet:testmymojo]
[INFO] Beginning Test Plugin....
[INFO] Inspecting project....
[INFO] Dependency:junit junit v=3.8.1
[INFO] Dependency:maven-project org.apache.maven v=2.0-alpha-3
[INFO] Dependency:maven-plugin-api org.apache.maven v=2.0-alpha-3
[INFO] Done.


My next entry will be on trying to do something useful with my plugin.

Thursday, September 15, 2005

How to publish an Axis webservice in Tomcat using Spring

I thought as noone has produced a good tutorial on how to combine axis and spring I would document my experiences as I try to get both working.

First things first: I've installed Axis v1.2.1 from the download site and set it up in my Tomcat 5.0.28 environment. HappyAxis.jsp is reporting all good. Setting these two fellas up is well documented on the respective apache sites so I'll start my commentary at the point of setting up new service.

Pre-requisites:

0. Add all of the below jars (or newer versions) to your WEB-INF/lib dir:

07/05/2005  21:14            55,147 activation.jar (from Sun, Java Activation Framework) 
14/09/2005 15:01 7,556 my-code.jar (this is where my app stuff lives)
14/06/2005 22:28 33,506 axis-ant.jar (Axis stuff)
14/06/2005 22:44 1,604,162 axis.jar (Axis stuff)
17/08/2005 17:36 188,671 commons-beanutils-1.7.0.jar (Spring stuff)
19/08/2005 15:51 518,641 commons-collections-3.0.jar (Spring stuff)
19/08/2005 15:51 139,966 commons-digester-1.7.jar (Spring stuff)
14/06/2005 22:28 71,442 commons-discovery-0.2.jar (Spring stuff)
17/08/2005 17:24 169,763 commons-lang-2.0.jar (Spring stuff)
14/06/2005 22:28 38,015 commons-logging-1.0.4.jar(Spring stuff)
14/06/2005 22:28 32,071 jaxrpc.jar (I got this from Suns Webservices ToolkitSuns Webservices toolkit, it was a pain to find)
14/06/2005 22:28 352,668 log4j-1.2.8.jar (Axis stuff)
09/09/2005 15:28 355,030 mail.jar (Sun mail jar)
14/06/2005 22:28 19,427 saaj.jar (Again part of this distro Suns Webservices ToolkitSuns Webservices toolkit, it was a pain to find)
07/09/2005 17:38 1,847,568 spring-1.2.4.jar (Dear friend Spring)
14/06/2005 22:28 126,771 wsdl4j-1.5.1.jar (I think this came with Axis but I may be wrong)


Steps for creating service:

1. Create an end-point for your service by extending the Spring class: ServletEndpointSupport

This was mine:

package com.drkw.cpds.webservices.axis.sample;

import java.rmi.RemoteException;

import org.springframework.context.ApplicationContext;
import org.springframework.remoting.jaxrpc.ServletEndpointSupport;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;

public class SampleAtom2ServiceServletEndPoint extends ServletEndpointSupport {

private ISampleAtom2Service _service = null;

public SampleAtom2ServiceServletEndPoint(){

WebApplicationContext ctx = this.getWebApplicationContext();
_service = (ISampleAtom2Service)ctx.getBean("sampleServiceImpl");
}

public Obligor[] getObligors() throws RemoteException {
return _service.getObligors();
}

public Obligor getObligor() throws RemoteException {
return _service.getObligor();
}

public User getUser() throws RemoteException {
return _service.getUser();
}

public User[] getUsers() throws RemoteException {
return _service.getUsers();
}

public void throwMeAnExecption() throws RemoteException {
try {
_service.throwMeAnException();
} catch(Exception ex){
throw new RemoteException( ex.getMessage(), ex );
}
}
}



As you can see I delegate everything to a real business implementation. As you can see from the code, this is my starting point to spring where I load my WebApplicationContext (this is analogous to the ApplicationContext interface except that it is keyed in the application store for you).

2. Add the spring listener configuration to your web.xml

<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>



3. Create an applicationContext.xml in the WEB-INF/ dir of your web app. It seems that Spring loads any file called application context by default. You can specify which files you want spring to load from a tag inside the web.xml but I have yet to venture this. Mine was exactly like this:


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="sampleServiceImpl" class="com.drkw.cpds.webservices.axis.sample.SampleAtom2Service" singleton="true"/>
</beans>


4. Define your service in the server-config.wsdd in the axis webapp dir in tomcat, like so:
(please note that this can be done using the admin client but I couldn't get that to work in this case so altered the file directly)

 <service name="Atom2SampleService" provider="java:RPC">
<requestFlow>
<handler type="soapmonitor"/>
</requestFlow>
<responseFlow>
<handler type="soapmonitor"/>
</responseFlow>
<parameter name="allowedMethods" value="*"/>
<parameter name="scope" value="Application"/>
<parameter name="className" value="com.drkw.cpds.webservices.axis.sample.SampleAtom2Service"/>
<!-- these are required if you have any POJO's you want to return above the standard primitives and java objects (String, Integer etc) -->
<beanMapping qname="myNS:Obligor" xmlns:myNS="urn:BeanService" languageSpecificType="java:com.drkw.cpds.webservices.axis.sample.Obligor"/>
<beanMapping qname="myNS:User" xmlns:myNS="urn:BeanService" languageSpecificType="java:com.drkw.cpds.webservices.axis.sample.User"/>

</service>


nb. If you have not set up the soapmonitor service/applet as described in the axis documentation then you will need remove the requestFlow and responseFlow elements.

5. Well that's it really. Fire-up your tomcat instance and check that all is well in the location of your web-app I found it invaluble to check the logs at this time. If all is well you should have something like this in them:

stdout.log

  INFO: Installing web application at context path /atom2 from URL file:C:\Program Files\Apache Software Foundation\Tomcat 5.0\webapps\atom2
- Root WebApplicationContext: initialization started
- Loading XML bean definitions from ServletContext resource [/WEB-INF/applicationContext.xml]
- Bean factory for application context [Root WebApplicationContext]: org.springframework.beans.factory.support.DefaultListableBeanFactory defining beans [sampleServiceImpl]; root of BeanFactory hierarchy
- 1 beans defined in application context [Root WebApplicationContext]
- JDK 1.4+ collections available
- Commons Collections 3.x available
- Unable to locate MessageSource with name 'messageSource': using default [org.springframework.context.support.DelegatingMessageSource@190ef12]
- Unable to locate ApplicationEventMulticaster with name 'applicationEventMulticaster': using default [org.springframework.context.event.SimpleApplicationEventMulticaster@32bd65]
- Unable to locate ThemeSource with name 'themeSource': using default [org.springframework.ui.context.support.ResourceBundleThemeSource@5a3923]
- Pre-instantiating singletons in factory [org.springframework.beans.factory.support.DefaultListableBeanFactory defining beans [sampleServiceImpl]; root of BeanFactory hierarchy]
- Creating shared instance of singleton bean 'sampleServiceImpl'
- Using context class [org.springframework.web.context.support.XmlWebApplicationContext] for root WebApplicationContext
- Root WebApplicationContext: initialization completed in 297 ms




localhost_((date)).log
2005-09-14 16:05:00 StandardContext[/atom2]Loading Spring root WebApplicationContext


After this I went on to test my service using C# (as this will be our main client) and it all ran like a dream. I've heard that there are many problems with RPC style service and dotnet but as yet (touches wood, hangs horseshoes up, prays west to Mecca) I haven't experienced this.

If anyone has any other comments configuration info to add, please post as comments.

I'm hoping my next post will be an example of how to consume my service with Spring and Axis......

Chris











Friday, August 05, 2005

Followup: Naven (Maybe Rookie)? Dotnet Continous Integration using Ivy, Nant and CruiseControl

Just another quick note, I've created an Nant plugin to modify the .proj with the results of an Ivy retrieval, adding all of you dependent libs as references. Will addit in when I find a place to publish the source....

Friday, July 08, 2005

Naven? Dotnet Continous Integration using Ivy, Nant and CruiseControl

I've recently been working on a project to produce a new/modified set of core services (SOAP/RMI) in Java. There was a further requirement during the project to expose these services to Dotnet clients.

We made a decision early on in the project to adopt Apache Maven 1.0 and CruiseControl
(+JUnit etc) for Continous Integration in the Java sections of the project as we had dependencies on many other teams for artifacts and in turn many teams were dependant on our published proxies. Thus versioning and build quality became very pertenant issues very quickly.

I'll cover our Maven joys and woes in another blog, however what became apparent from Maven and CruiseControl was that the continous integration environment bought us a massive amount in terms of keeping the code base stable, finding and fixing bugs quickly, Deploying distributions and designatiung responsibility when a developer broke a build.

After an aborted attempt to integrate our dotnet components into Maven (again a blog for another time) I was told by a collegue about a new Java based tool called Ivy which provided all the dependency management functionality of Maven + a bit more but in a separate Java library. As it was only this that was missing from our Nant scripts (all the SCM checkouts/Build etc were already in tasks) I decided to spend a few late nights trying to knock up an example of a Maven type build in dotnet.

The tasks I wanted my build to perform were:

- Checkout the newest version of the source from our SCM (Source Control Management) software.
- Download the dependant binaries (dll's) from a repository (our Maven repos primarily). Using Ivy.
- Build the dll's including a version number in the file name
(Example: "Company.System.Subsystem-0.0.0.1.dll" or "Company.System.Subsystem-SNAPSHOT.dll")
- Run the Unit Tests (NUnit) during the build process.
- Manage/Update the version numbers using the AssemblyInfo.cs.
- Depoly the artifact to our repository
- When distributing to clients build the documentation.
- Checkin the updated AssemblyInfo.cs to SCM.

To keep things as simple as possible I chose to use all of the naming conventions/build conventions from Maven so that the team could understand the build across the languages.



Task 1: Creating a Standard Structure in SCM

The first task I did then was to do a a little refactoring of the code base to make our dotnet stuff a bit more Maven (sharp intake of breath from the Microsoft faithful)

As I initially used the Dotnet naming convention for all our libraries the code existed in our SCM in a structure like this.

LibraryName/Company.System.Subsystem/**/*.cs
LibraryName/Company.System.Subsystem.UnitTest/**/*.cs

To make things a little easier for myself I changed this to be, again this was easy as I was still in the proposal stage for this idea. If I was changing the entirity of our dotnet codebase this may well not be possible. However the prinicpals that follow apply no matter how you format your code:

/* this contained all our source for the actual component */
trunk/svn-module/Company.System.Subsystem/src/**/*.cs

/* this contained all our source for the unit tests */
trunk/svn-module/Company.System.Subsystem/test/**/*.cs

/* this contained all the config stuff and extra files */
trunk/svn-module/Company.System.Subsystem/etc/**/*.config

Task 2: Getting stuff to compile with version numbers

Because adding version numbers to dll name causes havoc with Visual Studio's references mechanism I decided to use the
<csc>
Nant task rather than use the easier but more restrictive
<solution>
option. This allowed me to add references at build time whose filenames do not affect the compile. It also allowed me to specify where and what I wanted the resulting artifact to end up/be called.

Task 3: Using Ivy for dependency management/publishing

As I hinted at above. Ivy is a Java solution to managing dependencies at build time for a given library. Even though it is written in Java it is in no way limited to managing purely Java projectsas I hope to illustrate. It also does some nifty stuff like transitive dependencies (for a tutorial see ivy or maven2 explanation) and reports etc.

The basics of Ivy's model are that It uses 1 or n number of "resolvers" to locate your dependencies in a local or remote file system. In a configuration file you tell Ivy which libraries you need and it will use these "resolvers" to download your libraries from the remote file system so you can compile against them. It them allows you to publish the component you have just built to one of these file systems using its "resolvers". This is obviously a massive over simplification of the tool. But the thing to keep in mind is that before you do a build it will get the files that your build depends on and after it will publish the successful results of your build (dll's configs, xml files etc) so that others can reference them within their projects.

For a more detailed view on Ivy please see: Jayasoft's Docco

As I wanted to execute Ivy from within Nant I used the
<exec>
task to perform the retrieve and publish functions to/from the Ivy repository. See source on How this worked.

Task 4: Using Putty to publish the results of the build to a web server.

In my organisation access to all our boxes is generally through SSH. And thus to publish artifacts to a web server I needed to use SCP. For our Maven stuff this is done for us. But for the dotnet components I needed to create a hom grown solution.

Luckily the swiss army knife of remote terminals/ssh communication, Putty came to my rescue. The binaries that it has in its distribution are very happy to be used a command line. So wrapping this into another exec task I managed to publish my local ivy repository to our linux based maven repository. without any issues.

Where to go from here?

- This solution really caters for all of our immediate needs and can be extended to all of our library based dotnet projects but possible enhancements include:

- To work out how to compile a web project using Nant and this method.
- Create an Ant task to generate/modify the .proj Visual Studio file based on the result of the Ivy -retrieve command. Thus developers wouldn't have to manually add the references when they open a project. This would function much like the eclipse::generate-classpath goal in Maven 1.0.

Follow ups.

As I haven't included the configuration file/description for CruiseControl I'll do this in a follow up post but for those who want to dive straight in. I am using Java CruiseControl 2.2.1 with the nant build type.












Thursday, June 30, 2005