Thursday, April 24, 2008

Securing Subversion via LDAP -- A Followup

After some discussion of this on the Subversion mailing list we discovered that checking out the parent of a protected child will pull that child's contents to the local workspace. That probably isn't what you want so...

Let's say we have the repository paths .../parent and .../child and we want only a certain group of people to be able to checkout or update the child. In my initial pass at this I did the following:

<Location /svn/myRepo/parent>
Require valid-user
</Location>

<Location /svn/myRepo/parent/child>
Require group ...
</Location>

This will protect against the following:

svn ls http://myserver/svn/myRepo/parent/child
svn co http://myserver/svn/myRepo/parent/child

and commits against the child.

It will *not* protect against:

svn co http://myserver/svn/myRepo/parent

which will check out the child as it processes parent.

When we look in the apache logs we see a PROPFIND on the parent and some internal things but no mention of the child.

While digging through the mod_dav_svn source I discovered that, as it traverses the directory tree, it does an internal GET on each path to determine if the path is accessible. However, these GETs use the internal subversion URIs, not the ones that we naturally think of. So, to protect our child path, we can add another <Location/> tag thusly:

<Location ~ /svn/myRepo/!svn/ver/[0-9]+/parent/child>
Require group ...
</Location>

Note that this needs to come after:

<Location /svn/myRepo/!svn>
Require valid-user
</Location>

I wasted a couple of hours because I had that nugget tucked away in an included conf file following the one I was tweaking.

So, in order to protect the child the way we wanted to in the first place, our configuration would be:

<Location /svn/myRepo/parent>
Require valid-user
</Location>

<Location /svn/myRepo/parent/child>
Require group ...
</Location>

<Location ~ /svn/myRepo/!svn/ver/[0-9]+/parent/child>
Require group ...
</Location>

What's really nice about this is that a checkout of the parent silently ignores the now-protected child and populates the local workspace with everything that is unprotected.

I haven't tested it (yet) but it is reasonable to assume that the same technique will work with the more complicated <Limit/> and <LimitExcept/> tags.

Friday, April 18, 2008

Securing Subversion via LDAP

This started out as an email reply to the Subversion users mailing list but I decided to post it here instead since it got a little wordy. I can't believe it's been a year since I posted anything, time flies when you're falling off of a mountain bike...


So, in our environment, we're using the LDAP interface to Active Directory. All of our users are known there and we have defined groups to represent various bits of the repository. I'm using mod_auth_ldap in front of SVN to do the integration.


$ grep svn.conf httpd.conf
include conf/svn.conf

$ cat svn.conf

<IfModule mod_dav_svn.c>
<IfModule util_ldap.c>
<IfModule mod_auth_ldap.c>

LDAPSharedCacheSize 200000
LDAPCacheEntries 1024
LDAPCacheTTL 600
LDAPOpCacheEntries 1024
LDAPOpCacheTTL 600

<Location /ldap-status>
SetHandler ldap-status
Order deny,allow
Deny from all
Allow from .myco.com 172.18.
</Location>

<Location /svn>
DAV svn
SVNParentPath /usr/local/svn/repositories
SVNListParentPath on
SVNAutoVersioning on
AuthName "Enterprise Shared SCM Repository"
AuthType Basic
Require valid-user
AuthLDAPUrl ldap://myLdapHost/DC=foo,DC=myco, \
DC=com?SAMAccountName?sub?(objectCategory=person)
AuthLDAPBindDN "CN=foobar,DC=foo,DC=myco,DC=com"
AuthLDAPBindPassword ...

</Location>

include conf/svn/Authorization.conf

</IfModule>
</IfModule>
</IfModule>

So that sets up the basic wiring and in Authorization.conf I break it down into Subversion specifics:

$ cat svn/Authorization.conf

include conf/svn/root.conf
include conf/svn/sandbox.conf
include conf/svn/admin.conf
include conf/svn/BusinessUnit1.conf
include conf/svn/BusinessUnit2.conf
...
include conf/svn/BusinessUnit3.conf

We've taken the route of one large repository shared by our multiple business units. There are pros and cons of this approach (as there are with the repository-per-project approach). For various reasons we felt that a single repository would be most appropriate.

Defining the per-whatever rules below was a bit of trial and error. I put together a set of typical use cases for various paths and who should and should not access them. By running through those cases and watching the logs I was able to sort out the Limit tags necessary to secure things the way I needed.

Since the repository is shared across business units, the first thing is to make sure that only the admin can create top-level directories. this is done in the root.conf file included above:

$ cat svn/root.conf

# Any attempt to modify the myRepo repository requires
# membership in the admin group.
<Location /svn/myRepo>
<Limit MERGE MKCOL POST PUT DELETE PATCH PROPPATCH>
Require group CN=ACL-SVN-ADMIN,OU=SVN,DC=foo,DC=myco,DC=com
</Limit>
</Location>

# On many operations, Subversion does some behind the
# scenes work at .../!svn
<Location /svn/myRepo/!svn>
Require valid-user
</Location>

# Any paths below the root that are not otherwise secured
# only require a valid user for read/write access
<LocationMatch /svn/myRepo/.*>
Require valid-user
</LocationMatch>

In order to encourage people to use Subversion, I have setup a special "sandbox" are of the repository that is more or less a free-for-all. Given the above configuration, this config isn't entirely necessary but if we change the default policy (via the LocationMatch tag) from all-access to no-access our sandbox would break.

$ cat svn/sandbox.conf

<Location /svn/myRepo/sandbox>
Require valid-user
</Location>

Now I define business unit specific access. For each path we want to secure, the business unit tells me if they want the path to be read/write for all members of the group or if they want to have some people with read-only and others with read-write.

For instance, I keep the actual Subversion configuration in the repository. Because some of that may be "sensitive" (e.g. -- the LDAP bind password) I need to protect it.

$ cat svn/admin.conf

<Location /svn/myRepo/Admin/SVN>
Require group CN=ACL-SVN-ADMIN,OU=SVN,DC=foo,DC=myco,DC=com
</Location>

Thus, any access (read/write/merge/...) to /svn/myRepo/Admin/SVN requires membership in the administration group.

In some cases, we may have a project where it is acceptable for anybody with a valid username/password to view the files but only members of a particular group can edit them:

$ cat svn/BusinessUnit1/someProject1.conf

<Location /svn/myRepo/BusinessUnit1/SharedSource>
<Limit MERGE MKCOL POST PUT DELETE PATCH PROPPATCH>
Require group CN=ACL-SVN-ADMIN,OU=SVN,DC=foo,DC=myco,DC=com
Require group CN=ACL-SVN-BusinessUnit1-SharedSource-RW, \
OU=ACLs,DC=foo,DC=myco,DC=com
</Limit>
</Location>

The group that protects this branch is "CN=ACL-SVN-BusinessUnit1-SharedSource-RW,...". I've included the administration group also, however, so that the repository admins can help out if people get in a bind. This may or may not be something you want to do or you may have a different group (e.g. -- a help desk or support org) that would do this kind of thing:

<Location /svn/myRepo/BusinessUnit1/SharedSource>
<Limit MERGE MKCOL POST PUT DELETE PATCH PROPPATCH>
Require group CN=ACL-SVN-Support,OU=SVN,DC=foo,DC=myco,DC=com
Require group CN=ACL-SVN-BusinessUnit1-SharedSource-RW, \
OU=ACLs,DC=foo,DC=myco,DC=com
</Limit>
</Location>

In any case, you can see how you can list multiple 'Require group' directives for the path. These are OR'd together BTW.

There may be other paths that I do not want to be world-readable. In the simple case this is identical to my /svn/myRepo/Admin/SVN example. In the more complex case, however, you may have one group of people who need read-only and another who need read-write access.

$ cat svn/BusinessUnit2/someProject1.conf

<Location /svn/myRepo/BusinessUnit2/someProject1>
<LimitExcept MERGE MKCOL POST PUT DELETE PATCH PROPPATCH>
Require group CN=ACL-SVN-ADMIN,OU=SVN,DC=foo,DC=myco,DC=com
Require group CN=ACL-SVN-BusinessUnit2-someProject1-RO, \
OU=ACLs,DC=foo,DC=myco,DC=com
Require group CN=ACL-SVN-BusinessUnit2-someProject1-RW, \
OU=ACLs,DC=foo,DC=myco,DC=com
</LimitExcept >
<Limit MERGE MKCOL POST PUT DELETE PATCH PROPPATCH>
Require group CN=ACL-SVN-ADMIN,OU=SVN,DC=foo,DC=myco,DC=com
Require group CN=ACL-SVN-BusinessUnit2-someProject1-RW, \
OU=ACLs,DC=foo,DC=myco,DC=com
</Limit >
</Location>

By pairing the Limit and LimitExcept tags I can control who gets to make changes separately from who gets to view files.

One last thing I'll cover is protecting sub-paths within a parent path. Assuming that below /svn/myRepo/BusinessUnit2/someProject1 we have the traditional Subversion directories of "trunk", "tags" and "branches", it is very likely that your enterprise will want to secure the branches area to a specific group. In our case, we want to be sure that our branched code is only modifiable by a few experienced people who are allowed to make changes to the production candidate.

So, building on the /svn/myRepo/BusinessUnit2/someProject1 example, we further restrict the branches to this smaller group of people:

<Location ~ /svn/myRepo/BusinessUnit2/someProject1/[^/]+/branches>
<Limit MERGE MKCOL POST PUT DELETE PATCH PROPPATCH>
Require group CN=ACL-SVN-ADMIN,OU=SVN,DC=foo,DC=myco,DC=com
Require group CN=ACL-SVN-BusinessUnit2-someProject1-branches-RW, \
OU=ACLs,DC=foo,DC=myco,DC=com
</Limit >
</Location>

That's about all of the highlights I can think of to share. The most critical thing is to setup a test LDAP (I used OpenLDAP) and repository that is similar to your production environment. Then drill through as many usecases as you can think of to ensure that you're getting the behavior you expect. Once you're done, leave the test environment in place so that when somebody thinks something is broken you can go there and prove that it is user error :-)

My future goal is to define a simple XML format such that business unit (or project) owners can define their authorization requirements and submit them to an agreed-upon repository path. I would then have code to transform that XML into the Apache rules and drop them into place. In other words, I really don't intend to create and maintain all of the Apache configs by hand as we roll this out to dozens of projects.

Thursday, April 12, 2007

From maven to mvn : Part 7 -- A Groovier Timestamp

With a bit of help from the maven users list I've sorted out the details of creating a maven plugin written in Groovy. It isn't as clean or elegant as I had hoped but things are still brewing so I suspect they will only get better. I'm told that there is a discussion about this on the Groovy mailing list as well.

Before I continue I would like to thank Dennis Lundberg and Martin Gilday on the maven users list. You can see our conversation here. Dennis turned me on to the groovy compiler and Martin's blog entry got me hooked up with javalike-maven-plugin-tools for generating my project descriptor.

So... Let's get down to it and figure out how to write a simple plugin in Groovy. My goal will be to replace my earlier attempt with a compiled Groovy class.

To begin with, let's take a look at TimestampMojo.groovy:
/**
* Set the current time into a system property for use by resource
* filtering.
*
* @goal execute
* @phase generate-resources
* @requiresDependencyResolution runtime
*/
class TimestampMojo extends AbstractMojo
{
/**
* The System property which will hold the current time.
*
* @parameter default-value="timestamp"
*/
String propertyName;

/**
* The format string given to MessageFormat.format()
*
* @parameter default-value="{0,date,yyyy-MM-dd HH:mm:ss}"
*/
String formatString;

public void execute()
throws MojoExecutionException
{
String timestamp = MessageFormat.format(formatString, new Date())
System.setProperty(propertyName, timestamp)
}
}
Well, that's simple enough. Note that there appear to be quite a few extra semi-colons for a Groovy script. It turns out that they're necessary because of the javalike plugin we will use to build the plugin descriptor. Martin's blog entry goes into that in detail so I won't repeat it here.

Next we need a pom to build the plugin. It's a bit large so I'll break it down into bite-sized pieces beginning with the typical header parts:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/maven-v4_0_0.xsd">

<modelVersion>4.0.0</modelVersion>

<groupId>myco.util.mvn.timestamp-plugin</groupId>
<artifactId>myco-timestamp-plugin</artifactId>
<packaging>maven-plugin</packaging>
<version>1.1-SNAPSHOT</version>

<name>myco-timestamp-plugin Maven Mojo</name>
No surprises there so let's move on to specify some extra repositories. We do this because some of the things we need are hosted at Codehaus rather than the default repository.
  <repositories>
<repository>
<id>Codehaus</id>
<url>http://repository.codehaus.org/org/codehaus/mojo/</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>Snapshots</id>
<url>http://repository.codehaus.org/org/codehaus/mojo/</url>
</pluginRepository>
</pluginRepositories>
And now we come to the build section where we define the plugins we need in order to build our plugin. However, we first need to tell the groovy plugin where it can find it's sources:
  <build>
<sourceDirectory>${basedir}/src/main/groovy</sourceDirectory>
Did someone say groovy plugin? Yup. If we're going to write the source as groovy then we need some way to compile that into a .class...
    <plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>groovy-maven-plugin</artifactId>
<version>1.0-alpha-2</version>
<executions>
<execution>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
</plugin>
Remember the annotations in my *.groovy file above? Well, we're going to use the plugin plugin to turn those into a plugin descriptor. Out of the box that plugin doesn't know anything about groovy, though, so we use javalike-maven-plugin-tools to enhance it a bit. As of this writting (April 12, 2007) the javalike plugin hasn't escaped from the Codehaus mojo sandbox so you'll have to check it out of their svn repo and build it yourself. Martin's blog covers that but I'll repeat it here for convenience:
svn co http://svn.codehaus.org/mojo/trunk/mojo \
/mojo-sandbox/groovy-maven-tools/javalike-maven-plugin-tools/

      <plugin>
<artifactId>maven-plugin-plugin</artifactId>
<dependencies>
<dependency>
<groupId>org.codehaus.mojo</groupId>
<artifactId>
javalike-maven-plugin-tools
</artifactId>
<version>2.0-SNAPSHOT</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
Now that we have our plugins defined we need to define our dependencies. Of course, the plugins will pull in whatever they need but because somebody is going to use us as a plugin (we hope) we need to include groovy in our dependencies so that that somebody will get it (transitively) when they build.
  <dependencies>
<dependency>
<groupId>groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-plugin-api</artifactId>
<version>2.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
And that's it! Drop the above into a normal maven project structure (remember, TimestampMojo.groovy goes into src/main/groovy). Compile and install and you're ready to go.



Using the plugin is as simple as using any other. For this kind of thing I would recommend putting it into your ancestral pom.xml that all of your other projects extend but that's a personal choice. In any case, usage looks like this:
    <plugin>
<groupId>myco.util.mvn.timestamp-plugin</groupId>
<artifactId>myco-timestamp-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>execute</goal>
</goals>
</execution>
</executions>
</plugin>
The execute goal is tied to the generate-resources lifecycle phase so that the property will be available to you prior to copying of resources. This means that any ${timestamp} in any filtered resource will be replaced by the current timestamp. If you don't remember how to setup filtered resources, well, neither do I so here it is again:
  <resources>
<resource>
<directory>${basedir}/src/main/java</directory>
<filtering>true</filtering>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
</resources>
And if you want to filter your jsp's before creating the war file (I like to have a ${timestamp} in my footer.jsp...) then you want something like this:
    <plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<webResources>
<resource>
<directory>
${basedir}/src/main/webapp
</directory>
<filtering>true</filtering>
<includes>
<include>**/*.xml</include>
<include>**/footer.jsp</include>
</includes>
</resource>
</webResources>
</configuration>
</plugin>
Remember these bits are in the pom of the application using the timestamp plugin, not in the pom of the plugin itself.

Tuesday, April 10, 2007

From maven to mvn : Part 6 -- Classloader Maddness & A Custom Plugin

Wow. I had no idea it has been a month since my last post. I would plead business but that's cheating. After all, we're all busy...

Tonight's post is going to be a bit eclectic & is the result of considerable (mythical) man-hours of work. Much of that time was spent figuring out why things were broken rather than actually fixing them. The fixing part was really quite short though it was interrupted from time to time by more figuring parts.

The Setup:

I have a utility that I wrote quite some time ago that implements the notion of Evolutionary Database Design. It's an internal tool that I may or may not be able to publish someday but that's not really relevant right now. What is relevant is that it is simply a jar that relies on a number of dependencies, most notably Spring. It runs happily from the command line, maven 1 and Eclipse.

The Plan:

Create a maven 2 plugin around my utility by firing it's Main class with the appropriate parameters.

The Pain:

Classloaders. Man I hate classloaders.

It's a long and unpleasant tale and I won't bore you with the details. You can read some about it here though the rafb posts are long gone.

The short version is that maven creates a custom classloader for itself (a reasonable thing to do) and puts a pom's dependencies into it. In my case, one of those is Spring and when the context begins loading classes it gets tripped up in classloader evilness and begins to belive org.springframework.beans.factory.xml. SimplePropertyNamespaceHandler] does
not implement the NamespaceHandler interface which is patently not true.

The Solution:

I can't explain exactly why but it took me *hours* to figure out that the problem was classloader related and *more* hours to work out the solution. I can only say that it was most likely a Monday (and possibly a Tuesday) and hope that you will forgive me for being somewhat dense.

What you really want to know, though, is how I solved the problem. It is my expectation that someone else may very well run into the same thing so here we go...

First we have my mojo:
/**
* Invoke EDD to generate SQL from XML.
*
* @goal generateSql
* @phase compile
* @requiresDependencyResolution runtime
*/
public class GenerateSqlMojo extends AbstractMojo
{
/**
* Where to get the *.xml/*.sql files to be processed.
*
*
* @parameter expression="${project.build.directory}/classes"
* @required
*/
private String inputDirectory;

/**
* Prefix for the files created by EDD.
*
* @parameter expression="${project.build.directory}/classes/${project.artifactId}"
* @required
*/
private String outputPrefix;

/**
* The classpath elements of the project. We need this so that we can
* get the JDBC driver for the project.
*
* @parameter expression="${project.runtimeClasspathElements}"
* @required
* @readonly
*/
private List classpathElements;

public void execute() throws MojoExecutionException
{
try
{
new File(outputPrefix).getParentFile().mkdirs();
new UtilityExecutor().execute(inputDirectory,
outputPrefix, classpathElements);
}
catch (final Exception cause)
{
throw new MojoExecutionException("Failed to invoke EDD", cause);
}
}
}
OK... Not very exciting... In particular we see nothing about classpaths or classloaders other than the classpathElements parameter/attribute. All of that evilness is hidden in my UtilityExecutor. I'll tell you now: It could be done better! A more robust / reusable solution would extract the classloader rot out of UtilityExecutor and into a more generic helper class. That's a great idea... I'll do it later.

So, let's see UtilityExecutor:
public class UtilityExecutor
{
public UtilityExecutor()
{
super();
}

public void execute(final String inputDirectory, final String outputPrefix,
final Boolean saveOnlyMode,
final List<String> classpathElements)
throws ...
{
final URL[] urls = buildUrls(classpathElements);
final URLClassLoader cl =
new URLClassLoader(urls, ClassLoader.getSystemClassLoader());
Thread.currentThread().setContextClassLoader(cl);

final Class clazz = cl.loadClass(getClass().getName());
final Constructor ctor =
clazz.getConstructor(
new Class[] { String.class, String.class, Boolean.class });
ctor.newInstance(new Object[] { inputDirectory, outputPrefix, saveOnlyMode });
}

public UtilityExecutor(final String inputDirectory,
final String outputDirectory,
final Boolean saveOnlyMode)
{
final Main main = new Main();
main.setSaveOnlyMode(saveOnlyMode.booleanValue());
main.setSaveTarget(outputDirectory);
main.execute(new String[] { inputDirectory });
}

So let's break this down...

1) GenerateSqlMojo fires new UtilityExecutor().execute(...) (a convenience method which simply delegates to the execute(...) method shown).

2) execute(...) builds a URL[] of for our custom classloader (more on that in a minute).

3) The custom classloader is created.

Now it gets a bit weird...

4) We ask the custom classloader to load the Class for ourselves. This causes the class to be loaded from the custom classloader against whatever classpath we built in #2 then

5) fetch the "do some work" constructor for ourself and

6) finally invoke the constructor to launch the utility. (I chose to do the "work" in the constructor to reduce the need for reflection BTW.)


Now a note about the buildUrls(...) method. There isn't anything really magic about what we're doing here. Simply take the URLs from our current classloader (the classloader that loaded the mojo) and combine them with the classpath elements provided from our execution environment. These classpath elements are the dependencies of the pom in which the plugin is being invoked. In my case I need this because the JDBC driver to be used is specific to the client of the plugin rather than to the plugin itself. I also need these because the utility expects to find some of it's runtime configuration in a properties file in ${basedir}/target/classes.

Pay particular attention to the code in bold... If your classpath element is a directory you need to append the trailing slash or your resources there (e.g. -- ${basedir}/target/classes) will not be found. This was something of a painful and frustrating lesson...
  private URL[] buildUrls(final List classpathElements) throws MalformedURLException
{
final URL[] mojoUrls = ((URLClassLoader) getClass().getClassLoader()).getURLs();
final URL[] urls = new URL[mojoUrls.length + classpathElements.size()];
int ndx = 0;
for (final URL url : mojoUrls) urls[ndx++] = url;
for (String cpe : classpathElements)
{
final File file = new File(cpe);
if (file.isDirectory())cpe += "/";
urls[ndx++] = new URL("file:/" + cpe);
}
return urls;
}
}

BTW, I also have an UpdateSchemaMojo that is basically the same as GenerateSqlMojo but invokes a different convenience method on UtilityExecutor.


A Custom Lifecycle:

Now the point of my plugin is to create SQL from XML and optionally apply it to your schema. Great. That doesn't really fit with the typical maven build lifecycle. Never one to try the shallow end of the pool first I decided to jump in the deep end (with lead weights) and create a custom package type for my plugin. It turns out not to be too difficult.

To create a custom lifecycle you only need to create a plexus components.xml file. In my case it looks like this:
<component-set>
<components>
<component>
<role>
org.apache.maven.lifecycle.mapping.LifecycleMapping
</role>
<role-hint>sa</role-hint>
<implementation>
org.apache.maven.lifecycle.mapping.DefaultLifecycleMapping
</implementation>
<configuration>
<phases>
<process-resources>
org.apache.maven.plugins:maven-resources-plugin:resources
</process-resources>
<compile>
myco.util:myco-util-edd-plugin:generateSql
</compile>
<package>
org.apache.maven.plugins:maven-jar-plugin:jar
</package>
<install>
org.apache.maven.plugins:maven-install-plugin:install,
myco.util:myco-util-edd-plugin:updateSchema
</install>
<deploy>
org.apache.maven.plugins:maven-deploy-plugin:deploy
</deploy>
</phases>
</configuration>
</component>
<component>
<role>
org.apache.maven.artifact.handler.ArtifactHandler
</role>
<role-hint>sa</role-hint>
<implementation>
org.apache.maven.artifact.handler.DefaultArtifactHandler
</implementation>
<configuration>
<type>sa</type>
<extension>zip</extension>
<packaging>sa</packaging>
</configuration>
</component>
</components>
</component-set>

It isn't really that bad... All I'm doing here is defining the stages of the lifecycle I'm interested in and the plugins to fire at each one. Notice that in install I specify both the standard install plugin as well as my own updateSchema plugin. This means that after my sa artifact is published to the local repo updateSchema will be fired to (optionally) update my database for me. (In case you're wondering, UpdateSchemaMojo has a Boolean parameter that will enable/disable the database update.)

The type, extension and packaging tags are there to tell maven about my custom packaging type and what to call it when it is installed. We will see what this means in just a bit.


The only thing left is the pom.xml for my plugin. There is nothing magic here either:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/maven-v4_0_0.xsd">

<modelVersion>4.0.0</modelVersion>

<version>1.4-SNAPSHOT</version>
<groupId>myco.util</groupId>
<artifactId>myco-util-edd-plugin</artifactId>
<packaging>maven-plugin</packaging>

<name>EDD Maven Mojo</name>

<dependencies>
... As necessary
</dependencies>
</project>

Using The Plugin:

OK, so now we know everything there is to know about the plugin. All that's left is to use it. Since we're introducing a new packaging type there is a detail we need to be aware of.

The pom starts out as usual:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/maven-v4_0_0.xsd">

<modelVersion>4.0.0</modelVersion>

<parent>
... as necessary
</parent>

<name>contacts database</name>
<artifactId>contacts-database</artifactId>
then we specify our custom packaging type:
  <packaging>sa</packaging>
and then define the plugin. There are two things to note here: (a) the extensions tag tells maven that the plugin extends the standard behavior and (b) the enableEDD property that enables/disables the UpdateSchemaMojo.
  <build>
<plugins>
<plugin>
<groupId>myco.util</groupId>
<artifactId>myco-util-edd-plugin</artifactId>
<version>1.4-SNAPSHOT</version>
<extensions>true</extensions>
<configuration>
<enableEDD>${enableEDD}</enableEDD>
</configuration>
</plugin>
</plugins>
In my case the plugin needs some runtime configuration from a property file so I use a resource tag to be sure that gets copied into ${basedir}/target/classes. I also need the Oracle JDBC driver so that the utility can connect to the database to update the schema.
    <resources>
... As necessary
</resources>
</build>

<dependencies>
<dependency>
<!--
We have an Oracle database so we need to have
this in our classpath to pickup the JDBC driver.
-->
<groupId>oracle</groupId>
<artifactId>ojdbc14</artifactId>
<version>10.2.0.2</version>
</dependency>
</dependencies>
Modifying the schema with every build is probably not a good idea so we disable the update by default and invoke 'mvn -DenableDD=true install' on demand. You could also control this with your ~/.m2/settings.xml and profiles.
  <properties>
<enableEDD>false</enableEDD>
</properties>

</project>

Postfix:

That's it my friend. It is a bit of a long post (hopefully making up for the previous one's shortness) but there was rather a lot to cover. Let's review what I've got here:

A) How to create a Java Mojo implementing your custom Maven 2 plugin functionality.

B) How to create a custom classloader to deal with oddball classloader issues.

C) A clever, IMO, way to isolate the classloader issues and minimize reflection. (Though, honestly, UtilityExecutor could be a bit more clever and less hackish.)

D) How to specify a custom package type with it's own custom lifecycle.

E) How to use your shiny new plugin and it's custom packaging type.


Yes, it took me a bit of time to get everything working. That's one of the problems with jumping into the deep end of the pool when you're first learning something new. On the other hand, I'm reasonably confident that I've faced many (hopefully most) of the odd edge-cases when creating plugins. With any luck this will be a true statement and I can lean on these experiences when I create my next, and hopefully simpler, plugin.

As always, thanks for reading. Feel free to post feedback & questions. Peace ya'll.

Monday, March 05, 2007

From maven to mvn : Part 5 -- A Groovy Timestamp

Good afternoon and happy Monday! I hope you had a great weekend, I know I did. I have to apologize for today's entry up front. It isn't about writing a plugin as I had hoped it would be. It's also somewhat brief. Well, it's Monday and there have been more than the usual number of distractions...

I've been bothered about abusing the maven-buildnumber-plugin to set a timestamp property used in ${foo} replacement. I happened to be doing some reading over the weekend and came across the groovy-maven-plugin. Which lets you run a random bit of groovy script during your build process. Now I still intend to explore the exciting world of creating my own maven plugins but I think this is a good place to start. In fact, when I *do* write my own plugins in the future they will very likely be written in groovy using this technique.

So, today's brief entry will (a) drop the buildnumber plugin and (b) use the groovy-maven plugin to replace its functionality.

Begin by adding repository entries to your pom:
<repositories>
<repository>
<id>Codehaus Snapshots</id>
<url>http://snapshots.repository.codehaus.org/</url>
</repository>
</repositories>

<pluginRepositories>
<pluginRepository>
<id>Codehaus Snapshots</id>
<url>http://snapshots.repository.codehaus.org/</url>
</pluginRepository>
</pluginRepositories>

Note: If you already have repositories declared just add these new entries, don't blow away what you've got.

Next, declare the plugin and some script for it to execute:

<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>groovy-maven-plugin</artifactId>
<executions>
<execution>
<phase>generate-resources</phase>
<goals>
<goal>execute</goal>
</goals>
<configuration>
<source>
<body>
import java.util.Date
import java.text.MessageFormat
def timestamp = MessageFormat.format("{0,date,yyyy-MM-dd HH:mm:ss}", new Date())
System.setProperty("timestamp", timestamp)
</body>
</source>
</configuration>
</execution>
</executions>
</plugin>


That's it! Now, during the generate-resources phase of the maven build lifecycle groovy will execute the script to set the system property timestamp to the current time.

That's it for today. I have to run off to take care of some other things. I promise you a better Part 6.

Thursday, March 01, 2007

From maven to mvn : Part 4 -- Transitive Dependencies

So I took a break from maven yesterday to do some performance investigation using JAMon and AspectJ. We don't need no fancy-pants monitoring tools. Just a bit of advice, a Monitor or two and an editor that can handle huge log files.

But that's not what I'm here to talk about today.

Today I'm continuing the m1-to-m2 migration and turning my attention to my nasty pile of dependencies. With m2's way cool transitive dependency management I hope to drop my 300 lines of dependencies in pom.xml to something a bit more manageable.

Now you probably don't want me to dump the original 300 lines here so I can't really show you what I'm starting with. However, the high level framework bits include: spring, hibernate, acegi, aspectj and misc Jakarta commons. If you've worked with any of these you know that each one introduces still other dependencies you have to deal with.

To get started I simply commented out all of the dependencies we didn't produce in-house and started compiling. AspectJ was the first thing mvn complained about so now I have to go figure out how to add it correctly... I mentioned this link earlier (Part 2 I believe). I'm not a big fan of repeating someone else's words so please take a moment to read the link. I'll wait.

Did you see this bit: "site:www.ibiblio.org maven2 log4j" If you didn't, go back and look for it. That is the secret to finding your m2 dependencies. I will caution you, however, that maven-metadata.xml may not always list the most recent available versions. It's worth doing a directory listing to see what is there.

For instance, "site:www.ibiblio.org maven2 aspectj" gets me to a maven-metadata.xml that does not list version 1.5.2a which, as it turns out, is the one I want. The directory listing, however, shows me a 1.5.2a subdirectory which has exactly what I need.

Also in maven-metadata.xml you will find the appropriate groupId and artifactId values you need for your dependency. Armed with all of this information I can now add the appropriate pom.xml entry:
<dependency>
<groupId>aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.5.2a</version>
</dependency>

Good. That was easy. Now rinse & repeat for my other dependencies and lets see where we end up...

Well, that didn't last very long. My very next dependency is hibernate. My google search showed up beanlib (which is really cool BTW) and the old net.sf.hibernate stuff but not the 3.2.1.ga version I need. Fortunately, I know how the m1 repository is layed out and a bit about how the m2 repository is layed out. On a hunch I try a reasonable URL and find something useful. (I also noticed that 3.2.2.ga is available so I may consider an upgrade soon. But I digress...) Now I know the groupId and artifactId values for my hibernate dependency so that gets added to my pom.xml

Now rinse & repeat. Occasionally a dependency shows up with a groupId or artifactId that I wasn't quite expecting from my m1 days but those things are easy to work with.

javax.transaction introduced a new wrinkle. Apparently you have to go get that from Sun yourself. Thanks guys. <sigh/> Fortunately m2 tells us exactly what to do:
Missing:
----------
1) javax.transaction:jta:jar:1.0.1B

Try downloading the file manually from:
http://java.sun.com/products/jta

Then, install it using the command:
mvn install:install-file -DgroupId=javax.transaction -DartifactId=jta \
-Dversion=1.0.1B -Dpackaging=jar -Dfile=/path/to/file

Path to dependency:
1) com.myCompany.personal.jcej:contacts:war:1.0-SNAPSHOT
2) org.hibernate:hibernate:jar:3.2.1.ga
3) javax.transaction:jta:jar:1.0.1B
Oh what fun... However, if you run across other dependencies that are not available at the venerable ibiblio you can use this same technique to install them to your local repository. Certainly not ideal but tolerable until they do become available.

It is worth pointing out that as you make your way through your dependencies you may be able to remove some you added previously. For instance, I added org.hibernate/hibernate because my build broke without it. Later I added org.hibernate/hibernate-annotations because the build broke on that. The later requires the former so I can remove my explicit org.hibernate/hibernate dependency and rely on the transitive dependency from org.hibernate/hibernate-annotations. It is a bit more work to backtrack this way but in the long run it will make for a much smaller and easier to maintain pom.xml. For simplicity it is worth my time to inspect my dependencies' pom.

While exploring that, because I know acegisecurity depends on spring, I discovered that some plugins specify their dependency versions via properties. Now that's a great idea! Because it centralizes dependency version management and lets me override their required version by overriding the property.

Thus I now have a properties section in my pom.xml something like this:
<properties>
<spring.version>2.0.1</spring.version>
...
I just have to pay attention to my dependencies dependencies. But I was going to do that anyway. Using the properties is easy:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring</artifactId>
<version>${spring.version}</version>
</dependency>
[ time passes ]

So after about an hour (which isn't bad considering the normal afternoon interruptions) I get to the point where things are successfully compiling. But my unit tests are failing!? Looking at the surefire reports clues me in to the thought that maybe some of my dependencies aren't consistent:
    java.lang.NoSuchMethodError: org.springframework.util.ObjectUtils.nullSafeToString(Ljava/lang/Object;)Ljava/lang/String;
That don't look good...

Build again with the tests disabled (mvn -Dmaven.test.skip=true install) and see what we have in our war's lib directory. Yea... Well... Who put spring 1.2.8 jars in there? This is a case where transitive dependencies hurt rather than help.

OK, roll the sleeves up and crank up the tunes. Lets go figure this one out. First, we need to tell maven to tell us more: mvn -X -Dmaven.test.skip=true install. It's probably wise to capture that via nohup or script since it will be a tad verbose. I know that acegisecurity depends on spring and after a few minutes I realize that the spring.version property I was so excited about didn't really help me. Sure, I set it to 2.0.1 in my own pom.xml but that didn't override the value in the acegisecurity dependency's pom. Drat. After a bit of reading I come to the conclusion that I need to exclude spring from acegisecurity's dependencies. Once I do this I can specify the spring versions I want in my own pom.xml and we should be in good shape.

Better but still not good. We have the correct version of spring and that's good. But my hsqldb-based unit tests are failing. They're complaining about "The database is already in use by another process" and I wasn't getting that before I started mucking about with transitive dependencies. This turned out to be similar to the spring problem in that struts-menu was pulling an old version of hsqldb.

There seems to be some difference in the way transitive dependencies are excluded. In the acegisecurity-uses-spring case spring is specified in the dependencyManagement section. When I add an exclusion to my acegisecurity dependency declaration all works as expected. Namely, my spring dependency is used instead of acegi's. However, struts-menu declares its dependency on hsqldb in its dependencies, not under dependencyManagement. When I add an exclusion to struts-menu not only does mvn not include the version of hsqldb that struts-menu wants but it does not include *any* version of hsqldb though it is clearly listed in my own dependencies. Could be operator error... Just be cautious. (If you have any word on this please drop me a comment so I can quit pulling my hair out over it.)

That's it for today. My m1-built war still has different dependencies than my m2-built war but I'm confident that those remaining dependencies can be solved by applying the steps above. It's raining and 5:00 and I would like to get home before dinner gets cold. I'll wrap this up in the morning and if I make any amazing discoveries I'll document 'em here for you. Otherwise you can safely assume that things went well.

Part 5 coming soon. I'm not quite sure what that'll be but it might have something to do with creating my own plugin. Check back soon...

(Oh, and if you're keeping score, my 300 lines of dependencies are now about 120 lines. Maybe less if I drop the odd comment here and there. I call that an improvement!)

Tuesday, February 27, 2007

From maven to mvn : Part 3 -- Filtering Resources & Timestamps

Good morning all!

We left off yesterday with an application that will build and tests that will pass. We even have a war file sitting in our target directory. Before I try to deploy that, however, there are some loose ends I want to clean up.

Our goals for Part 3 are:
- @foo@ (or ${foo}) replacement
- attempt to deploy the war

M1 grew out of a set of ant build scripts. As such, filter-when-copying worked pretty much like you would expect from ant. In other words, any @foo@ text in a filter-when-copying resource would be replaced by the foo property value. M2 uses the newer ${foo} syntax and that's probably a good thing.

The first thing I want to do is identify the files I need to change:
  grep -r '@.*@' src/ | grep -v /.svn | grep -v /images/

It turns out that I'm only using a few properties. One no-brainer is the application version that comes from the pom. The others are application specific properties I need to deal with.

So, @pom.artifactId@ should simply become ${pom.artifactId}. Enabling filters is easy enough to do by adding <filtering>true</filtering> to the appropriate resource tag(s).

That gets me very close but not quite done... You see, I'm filtering /WEB-INF/geronimo-web.xml. The one that gets copied into target/test-classes (because of my pom's testResources as discussed in Part 2) is fine. However, the one in target/contacts-1.0-SNAPSHOT (which is managed by the war plugin) is not filtered. Hrm...

It turns out that the war plugin is responsible for managing that bit of the project. Well, that makes sense. So, to change it's behavior we need to configure the plugin:

<build>
...
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<webResources>
<resource>
<directory>${basedir}/src/main/webapp</directory>
<filtering>true</filtering>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
</webResources>
</configuration>
</plugin>
Now if you read the plugin documentation about this topic you will probably reach the conclusion that I'm doing it wrong. That is probably the correct conclusion. What I should do is create a ${basedir}/otherResources directory and put the to-be-filtered things there. Well, as with my dependency ick, I'm simply going to defer that to later. I still haven't quite decided for myself whether I like the idea of separating the resources from everything else anyway...

So, now I have my ${pom.*} properties filtered but I still have my application-specific properties to contend with. In particular, I have a myApp.timestamp property supplied by the venerable ant <tstamp/> tag that I slam into footer.jsp to display the build time (very handy for our QA folks.) It turns out that getting the timestamp (oh so easy with m1) is a bit of a trick with m2. Thanks to some good folks on the forum for pointing out a clever use of the build number plugin.

First -- tell maven where to find the plugin:
<pluginRepositories>
<pluginRepository>
<id>tlc</id>
<name>TLC Repository</name>
<url>http://commons.ucalgary.ca/pub/m2</url>
</pluginRepository>
</pluginRepositories>

Second -- configure the plugin to provide a timestamp:
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>maven-buildnumber-plugin</artifactId>
<version>0.9.4</version>
<configuration>
<format>{0,date,yyyy-MM-dd HH:mm:ss}</format>
<items>
<item>timestamp</item>
</items>
<doCheck>false</doCheck>
<doUpdate>false</doUpdate>
</configuration>
<executions>
<execution>
<phase>validate</phase>
<goals>
<goal>create</goal>
</goals>
</execution>
</executions>
</plugin>
In my case I also have to add an <include...> to my filtering since I want my timestamp in footer.jsp. With all of that in place I now get the date/time in my footer.jsp via ${buildNumber}. It isn't nearly so easy or flexible as with m1 but right now I'm interested in results, not cleverness. Perhaps I'll find something better in the future. (Or maybe write my own timestamp plugin!)

(BTW: If you have simple, static properties they are very easy to define in an external file or directly in pom.xml as described by the link above.)

OK, so now I'm filtering and building and testing and packaging so I should have a deployable war. I'm not brave enough to try to deploy via m2 (perhaps another day) so I'll fire up my application server of choice and deploy the war manually. Nothing really to write about here. You probably don't care. I just thought you would like to know that it actually deploys correctly.

You will have to excuse me now; there are some other, non-maven things I need to address this afternoon. (Yes, it is afternoon now. What began in the morning has bled over the international lunch line due to the usual distractions.) If my meeting schedule allows I'll pickup Part 4 tomorrow and try to do a bit of cleanup on my dependencies.