Showing posts with label programming. Show all posts
Showing posts with label programming. Show all posts

Monday, January 22, 2007

Using AspectJ with Annotations

Recently I've been exploring annotations and how to use them with AspectJ. I've ran into a few things that I found to be odd, and may be due to the fact that annotations are still new to Java. Consider this example:

/* Required.java @Required annotation */
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Required {
String[] names();
}


/*MyClass.java*/
import java.util.Map;
public class MyClass {

@Required(names = {"id","label"})
public static void myMethod(Map<String,String> args,Object obj)
{
//id and label should be guaranteed not null
}
}


The following aspect with the required pointcut will match against the method above like one would expect.

/*CheckRequired.aj*/
import java.util.Map;

public aspect CheckRequired {

protected pointcut required(Required required) :
execution(@Required * *(Map<String,String>,..))
&& @annotation(required);

before(Required required) : required(required)
{
Map<String,String> args = (Map)thisJoinPoint.getArguments()[0];
for (String name:required.names())
{
if (!args.containsKey(name))
throw new IllegalArgumentException(name + " is a required parameter.");
}
}
}

Instead of getting the arguments from thisJoinPoint we would like the arguments to be part of the pointcut like the following:

/*CheckRequired.aj*/
import java.util.Map;
public aspect CheckRequired {
protected pointcut required(Map<String,String> args, Required required) :
execution(@Required * *(Map<String,String>,*))
&& @annotation(required)
&& args(args);
before(Map<String,String> args, Required required) : required(args,required)
{
for (String name:required.names())
{
if (!args.containsKey(name))
throw new IllegalArgumentException(name + " is a required parameter.");
}
}
}

But when this is compiled we get the warning "advice defined in CheckRequired has not been applied". Why is this? Changing the method from static to non-static doesn't make a difference. You'll notice that MyClass.myMethod takes a second argument that is never used in this example. Removing the argument will cause the second example of CheckRequired.aj to advise that pointcut. I came across this problem with a method that actually does need a second argument so that is why I included it in this example.

Tuesday, December 5, 2006

Building scala from maven - maven-scala-plugin

I recently wanted to build scala code in maven. There was no plugin to do this but I did find a patch for maven. I didn't like this approach because maven is supposed to be plugin based and you should be able to extend it without changing it. So I wrote a quick and dirty little plugin http://millstone.iodp.tamu.edu/~blambi/maven-scala-plugin(docs). Update 7/11/07: The maven-scala-plugin is available on Google Code

At first I wanted to use the api provided by scala to compile code. I used the scala eclipse plugin as an example, and got it working. When I ran it as a maven plugin I got a strange error: "object scala not found". I assumed this meant that the package scala wasn't available, due to the fact that the scala jar files are not included on the classpath when the jvm is started by maven. So next I tried creating a new ClassLoader and force loading every class in the scala libraries, but I received the same error.

The simple way, I decided, was to run a new jvm with the proper classpath. This is what scalac does when it runs. So I simply setup the classpath and execute 'java scala.tools.nsc.Main'. It may be a naive implementation, but it works and I'm happy I can now compile my scala code using maven.

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.