Monday, January 21, 2008

I finally made up my mind

Those who read this blog more often may have noticed the minor change in the sub-title. It now reads:
Experiences from a hard core Java programmer that finally decided Ruby is a very very nice language, but rather stays on the JVM.
Compare it to the previous text:
Experiences from a hard core Java programmer that struggles with the question of switching to the world of Ruby and Ruby on Rails.
So how did I come to this conclusion? Here are some random considerations:
  • I have only once in the last 2 years used Ruby professionally (to do some counting in a site dump).
  • I do have 3 Ruby sites running (one Camping and two using Radiant). So I am not going to stop using Ruby anytime soon.
  • ActiveRecord totally kicks ass and is hugely impressive. Rails for the rest not so much (totally old school request response based, with like JSP code in the templates). It may be just my lack of experience, so don't base your opinion on this.
  • I am very addicted to correct refactoring and code completion.
  • As I wrote before, we now have Scala.
  • Update 2008-01-23: This one needs to be added. Since I recently became a father, it is simply too much to keep track of 2 worlds at the same time.
So if you expected me to be on amsterdam.rb right now, here is why.

GWT in action - Book review

I recently joined JTeam, a self declared GWT specialist. Of course I do not want to stay behind so I started reading GWT in Action.

That was a mistake. There is nothing wrong with GWT, I think it is a very nice component oriented technology in the spirit of Swing and Wicket, which makes it a lot easier to build very complex applications.

But back to the book. Reading the book is a lot like looking at an extremely knowledgeable professor. The kind of professors that know so much that they don't know where to begin. And like with the professor you will eventually get it if you hang on and are able to sort it all out in your head.
Well, I was fed up with it in chapter 5, half way through the 17 chapter book. Here are my mischiefs: 1) the text was constantly referring to chapters further away in the book, 2) it was continuously repeating itself (I had to read at least 4 times that it is okay to use something else then Eclipse), but worse, 3) the text was almost continuously on a different conceptual level then the reader could be. For example implementation details are mentioned (for example the event_bits attribute in the generated HTML) while they have nothing to do with making you understand the API (writing an event callback class). Or the other way around, at places where a simple examples would do wonders there is only some broad goal description.

Since there are many other books on GWT, you are probably better of with another one.

Thursday, December 27, 2007

JRuby on Rails - Book review

So what do you put in a book on JRuby? After all, for the programmer JRuby is 'just' another Ruby implementation. The question must have come up when Ola Bini decided to write this book. The result is materialized in Practical JRuby on Rails Web 2.0 Projects - Bringing Ruby on Rails to the Java Platform. The title is well chosen and also answers the question: JRuby on Rails is a practical book; it guides you through the implementation of several Rails sites and along the way it shows you neat tricks that are only possible in JRuby.

Contents
The first half of the book is all about JRuby on Rails. If you already know Rails it is funny to consistently see jruby instead of ruby, but otherwise this is part is boring for the initiated. If you do not know Rails and like to learn from examples, this chapter is well worth your read.

Right after that is an interesting part for all us that want to mix Java and Ruby.

Later in the book, Ola goes through the trouble of explaining how to use JRuby with all the non interesting things in Java: session beans, message beans, JMX, XML processing and SOAP. I have been in Java business for 7 years and have been luckily able to avoid these for most of the time. The integration provided by Spring could have been a very nice replacement for these subjects.

A bit hidden between this stuff, the book again shows it practicality and goes through all the options of deploying JRuby on Rails applications.

The book ends with some convenient JRuby specific references.

Conclusion
I strongly recommend this book when you are a Java programmer (of any skill) that knows some Ruby and want to start working with (J)Ruby on Rails. If you know Rails well, but little Java and you want to start with JRuby on Rails, the book is probably too heavy and will teach you only a few useful things. Do not start on this book with zero Ruby knowledge. Though appendix A helps, for seriously learning Ruby, the old Pickaxe (dead tree version) is an excellent first read.

Sunday, December 16, 2007

WebBeans, the JSF cover up

At JavaPolis I saw very good presentation on WebBeans by Bob Lee. Bob did a very good job in explaining the concepts while thankfully still giving lots of code examples. WebBeans provides a way to use ordinary Java beans in the JSF environment. You do this with annotations.

Praise for WebBeans, and its main predecessor Seam; they really make developing JSF applications simpler. In particular, the conversation scope is a brilliant invention. But how simple does it get? Well lets see. For every aspect that can be managed there is an annotation. In one of the presentation examples I counted about 5 lines of ordinary Java code plus at least 12 annotations! Now there are ways to group annotations, but you do this by introducing more annotations! So instead of having to deal with JSF stuff, you now how to cook annotation soup.

I have always hated JSP programming, and now that I see that even talented people like Bob Lee and Gavin King are wrestling to fix JSF, I finally know why. The problem is that the whole idea of having contexts to store page data is flawed. It breaks all kinds of encapsulation rules and breaks the beloved type safety.

So what are the alternatives? Actually there are a few very nice web frameworks that do not use contexts. The one I am most familiar with is Wicket. Another often named contender is Tapestry, but of course there are many more. Wicket, like Swing, uses a component tree to build pages. Each component is completely responsible for its own markup (html) and data (the model). No encapsulation rules are broken. Another addition is GWT, not exactly a web framework, but nevertheless useful for making componentized web applications.

The demise of Java, long live the JVM!

The Java language is dying. Most people do not yet realize this yet, but it is inevitable. What do I mean by dying? Why do I think it is dying, and why is there lots of hope?

Dead languages
So what do I mean by dead? First of all, languages do not die just like that. There are still people programming in Cobol, and likewise, there are still people that can read old Greek fluently. But just like Cobol and every other programming language, Java will at some time be left to the dinosaur programmers. I think that Java is currently at its peak (or will soon be), and will go downwards from here. It will take some time, but the signs are there.

The signs
First of all, there is the rise of dynamic languages. They have been around for quite some time, but all of sudden there was Ruby on Rails. Many Java programmers bit the bullet and switched to be rewarded with a up to 10 times productivity boost. There are those that say this can be attributed to dynamic typing. I believe this is not the case. I think the boost is possible because the Java language is quite weak compared to Ruby; the same problem can be expressed in a lot less lines of Ruby then lines of Java. This has nothing to do with typing, but everything with how the language is structured.

Secondly, there is the way Java is extended over time. The extensions have been done with great care. This has paid off: Java is still mostly a clean and simple language. Even with the tiny changes, each new version brought its problems. The largest change so far has been the introduction of generics. I see generics as an improvement, but as I wrote earlier, they can be a pain to use.
And now there is the closures debate. Some people (like James Gosling) want to have them at every price. Others (like Joshua Bloch) say that the complexity budget has been used and that Java is not ready for big changes like closures. I agree with the latter.

Despite the care with which Java was and is being extended, the possibilities for doing so are reaching zero rapidly.

There is hope!
If there is one gem coming out of the Java world, it is the Java Virtual Machine. With each release the JVM has brought performance improvements. And this is likely to continue for the next versions. The JVM is still climbing and is nowhere near its peak. This can be proved by looking at the many languages that now run on the JVM. Everything from PHP, Lisp, Cobol to Ruby and Python. Some of these (in particular Ruby) are very well supported by several IDEs.

The Java language does not need closures or other more advanced stuff, there are many languages on the JVM that already provide those things: Ruby, Python, Lisp and Scala.

Yes, now there is Scala! Scala is a language for both the JVM and the CLR. Scala's syntax is as concise as Ruby's. It provides a very smooth transition from the Java language, but nevertheless is a complete functional language while at the same time staying purely object oriented and statically typed. And, as I learned at Javapolis, its library is already excellent, and even better: without any particular optimizations Scala programs outperform Java programs on the same JVM! What is not to like about Scala?

Disclaimer
Many statements in this article are based on personal observations and anecdotal evidence.

Update 2007-12-24: Despite the tone of this article, I am still writing Java and foresee to do so for quite a bit longer. And note: having fun doing it!

Thursday, November 22, 2007

Double-Checked Locking found in JVM

I just found an implementation of the broken double checked locking in the java 6 runtime library!

Here is a snippet of the offending code:

package java.lang; import java.util.Random; public final class Math { private static Random randomNumberGenerator; private static synchronized void initRNG() { if (randomNumberGenerator == null) randomNumberGenerator = new Random(); } public static double random() { if (randomNumberGenerator == null) initRNG(); return randomNumberGenerator.nextDouble(); } }

Amazingly, it used to work correctly, but the extra synchronization was removed in java 1.3. You can track the progress of this bug in report 6470700 and report 6633229.

Friday, November 16, 2007

Howto extend LDAP in java with JLDAP

LDAP is a protocol that is wonderfully extensible. You can augment existing messages by adding 'controls', and you can define complete new messages. Extensions are identified by a universal OID, so that even code that does not know about an extensions can still work properly. For this each extension has a criticality flag to indicate whether the receiver may ignore unknown extensions. As a bonus, the content of controls and messages are all defined by a common syntax (ASN.1) and common encoding (which is BER with restrictions).

Writing you own controls and messages is a kind of under documented thing. In addition, not all LDAP libraries support all kinds of messages. In this small howto I show how to implement custom controls and messages based on my experiences with implementing a RFC4533 (synchronization) client. I used JLDAP as it is the only java library I could find that supports IntermediateMessages, a requirement for RFC4533. And before you ask: no sorry, I can not open source the results.

In case you actually want to start with JLDAP, you get a lot of knowledge from at the examples that are provided by Novel. You can find them through the JLDAP site. The javadoc is also useful at times.

Decoding a control

Lets take a look at the SyncDoneControl from RFC4533. The control's OID is 1.3.6.1.4.1.4203.1.9.1.3 and its content value is defined with ASN.1 as:

syncDoneValue ::= SEQUENCE { cookie OCTET STRING OPTIONAL, refreshDeletes BOOLEAN DEFAULT FALSE }

Read this as: the value is a sequence that contains 2 other values. The first, named cookie is optional and has binary content. The second is named refreshDeletes and has boolean content. The default of refreshDeletes is false. See RFC4533 for the semantics.

Lets map this to java. All controls must have the same constructor signature so that JLDAP can instantiate it. We'll start with:

public class SyncDoneControl extends LDAPControl { public static final String OID = "1.3.6.1.4.1.4203.1.9.1.3"; private byte[] cookie; private boolean refreshDeletes; // add getters for cookie and refresDeletes public SyncDoneControl(String oid, boolean critical, byte[] value) { super(oid, critical, value); ...see below } }

The byte array value contains the BER encoded value of the control. The LDAP restriction put on the BER encoding mean that optional values and values that are equal to the default value must be omitted. With other words: when there is no cookie (allowed because it is declared OPTIONAL), and refreshDeletes is FALSE (which is the default), constructor argument value is null! Just to be robust we'll check for the empty array as well:

if (value == null || value.lenght == 0) { cookie = null; refreshDeletes = false; } else { ...see below }

If it is not null/empty, we'll use the decoder as provided by JLDAP to decode the bytes. As the ASN.1 value is defined to start with a SEQUENCE (one of the native ASN.1 types), the LBERDecoder will instantiate an object of type ASN1Sequence:

ASN1Sequence asn1 = (ASN1Sequence) new LBERDecoder().decode(value);
The decoder can decode all native ASN.1 types. These native types are called "universal". Other important universal types are BOOLEAN, OCTET STRING, CHOICE and SET. Type information is available on every ASN1Object through the ASN1Object#getIdentifier() method.

We can examine the sequence further by calling the ASN1Sequence#size() and ASN1Sequence#get(int) methods. Again, we must take into account that each element may be omitted. You can do this by examining the type of ASN.1 value you get out of the sequence. First, extract a value from the sequence:

ASN1Object asn1Obj = asn1.get(0);
When this is the cookie, the value must be from the type-class UNIVERSAL, with as type OCTET STRING:
boolean isCookie = asn1Obj.getIdentifier().getASN1Class() == ASN1Identifier.UNIVERSAL && asn1Obj.getIdentifier().getTag() == ASN1OctetString.TAG;
If it is, we can safely cast the object to an ASN1OctetString and extract the cookie:
cookie = ((ASN1OctetString) asn1Obj).byteValue()

We can do the same for the value refreshDeletes and JLPAP class ASN1Boolean. After we have moved this very verbose code to the utility class Asn1Util (exercise for the reader) we'll get the following code:

ASN1Sequence asn1 = (ASN1Sequence) new LBERDecoder().decode(value); for (int i = 0; i < asn1.size(); i++) { ASN1Object asnSeqObj = asn1.get(i); if (i == 0 && Asn1Util.isOctetString(asnSeqObj)) { cookie = Asn1Util.getByteValue(asnSeqObj); } else if (i == (cookie == null ? 0 : 1) && Asn1Util.isBoolean(asnSeqObj)) { refreshDeletes = Asn1Util.getBooleanValue(asnSeqObj); } else { throw new IllegalArgumentException("Parse error at index " + i + ", parsing: " + asnSeqObj); } }

Tada! Your first JLDAP extension. All we have to do is make JLDAP aware of the extension and it will be parsed automatically when the control is present in a received LDAP message.

LDAPControl.register(SyncDoneControl.OID, SyncDoneControl.class);

One small warning: when there is an exception in the control's constructor, JLDAP will silently ignore your class and do its default thing.

Encoding a control

To start a sync operation, one must add a SyncRequestControl to the search constraints. Here is the ASN.1 definition of the control value:

syncRequestValue ::= SEQUENCE { mode ENUMERATED { refreshOnly (1), refreshAndPersist (3) }, cookie OCTET STRING OPTIONAL, reloadHint BOOLEAN DEFAULT FALSE }

First the ASN.1 enumeration is translated into a Java enumeration:

public enum SyncRequestMode { REFRESH_ONLY, REFRESH_AND_PERSIST }

The we'll start the control with

public class SyncRequestControl extends LDAPControl { public static final String OID = "1.3.6.1.4.1.4203.1.9.1.1"; private SyncRequestMode mode; private byte cookie[]; boolean reloadHint = false;

As we will construct this control ourself, and not JLDAP, we can give it any constructor we like. For example:

public SyncRequestControl(SyncRequestMode mode, byte cookie[], boolean reloadHint) { super(OID, true, null); this.mode = mode; this.cookie = cookie; this.reloadHint = reloadHint; setValue(encodedValue()); }

In the last line we set the BER encoded value. Here is a complete implementation of the encode method. Note how we follow the ASN.1 definition, but skip optional values and values that have the default value.

private byte[] encodedValue() throws IOException { ASN1Sequence asn1 = new ASN1Sequence(); asn1.add(new ASN1Enumerated(mode == REFRESH_ONLY ? 1 : 3)); if (cookie != null) { asn1.add(new ASN1OctetString(cookie)); } if (reloadHint) { asn1.add(new ASN1Boolean(reloadHint)); } ByteArrayOutputStream baos = new ByteArrayOutputStream(); new LBEREncoder().encode(asn1, baos); return baos.toByteArray(); }

More complex example, decoding a message

The JLDAP BER decoder can only decode ASN.1 universal types. As soon as you define your own types, you must help the decoder. Lets look at the decoding of the SyncInfoMessage to see how this works. The value of SyncInfoMessage is defined with the following ASN.1:

syncInfoValue ::= CHOICE { newcookie [0] OCTET STRING, refreshDelete [1] SEQUENCE { cookie OCTET STRING OPTIONAL, refreshDone BOOLEAN DEFAULT TRUE }, refreshPresent [2] SEQUENCE { cookie OCTET STRING OPTIONAL, refreshDone BOOLEAN DEFAULT TRUE }, syncIdSet [3] SEQUENCE { cookie OCTET STRING OPTIONAL, refreshDeletes BOOLEAN DEFAULT FALSE, syncUUIDs SET OF OCTET STRING (SIZE)16)) } }

The ASN.1 defines that the value can have one or four values. We'll represent the chosen value with a Java enumeration. By defining the enum values in order, we can abuse that the ordinal value of the enum values corresponds to the tag (defined between brackets []) value.
public static enum SyncInfoMessageChoiceType { // Note: order is important NEW_COOKIE, REFRESH_DELETE, REFRESH_PRESENT, SYNC_ID_SET }

As SyncInfoMessage is an intermediate response, we'll start the message implementation as:

public class SyncInfoMessage extends LDAPIntermediateResponse { public static final String OID = "1.3.6.1.4.1.4203.1.9.1.4"; private SyncInfoMessageChoiceType syncInfoMessageChoiceType; private byte[] cookie; private Boolean refreshDone; private Boolean refreshDeletes; private List syncUuids; // add getters ...

Not all fields will always get a value. For example field syncUuids will only be set when syncInfoMessageChoiceType == SYNC_ID_SET. This is the most simple implementation, and the user of this class must know about the CHOICE type anyway.

Intermediate messages must always have the same constructor, so that JLDAP can construct it for us:

public SyncInfoMessage(RfcLDAPMessage message) { super(message); ...

The choice is represented by an instance of type ASN1Tagged. The identifier of the tag indicates the choice. Instantiate field syncInfoMessageChoiceType so:

ASN1Tagged asn1Choice = (ASN1Tagged) new LBERDecoder().decode(getValue()); int tag = asn1Choice.getIdentifier().getTag(); syncInfoMessageChoiceType = SyncInfoMessageChoiceType.values()[tag];

Now comes the tricky part. As JLDAP has no clue about the ASN.1 definition, it does not know about the choice, and it can not decode any further. What we can do is get the contents of asn1Choice as an OCTET STRING, get its byte array, and decode that again with the JLDAP decoder.

So for most choice types we need to decode the tag's contents to a SEQUENCE. Here is a utility method we can add to the AsnUtil class:

public static ASN1Sequence parseContentAsSequence(ASN1Tagged asn1Choice) throws IOException { ASN1OctetString taggedValue = (ASN1OctetString) asn1Choice.taggedValue(); byte[] taggedContent = taggedValue.byteValue(); return new ASN1Sequence(new LBERDecoder(), new ByteArrayInputStream(taggedContent), taggedContent.length); }

With this tool we'll decode the choice refreshPresent. Notice how we decode the contents of the tag, and how refreshDone is set to its default value when we did not see it in the sequence.

if (syncInfoMessageChoiceType == SyncInfoMessageChoiceType.REFRESH_PRESENT) { ASN1Sequence asn1Seq = Asn1Util.parseContentAsSequence(asn1Choice); for (int i = 0; i < asn1Seq.size(); i++) { ASN1Object asnSeqObj = asn1Seq.get(i); if (i == 0 && Asn1Util.isOctetString(asnSeqObj)) { cookie = Asn1Util.getByteValue(asnSeqObj); } else if ((i == (cookie == null ? 0 : 1)) && Asn1Util.isBoolean(asnSeqObj)) { refreshDone = Asn1Util.getBooleanValue(asnSeqObj); } else { throw new RuntimeException("Parse error"); } } if (refreshDone == null) { refreshDone = Boolean.TRUE; } }

When the choice is newCookie things are a bit simpler. The content of asnChoice is already an OCTET_STRING, so we can use that directly:

if (syncInfoMessageChoiceType == SyncInfoMessageChoiceType.NEW_COOKIE) { ASN1OctetString taggedValue = (ASN1OctetString) asn1Choice.taggedValue(); cookie = taggedValue.byteValue(); }

Again, we have to make JLDAP aware of the new message. it will be parsed automatically when the control is present in a received LDAP message.

LDAPIntermediateResponse.register(SyncInfoMessage.OID, SyncInfoMessage.class);

Conclusion

In this article I showed how to get started with extending JLDAP. The example are functional but not always complete. Nor are they always according to best practices (for example, I would normally not declare so many exceptions). I shared some of the pitfalls you will encounter when encoding and decoding messages and controls.