Tuesday, November 24, 2009

M2Eclipse and the case of the missing sources

I overheard Richard complaining the other day that no matter what he did eclipse would not download the source for one of his dependencies. The source was for a third party library that we manually added to archiva but forgot the source the first time around.

I just remembered reading a note about dependency:sources caching unsuccessful requests. The cache is stored in the target directory so using mvn clean will clear it. I can't imagine why you would want this at the artifact level esp when its cached in the target directory. The whole repository manager idea is a ad-hoc solution to solve a fundamental flaw in the approach to artifact management. But I'll leave that for another day.

I assume that a similar thing happens in m2e. So if you Right click on project -> Maven -> Download sources and nothing happens, try doing a project clean first. And see if that helps.

Friday, November 20, 2009

M2Eclipse and the dreaded ClassNotFound

M2Eclipse and split output directories
Recently I had the experience of some pain using m2eclipse, maven and eclipse. I thought i would share this with you and hopefully save you the same pain.

If you use maven2 and eclipse, m2e is very handy tool.
  1. It resolves dependencies and gives you a classpath as defined by the pom.
  2. You can edit the pom live and autocomplete things like dependency names and plugin configurations
  3. It will link snapshoted projects together for refactoring
  4. It will automatically link up source jars so you can browse the source of dependencies

I think those benefits outweight many of the issues that other people seem to baulk at.

Eclipse is my IDE

To me it seems natural that Eclipse is my IDE I use it to develop quickly with refactoring support and all the other goodness. I use maven to manage my dependencies and releases. I keep them separate so always had separate output paths for eclipse and maven. I never run maven from inside eclipse.

If you ask why, consider I want to do a release:prepare which does a clean, test then a tag. Now if eclipse is open and using the same output paths it will rebuild the classes as soon as I do the clean. The whole point of using maven for the release is that the classpath is very well defined for main compile. Eclipse merges test and compile scope and add its own jars in if you're not careful. So that means the class that are compiled might not be using the well defined classpath and I've wasted my time.

So everything in my world was good until I started a new project and was forced to install a new machine. Naturally I got the latest version of the M2Eclipse plugin and everything went to custard.


ClassNotFound

So I carried on with my standard approach of defining different output folders for eclipse. Maven would build to target/classes and eclipse to target-eclipse/classes. However without rhyme nor reason junit or my application runners would not find classes that eclipse had built.

Richard mentioned the note on the m2eclipse website saying that as of 0.9.4 the classpath was tweaked for maven in eclipse.

So I'm running Junit in eclipse so why should I care? Well I went trawling in the code base and comparing revisions to see what was going on and this is what I found.

  1. It was actually the third 0.9.4 release that made the change. I was previously using the first 0.9.4 release and had no problems.
  2. M2Eclipse intercepts the application, junit and testng launchers and scopes them properly. That means application run in runtime scope only. Tests run in test scope.
  3. Did you click?... that means that the output folders in the classapth for applications, unit and testng are the maven ones NOT the eclipse ones.
  4. M2Eclipse stores the output directoris in the workspace metadata separately from how eclipse defines them. It IS possible for this to be stale and that is incredibly confusing. This is when everything looks right everywhere but regardless your classes are still not found.
On pondering for a while I can see the reasons

  1. This was too allow proper scoping of tests and applications to stop the eclipse scope bleed.
  2. And because of this pearler... some maven plugins hardcode there paths and won't run in eclipse. I would have thought fixing the plugins would be a better idea but in any case...
It was a bit mean to just change the default rather than allowing a new option to configure it so if you had issues with a plugin you could work around it till the plugin was fixed. As stated above I think its pretty crazy to share the output paths cause you never know what you are actually getting.

Not to the solution...

How to set things up properly
So you should follow the instructions on the m2eclipse website and set up a profile for maven in your parent pom. Or check out my example. The following assume you are using my example which differs only in the profile name.

Because you are configuring how maven will run in eclipse as well as eclipse the easier approach is to use the maven-eclipse-plugin with the m2clipse goal
mvn eclipse:m2eclipse
You will need to make sure however that
  1. You enable the profile when you do that... mvn -Prunning-in-eclipse eclipse:m2eclipse
  2. You run eclipse:clean do remove the old config file mvn -Prunning-in-eclipse eclipse:clean eclipse:m2eclipse
  3. You don't have the project open in eclipse otherwise the workspace will end up stale and m2eclipse will see the old path. You might even need to clean your workspace if things go pear shaped
  4. Configure the maven eclipse plugin to enable the profile by default otherwise when you regenerate it will be turned off again
The easiest way to see this in action is to check out

svn co http://gholam.googlecode.com/svn/trunk/net.gholam.example/m2eclipse

and look at the README, you will find instruction for using the maven-eclipse-plugin in the different ways to see it generate correctly.

Don't panic
Ultimately using maven2 and eclipse you will get unexpected thing happening. M2Eclipse is only 0.9.8 so still not a release. Hopefully I have provided a little insight into the things that might go wrong when spliting output directories in eclipse.

If you done everything and you still have issues try starting eclipse with -clean to refresh your workspace and then opening and closing the relevant projects and see if that helps.

Credits
Thanks to Richard Vowles for encouraging me to start a blog, and for announcing it on IllegalArgument.com.

Friday, November 6, 2009

Containment vs Configuration

What is it that I don't like about xml driven configuration for spring?
I find the xml configuration duplicates (or worse doesn't) the dependencies I have configured in the project. Its basically about coding with intention, but I'll need to give you a little background.

Because I make heavy use of dependencies and fine grained artifacts (thanks Maven) containment is the natural way to define applications. If I want to use a component then I add it to the dependencies and its appears in the war. That does not mean much unless I have a way of putting them together, which is where Spring comes in.

Now of course I could now define the beans in my applicationContext.xml, but didn't I just say I wanted them? I intended to include the services from the project otherwise why add the dependency, if so isn't the extra definition redundant?

On discovery of the Component annotation and ClassPathBeanDefinitionScanner it was a natural fit. I could construct the context defined by the containment of the project and it would be that same containment defined by the deployed war.

GenericApplicationContext context = new GenericApplicationContext();
ClassPathBeadDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context, true); // true == use the default spring component filters
scanner.scan("base.package");
context.refresh();


What actually happens when this scanning occurs?
Spring will look into the Classpath from base.package down and find every class thats annotated with Component, that class will be registered with the context based on its Scope which defaults to Singleton. Thats awesome but ultimately by itself useless.

For example
@Component
public class NotVeryUsefulService {
}


Gives me a bean with id notVeryUsefulService and its not very useful. To make it useful I could give it something to play with.

For example
@Component
public class UsefulService {

private final IUsefulDataRepository usefulDataRepository;

@Autowired
public UsefulService(IUsefulDataRepository usefulDataRepository) {
this.usefulDataRepository = usefulDataRepository;
}
}


Spring has handy things called BeanPostProcessor that maniplate beans at various stages of their lifecycle. The Autowired post processor will require that a bean of type IUsefulDataRepository exists in the context or throw a BeanCreationException. This is _really_ important because it ensures the coherency of the context.

When using a decent release process like that defined by the maven release plugin, I will always verify that my context is coherent when releasing. It is simple define a test to validate this.


public class SanityTest {

@Test
public void verify() {
GenericApplicationContext context = new GenericApplicationContext();
ClassPathBeadDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context, true); // true == use the default spring component filters
scanner.scan("base.package");
context.refresh();
}
}



Environment configuration

Strategies


What does that mean?
That means that

Too many moving parts

Somehow I always seem to end up being heavily involved in deployment, and I've noticed that just like real machines software suffers by the number of moving parts. Everything you write a piece of code and need to configure it, you have a moving part.

Let me elaborate, consider your standard operations technician who takes your war and decides to have a nosey. Every piece of code thats not configured to be used is still there.

Will I ever accidentally add project with services that I don't want?
Sure its possible but what is the impact, if I define some simple processes to review things for each Release then it will be obvious.