After a series of post, Mark Reinhold today announced that not only is Sun halting the failed JSR-277, but also that it will work on modularizing the Sun JDK based on OSGi. This, from my point of view, might be one of the biggest and best improvements to the JDK in a long time. Granted it’s only the Sun JDK, I don’t think any of the other JDK vendors will object. But, having one, and only one, module system for Java, and one that is stable and battle proven, will greatly improve the Java deployment story. Finally.
In 2008, the JCP is costing Sun opportunities and friends and gaining us very little that I can see.
So I’d like Sun to set the JCP free, turn it over to the community, and when we develop some cool Java-based technology in-house, take it to market, try to make some money with it, and after it’s caught on and the bugs are shaken out, consider whether or not it ought to be taken to the JCP.
Amen.
While I’m a big fan of Scala, and in general like to write more stuff in less code, I’ve seen a few cases of people showing examples of how terse the code gets once its in Scala (or Ruby or what ever) compared to Java. However, in many of those examples, the effect is solely due to using one liner code rather than those, actually readable constructs that code conventions has taught us to use. Not to pick on this post in particular (it does in fact mention that they are not satisfied with the result), but it does show my problem. Of course, that Java code could be written like this:
Vector v = new Vector ();
for(String s : map.keySet()) if(s.startsWith(prefix) && !s.equals(prefix)) v.add(s);
return v.size() > 0 ? v.toArray(new String[0]) : null;
Is that generics beautiful? Not exactly. Do we have to do a lot of stuff the compile could do for us? Yeah. But I would argue that this code is as readable as the Scala code in the post.
(for (val key < - map.keys if key.startsWith(prefix) && (key != prefix)) yield key).toList match {
case Nil => null
case list => list.toArray
}
Which is to say, not a lot. And it’s not significantly more verbose. Now, as the comment on the post show, there are better ways of doing this which really does make Scala shine. That’s the examples we need.
Sometimes it’s interesting just to watch the obvious differences between competing products:
The EJB3 feature pack for WAS: 767 Mb
The entire Geronimo installation: 75 Mb
There’s tons of interesting discussions going on the the JSR-310 dev list right now. It’s the JSR for developing the new(er) date and time API for Java, based of Joda Time. The discussions are currently covering two important aspects, the basic design principles for the API and how to handle non-Gregorian calendars. The latter being something that the Calendar API failed at miserably. Not that it didn’t support it correctly, just that it did it in such an intrusive way that you quickly get annoyed with it.
The design principles this far looks well thought out:
I think that setting basic principles like this for your API is a really good idea. As we learn more and more on what the winning principles are, it would be a topic for a book to collect them much like the GoF book. Personally, I think the XOM principles (also covered here) are the best general guideline currently available.
It looks like we will finally get a decent API for working with dates and times in Java. After the debacles with Date (1900-based years anyone?) and Calendar (just how complex can an API be?), Joda has been a savior. Now, a new JSR has been registred that aims to start with Joda to create a new, java.time API. Hopefully, the leads can fight to keep this one as simple and logical as possible and fight the spec-by-committee plague.
Looks like Maven 2.0.5 might finally get out. Now, if they could only get up to speed on development once again.
When discussing our release builds at work today, I found this article which got me thinking. The author argues that you want the latest javac for your compilations, something I agree with. There has been quite a few bugs in the compilers (see my earlier post on the IBM JDK 1.3 compiler…) warranting using the latest and greatest. Also, since the feature set of javac haven’t changed that much, you would expect it to have continously improved over time (better test suites would also add to this).
The -target parameter for javac will make it produce Java byte code of the right version. But, if you’re not targeting the latest class libraries, you certainly want to catch any problems with using new features (like the classic “new RuntimeException(cause)”) during compilation. Now, the author argues for using a new javac and replacing the boot class loader classpath with an old rt.jar.
What do you think? How do you make your release builds? I will play around with the recommendations in the article just to see how it works out. Maven1 seems to support it using the undocumented maven.compile.bootclasspath property. Ant provides the bootclasspath for the javac task.
Last week I had the joy of setting up our new build server at work. Since we deal with integrating with legacy applications on god-forbidden platforms, we still need to support JDK 1.3. Since some of these platforms are somewhat weird, this tends to mean IBM JDK 1.3. But we’ve been sloppy with building on this JDK for some of our projects. Now, when setting up the build server I made sure to include both IBM JDK 1.3 and 1.4 in the available options and also included them for our main projects. They all broke. Not because we were using some fancy stuff in 1.4. No, no, they broke with the wonderful error
error: The encoding null is not supported
Google to the rescue, finds this page. Turns out that this train-wreck of a JDK doesn’t support non-ASCII characters in the Java source files. Since we’re a Swedish company, quite a few people has included @author tags which contains the letters åäö. Lots of search-and-replaces later all of those has been replaced with the harmless a and o. Oh, and someone also made the mistake of including some curly quotes in a comment which had to be replaced. That was boring, but easy. A lot of builds still wouldn’t work. That’s because some of the i18n unit tests contains strings with funky characters. Had to replace with loading that text from a file. Now everything worked as expected.
Only that some build should instead run on IBM JDK 1.4. These instead produce the equally wonderful
error: IO exception sun.io.MalformedInputException
Gee, thanks. Very helpful this time too. Google again. Found this page. This time I had to set the LANG environment variable to make the system not using UTF-8 (which Ubuntu does by default). Luckily, Pulse made this very easy.
Finally all builds worked, a few hours of massaging perfectly fine projects to build on buggy JDKs. An this was still on Linux, not one of the weird platforms. Luckily, JDKs tends to get better with each new version.
At work, I’m finishing up an application that uses Acegi Security System for authentication and authorization. Last week, I had to create an example configuration that used Active Directory for verifying users and assigning them roles that would allow them into the application. Now, this is probably pretty easy if you know Acegi and LDAP, but since I’m pretty poor at both of them it took me half a day. To save your time (should you be as ignorant as I am), here’s how I did it.
I should point out that below I use Acegi as the name of Acegi Security System. This is not recommended but I though that it’s a least better then ASS ;-)
To start with, my requirements was that I wanted to use the mail alias (in my case niklas.gustavsson) as the login name, not the CN. In our AD (and most others I presume) this is stored in the mailNickname attribute. If you like to use the sAMAccountName it will work the same way, just replace mailNickname.
Second, I wanted users from three different groups (our three offices) to have access.
The main Acegi file is pretty much the boilerplate config from the “Tutorial Sample” so I won’t go into very much depth on that here. What we need in addition to that files, is a working AuthenticationProvider that can be plugged in. For LDAP, there is LdapAuthenticationProvider. The provider needs two things, an Authenticator and an AuthoritiesPopulator.
The Authenticator
For authenticator there are two options, BindAuthenticator that will try to bind (login) to the LDAP server as the user and PasswordComparisonAuthenticator that will use the LDAP compare function to verify the provided password with that stored in LDAP. I went for BindAuthenticator, can’t really say if this decision makes any difference but it worked for me.
The BindAuthenticator needs the connection details for the LDAP server, provided by a Context, in Acegi a DefaultInitialDirContextFactory. Here you just provide the URL to your server (e.g. ldap://ad.example.com), the DN for the user you want to use for lookups, this needs to be a full DN, and the password for that user. In their example, Acegi includes a BaseDN in the server URL, this did not work for me and my config only started working after removing it.
<bean id="initialDirContextFactory"
class="org.acegisecurity.ldap.DefaultInitialDirContextFactory">
<constructor-arg value="ldap://ad.example.com"/>
<property name="managerDn">
<value>CN=queryuser,CN=Users,DC=example,DC=com</value>
</property>
<property name="managerPassword">
<value>secret</value>
</property>
</bean>
Now, here comes the first tricky part. The example in the Acegi documentation uses the userDnPatterns property on BindAuthenticator to perform the bind. Now, I was only able to get this working when logging in using my CN (Niklas Gustavsson) but since I wanted to use the mailNickname I was stuck here. Instead I had to use the filter search capabilities (did I say that Acegi is a marvel of flexibility with the price of complexity?). Here is my understanding of how this works.
- Instead of binding directly, Acegi uses the filter to find a matching user
- If no user is found, that’s a failure.
- If a user is found, takes that users DN and tries to bind using it
- Success to bind means that we are okay, failure means incorrect password
So, we need to set up a filter search. This is done using a FilterBasedLdapUserSearch with the following constructor arguments:
- The BaseDN for the search, for example OU=Users,DC=example,DC=com
- The filter statement
- The context factory
I also provided a property to enable search on any sublevel, leaving the following configuration:
<bean id="userSearch" class="org.acegisecurity.ldap.search.FilterBasedLdapUserSearch">
<constructor-arg index="0">
<value>OU=Users,DC=example,DC=com</value>
</constructor-arg>
<constructor-arg index="1">
<value>(mailNickname={0})</value>
</constructor-arg>
<constructor-arg index="2">
<ref local="initialDirContextFactory"/>
</constructor-arg>
<property name="searchSubtree">
<value>true</value>
</property>
</bean>
Now we’re ready to wire up the BindAuthenticator providing the context factory as a constructor argument and the filter search as the userSearch property:
<bean class="org.acegisecurity.providers.ldap.authenticator.BindAuthenticator">
<constructor-arg>
<ref local="initialDirContextFactory"/>
</constructor-arg>
<property name="userSearch" ref="userSearch"/>
</bean>
The AuthoritiesPopulator
Now, with the user authenticated we need to assign it a role that will allow access to the application. The simplest way to do this is with the DefaultLdapAuthoritiesPopulator. This class will populate the user with a set of roles based on the groups of which its a member. By default it will prefix the role name with “ROLE_” and uppercase the group name. So, a user which is a member of “MyAppUsers” and “MyAppAdmins” will get the roles ROLE_MYAPPUSERS and ROLE_MYAPPADMINS. Good enough, but how do we configure the populator? First we need the BaseDN for groups (e.g. CN=GroupsDC=example,DC=com) and second the attribute that you want to use for the role name, in our case “cn”. The configuration would then look like this:
<bean class="org.acegisecurity.providers.ldap.populator.DefaultLdapAuthoritiesPopulator">
<constructor-arg>
<ref local="initialDirContextFactory"/>
</constructor-arg>
<constructor-arg>
<value>CN=Groups,DC=example,DC=com</value>
</constructor-arg>
<property name="groupRoleAttribute">
<value>cn</value>
</property>
</bean>
What will happen is that Acegi will search this BaseDN for groups of which our user is listed in the “member” attribute. After that, it will take the CN of those groups, prefix, uppercase and assign as roles to the authenticated user.
Now all you need to do is to configure a LdapAuthenticationProvider with the BindAuthenticator as the first constructor argument and the DefaultLdapAuthoritiesPopulator as the last.
The full example configuration can be downloaded here.
The objectDefinitionSource
Last but not least, we need to configure the objectDefinitionSource in the boilerplate file from the tutorial sample to allow users with the correct roles to log in.
<property name="objectDefinitionSource">
<value>
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/login=IS_AUTHENTICATED_ANONYMOUSLY
/**=ROLE_MYAPPUSERS
</value>
</property>
Here we set the role ROLE_MYAPPUSERS should be allowed to access any URL of the application. We make one exception for the login page which any (unauthenticated) user can access, otherwise it would be impossible to log in.
Summary
That’s all you need to talk to an Active Directory and given Acegi’s flexibility you can pretty much bend this configuration any way you want to fit your needs.
I should point out that the possibility to set up all the beans in a simple TestCase instead of having to redeploy and test the web application made trying this out much faster. It also meant that I could try each part (e.g. the FilterBasedLdapUserSearch) in isolation.
Also, the power of Acegi is astounding. Given some (usually quite complex) configuration, it can be tweaked to perform authentication and authorization in pretty much any way imaginable. Very impressive and a great addition to Spring. Hopefully the new configuration support in Spring 2.0 will simplify the XML, I haven’t yet had the possibility to play with it.
Ah, glad to get that of my chest. Feedback, improvements are of course welcome!