Friday, December 1, 2006

Maven 2 Quick Guide

Maven 2 is a project management tool that you can use for building, deploying, configuring, documenting, dependency management, etc. It's not just for Java, it will also build many other languages and you can build a plugin to support any language you like.

First create a new project:

mvn archetype:create -DgroupId=my.org -DartifactId=myapp

groupId is whatever you want, it's sort of like a package name or namespace. artifactId is probably your application name, a directory with this value will be created and your final build will be named this value.

Inside the directory just created you will find the src dir. If you are developing a java app, put all your code under src/main/java. There will be some generated code in src/main/java and src/test/java, you can delete it or ignore it.

The easy way to get all your external jar files into your project is to set up your own repository. It can be a local dir, or be hosted with apache. Just make a directory somewhere and start putting jar files in your repository with this command:

mvn deploy:deploy-file -DgroupId=mailapi -DartifactId=smtp \
-Dversion=1.4 -Durl=file:///path/to/repo \
-Dpackaging=jar -Dfile=smtp.jar

If you have ssh keys setup, you can use scp in the url also.
Now you have a repository you can reference in your project. Open pom.xml and add the new repository to your project:

<repositories>
<repository>
<name>myname</name>
<id>someid</id>
<url>file:///path/to/repo</url>
</repository>
</repositories>

Now you can reference your jar files as dependencies in pom.xml.

<dependency>
<groupId>mailapi</groupId>
<artifactId>smtp</artifactId>
<version>1.4</version>
</dependency>

If you omit version, it will use the newest version available.
Now to you can use simple commands to build your application:

mvn compile

Only compiles the source code.

mvn install

Compiles and packages your project then installs it in your local repository.

mvn war:war

Builds a war file of your application, including everything from src/main/webapp and your packaged project.

Creating a web site for your project is really easy, you can use an xml format (xdoc), that you can embed html into, or you can use a wiki like format (apt). You need a site.xml file, put it in src/site. Put your xml or apt files in src/site/xdoc and src/site/apt repectively. Here is an example site.xml and xdoc file:

<!-- site.xml -->
<project name="My Project">
<bannerLeft>
<name>banner</name>
<src>relative/path/to/image</src>
<href>http://my.home.page</href>
</bannerLeft>
<body>
<links>
<item name="orglink" href="http://my.org" target="_blank"/>
</links>
<menu name="About My Project">
<item name="Introduction" href="index.html"/>
<item name="Getting Started" href="getting-started.html"/>
</menu>
</body>
</project>

<!-- index.xml -->
<document>
<properties>
<title>My Project</title>
<sub-title>Welcome to My Project</sub-title>
<authors>
<person name="Brice Lambi" email="bricelambi@my.org"/>
</authors>
</properties>
<body>
<section name="Welcome to My Project">
<p>Maven is cool!</p>
</section>
</body>
</document>

You can build your site with the command:

mvn site:site

Check out the maven documentation for more info: http://maven.apache.org

Using AspectJ in web applications

AspectJ is a great tool, and can make your life a lot easier when used properly. I have found several great uses for it in web application programming. Most web applications use a database connection and share that connection in a http session. Often a programmer will need to get the connection object from the session and check to make sure it isn't null, if it is try to instantiate it and catch any errors. Instead of putting this code everywhere you use the connection just use an aspect. The following example works well with a portlet application.


public aspect SanityCheck {
protected pointcut render(RenderRequest req,RenderResponse res) :
execution(void GenericPortlet.do*(RenderRequest,RenderResponse)
&& args(req,res);

around(RenderRequest req, RenderResponse res) : render(req,res)
{
Object conn = req.getPortletSession().getAttribute("conn");
if (conn == null)
{
try{
conn = new MyConnection();
req.getPortletSession().setAttribute("conn",conn,PortletSession.APPLICATION_SCOPE);
}catch(Exception e){
//handle error
}
}
proceed(req,res);
}
}


Now you have all this logic in one place and there is no need to be concerned with this problem elsewhere in your code. You also have a reusable pointcut for all renders, notice it is protected so any aspects that extend this aspect can also advise this pointcut.

Managing sessions can also be a pain sometimes. You might have one class that relies on session variables, but you want those to be cleared when a user navigates away from that page. Luckily we have the negation operator in AspectJ.


public aspect ClearSession {

private List<String> varNames = new ArrayList<String>();

protected pointcut display(MyPortlet portlet, RenderRequest req) :
execution(void MyPortlet.do*(RenderRequest,RenderResponse))
&& target(portlet)
&& args(req,*);

private pointcut notDisplay(RenderRequest req) :
execution (void GenericPortlet.do*(RenderRequest,RenderResponse)
&& !execution(void MyPortlet.do*(RenderRequest,RenderResponse))
&& args(req,*);

//only do this after returning, if it threw an exception we probably don't want to bother
after(MyPortlet portlet, RenderRequest req) returning : display(portlet,req)
{
//assumes MyPortlet has this method to retrieve a list of variable names it uses
List<String> names = portlet.getVarNames();
for (String x:names)
{
if (!varNames.contains(x))
varNames.add(x);
}
}

before(RenderRequest req) : notDisplay(req)
{
for (String x:varNames)
req.getPortletSession().removeAttribute(x,PortletSession.APPLICATION_SCOPE);
varNames.clear();
}
}


This will get a list of variables to be cleared from the portlet, and then when anything other than this portlet executes, we clear those variables.

File find in Java

The other day I wanted to search a directory and get a list of files that match a pattern. Basically the find command. I was very annoyed that Java didn't have anything like this built into the File API. So this little method does the trick. You might use it like


findFiles(new File("/home/blambi/workspace"),
File.separator,
".*\\.class");


to find all the class files in /home/blambi/workspace.


/**
* Recursively searches a directory for filenames matching pattern and
* uses delim as the file seperator.
* @param dir dir to search
* @param delim file seperator
* @param pattern string to match against
* @return a list of strings that are the filenames that match pattern
*/
List<String> findFiles(File dir, String delim, String pattern)
{
List<String> ls = new ArrayList<String>();
String[] dirs = dir.list();
for (String x:dirs)
{
File tmp = new File(dir,x);
if (tmp.isDirectory())
{
List<String> fs = findFiles(tmp,delim,pattern);
for (String y:fs)
{
ls.add(x + delim + y);
}
}
else if (x.matches(pattern))
{
ls.add(x);
}
}
return ls;
}