?

Log in

No account? Create an account

More Java ranting - Journal of Omnifarious

Dec. 26th, 2007

04:27 pm - More Java ranting

Previous Entry Share Next Entry

Today I needed to find a Java class. Much easier said than done.

Of course, first I had to find out that I needed to find the class. I get a very unhelpful message from ant when it tries to run an 'email' task
     [mail] Failed to initialise MIME mail: javax/mail/MessagingException

After digging through (of all things) the ant source code in order to figure out why it might say such a cryptic thing, I discover that the only possible way it could is if these lines of code fail:

            mailer = (Mailer) ClasspathUtils.newInstance(
                    "org.apache.tools.ant.taskdefs.email.MimeMailer",
                    MailLogger.class.getClassLoader(), Mailer.class);

For the unenlightened, this means that it needs to find a class called 'org.apache.tools.ant.taskdefs.email.MimeMailer' (gotta love the horribly long convoluted class names) and instantiate a copy of it using a no-argument constructor. (And I'm not going to explain what that means because I know many of you aren't programmers.). So, in order to figure out why this isn't happening, I search the web. I find out that it may be because of a missing ant-javamail.jar file in then $ANT_HOME/lib directory. I have one there though, and not only that, it contains the appropriate org/apache/tools/ant/taskdefs/email/MimeMailer.class file.

In order to dig further then, I write myself this Java program:

import org.apache.tools.ant.taskdefs.email.MimeMailer;

public class jm {
    public static void main(String args[]) {
        Object c = new MimeMailer();
    }
};

This class does (in much plainer fashion) what the previous code does. So it should fail and give me the exception that the wonderful ant has been so helpfully hiding from me (even when I turn on verbose, debug, diagnostics and every other 'show me stuff' flag I can find, what kind of person writes code like that?).

So, I track down the various jar files I need in my javac -cp argument in order to compile the thing. Then I run it and get the error I want:

Exception in thread "main" java.lang.NoClassDefFoundError: \
org/apache/tools/ant/taskdefs/email/MimeMailer
        at jm.main(jm.java:5)

Yay! Now, to find the mysterious missing class. It will be in no obvious place, clearly. The wonderful find and locate tools will be of no help whatsoever here because it will be hidden inside one of the ubiquitous jar files lying around.

sudo locate -r '\.jar$' | tr '\n' '\0' | \
xargs -0 sh -c 'for file in "$@"; do \
  if jar -tf "$file" | fgrep MessagingException; then echo "$file"; fi; \
done' fred

This tells me that it's found in mailapi.jar. There are about 50 of them (literally) lying around all over creation. This is because there was never a standard place to install jar files where Java would automatically find them. That might require some kind of *gasp* dependency on something not Java, or something. Of course, running a Java program at all requires depending on something not Java, but Java people are very weird that way and like to pretend they live in a universe that contains nothing but Java. Forget the turtles, it's Java all the way down! Honest!

I finally pick one of the mailapi.jar files to copy into a place where they should be found by ant. *rolls eyes* Which of course is a different place than any of the other 50 locations they're in. The stupidity never ends when Java is involved.

Edit: Well, that didn't work. But at least this time I was able to convince ant to print out an exception for me. But that was only marginally helpful as the exception was this:

javax.mail.NoSuchProviderException: smtp
        at javax.mail.Session.getService(Session.java:768)
        at javax.mail.Session.getTransport(Session.java:708)
        at javax.mail.Session.getTransport(Session.java:651)
        at javax.mail.Session.getTransport(Session.java:631)
        at javax.mail.Session.getTransport(Session.java:686)
        at javax.mail.Transport.send0(Transport.java:166)
        at javax.mail.Transport.send(Transport.java:98)
...etc

I was somehow supposed to mysteriously divine that what that really meant was that I needed com.sun.mail.smtp.SMTPTransport from some random jar file called smtp.jar, which I found by repeating that whole bletcherous find trick up above. Aigh! And I only found that that was the class I needed by searching for the exception on the Internet and finding someone who had gotten farther down in the stack trace because they did have smtp.jar.

In Python this would never have happened for any number of reasons. First, there is a standard place for .py files to go. And they're left as .py files so you don't have to use a stupid tool to find them inside of some mysterious container like .jar. Then, the source code is there so when something throws an exception you can always find out why. Lastly people typically do not hide as much information from you when handling exceptions as was hidden from me by the writers of these Java classes.

The only thing that would've saved me this much work in Java is encyclopedic knowledge of ant internals and the Java mail API. So stupid!

Current Mood: [mood icon] annoyed
Current Music: Delerium - Run for It

Comments:

[User Picture]
From:xerhino
Date:December 27th, 2007 02:11 am (UTC)
(Link)
Hilarious, except for the vicariously painful part. :-)

I've seen that sort of error before, but perhaps a more insidious form of error is a previous version of a jar file occurring earlier in the classpath and throwing an error that you can clearly see (in the current version) that it can't or shouldn't throw.

I wrote a perl script that grabs the environment information from a running process (you can't trust that the classpath is pristine when it is set in a start script) and scrubs it for duplicate or non-existing files and directories. Not that anyone where I work uses it. We still install patches and cross our fingers that they are picked up by the application. Sigh.
(Reply) (Thread)
[User Picture]
From:omnifarious
Date:December 27th, 2007 04:00 am (UTC)
(Link)

You know, if the Java people upon initially encountering the classpath problem had thought to themselves "Hey, you know, Debian and Redhat solved this problem for shared libraries ages ago!" this problem wouldn't exist. There'd be a standard system directory all .jar files went into and a dependency tracking system to make sure the right versions were there.

If you wanted to rely on some crufty old ancient piece of code that needed some old version of something and it couldn't be updated to use a new version, you'd make an environment specifically for it and the evil you're perpetrating by doing so would be readily apparent to all.

You'd still have the evil of two jar files that claimed to contain the same class, but that'd be fairly easy to check for and could clearly be considered a bug.

But, of course, that solution requires system support *gasp* outside Java and we couldn't have that, no no no!

(Reply) (Parent) (Thread)
From:srlamb
Date:December 27th, 2007 06:46 am (UTC)

You're thinking like a Linux, not like Sun

(Link)
Keeping everything using the latest version of everything is a very Linuxy attitude - one that works well if you have all of the packages managed by a central authority (RedHat, Debian, Ubuntu, whoever). (And, despite independent development of most packages, they are centrally managed - if the upstream packages don't go the way the same way, the distro packages them. Their goal is to provide a cohesive system.)

Sun, on the other hand, distributes a system for their customers to build mammoth systems Sun may never see, much less patch. They can't force upgrades. In particular, their major success with Java is web applications. Those are all designed so that the servlet container provides you with the bare minimum and you can just copy a .war file to the drop dir. Your .war simply includes whatever whacked-out .jar versions you need; no forced upgrades, dependency hell, or bartering with other webapps required. It's an effective solution to a problem you don't care about.
(Reply) (Parent) (Thread)
[User Picture]
From:omnifarious
Date:December 27th, 2007 06:53 am (UTC)

Re: You're thinking like a Linux, not like Sun

(Link)

The war file thing caused us to end up with gigantic war files that included every .jar under the sun (no pun intended) with no tracking of which versions were being used by which app. It basically created the specialized environment thing I was talking about for each and every single app, with the result that nobody ever tried to update their dependencies ever, and every app probably ended up with all kinds of calcified code that had strange hidden semantic dependencies on bugs or undocumented behavior of all kinds of other libraries.

That solution is leading the company that uses it down a garden path that ends up in gibbering senility.

One of the big arguments I got into at Amazon was that I felt Amazon should support a wide variety of different Linux environments on developer desktops so that when a new version of whatever came out all the existing code wasn't so touchily dependent on the exact behavior of the old version and an upgrade to the new version would be relatively painless.

I think changing your organization to adapt to change in this way is ultimately a cheaper solution, even if it seems more expensive or harder to manage in the short run.

(Reply) (Parent) (Thread)
From:srlamb
Date:December 27th, 2007 07:26 am (UTC)

Re: You're thinking like a Linux, not like Sun

(Link)
I think it's up to each Sun customer to make their own choice. Sun's just not in a position to be mandating a particular way of managing dependencies. You can put all the jars into the servlet container's main classpath if you want. What does it tell you that few people do?

Google basically has one giant source tree with no versioning of individual components. (I think of this as the *BSD model - they import everyone's code into their source tree and run "make world".) It has its ups and downs.
(Reply) (Parent) (Thread)
From:srlamb
Date:December 27th, 2007 06:32 am (UTC)

RTFM?

(Link)
The only thing that would've saved me this much work in Java is encyclopedic knowledge of ant internals and the Java mail API. So stupid!

...or, you know, reading the manual.

It looks like the MimeMailer vs. PlainMailer thing is to allow you to send at least plain mail without having the JavaMail interfaces and classes. In particular, if you don't have JavaMail, the reflection allows you to compile everything but the MimeMailer class.

Hiding the underlying cause of the exception is a horrible idea. It sounds similar to this problem I had four years ago. I guess the state of the art hasn't advanced much.

(Reply) (Thread)
[User Picture]
From:omnifarious
Date:December 27th, 2007 06:47 am (UTC)

Re: RTFM?

(Link)

Strangely enough, that manual is worse than useless as it talks about email without mentioning either of the two jar files I eventually had to copy into place. I bet I would've futzed around hunting down those jar files or hand compiling them myself only to find they were in some way incompatible.

And of course, if I were doing this in Python, I'd say 'install ant' and poof, all the dependencies would be downloaded for me. And if there were extension packages that allowed more ant tasks, there would be 'install ant-mimemail' or something similar.

(Reply) (Parent) (Thread)
From:srlamb
Date:December 27th, 2007 06:48 am (UTC)

Re: RTFM?

(Link)
I saw that. Their instructions are probably better/easier than what you did. IIRC, JavaMail has a single combined .jar file or a split-out version. With the split-out one, you get relatively weird errors when you forget a piece.
(Reply) (Parent) (Thread)
From:rosencrantz319
Date:December 27th, 2007 11:46 am (UTC)
(Link)
(Reply) (Thread)
[User Picture]
From:smitty1e
Date:December 28th, 2007 03:06 am (UTC)

I thought that

(Link)
...whoever dies with the longest, most unintelligible java exception trace wins.
(Reply) (Thread)