Tuesday, June 21, 2011

Using UnboundID LDAP SDK Directory API from Scala

I'm very new in the world of LDAP Directory Services. After installing ApacheDS Server, Apache Directory Studio LDAP GUI Client, and trying the plain old ldapsearch and ldapcompare command-line utilities, it was time for me to do some integration with Java applications (I'm actually trying to query LDAP directory from Ant build script task).

The most popular and programmer-friendly LDAP API at the moment is UnboundID LDAP SDK. To get a feel of how it works, I decided to use Scala REPL interpreter. I really love this tool (and the Scala programming language itself) more and more every time I use it!

The screen captures below doesn't do justice to the Scala interpreter. The tab-completion works really well!
Indeed the UnboundID LDAP SDK is really easy to use, and it's better with powerful Scala & its versatile interpreter for a quick ride. :-)

Learn Scala programming language quicker! Get Programming in Scala: A Comprehensive Step-by-Step Guide, 2nd Edition.

Connect & Bind using UnboundID LDAP SDK and Scala

ceefour@annafi:~/vendor/unboundid-ldapsdk-2.2.0-se$ scala -cp unboundid-ldapsdk-se.jar  Welcome to Scala version 2.9.0.1 (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_25). Type in expressions to have them evaluated. Type :help for more information. scala> import com.unboundid.ldap.sdk._ import com.unboundid.ldap.sdk._ scala> val con = new LDAPConnection con: com.unboundid.ldap.sdk.LDAPConnection = LDAPConnection(not connected) scala> con.connect("localhost", 10389) scala> con.bind("uid=admin,ou=system","password") res5: com.unboundid.ldap.sdk.BindResult = LDAPResult(resultCode=0 (success), messageID=1)

Connect & Bind via SSL

scala> import com.unboundid.util.ssl._ import com.unboundid.util.ssl._ scala> var sslUtil = new SSLUtil( new TrustAllTrustManager() ) sslUtil: com.unboundid.util.ssl.SSLUtil = com.unboundid.util.ssl.SSLUtil@43763e0b scala> var socketFactory = sslUtil.createSSL createSSLContext createSSLServerSocketFactory createSSLSocketFactory scala> var socketFactory = sslUtil.createSSLSocketFactory socketFactory: javax.net.ssl.SSLSocketFactory = com.sun.net.ssl.internal.ssl.SSLSocketFactoryImpl@43d7a5a scala> val con = new LDAPConnection LDAPConnection LDAPConnectionInternals LDAPConnectionOptions LDAPConnectionPool LDAPConnectionPoolHealthCheck LDAPConnectionPoolHealthCheckThread LDAPConnectionPoolStatistics LDAPConnectionReader LDAPConnectionStatistics scala> val con = new LDAPConnection(socketFactory, "localhost", 10636) con: com.unboundid.ldap.sdk.LDAPConnection = LDAPConnection(connected to localhost:10636) scala> con.bind("uid=admin,ou=system","password") res18: com.unboundid.ldap.sdk.BindResult = LDAPResult(resultCode=0 (success), messageID=1)

Search

scala> val results=con.search("ou=system",SearchScope.SUB,"(uid=admin)") results: com.unboundid.ldap.sdk.SearchResult = SearchResult(resultCode=0 (success), messageID=3, entriesReturned=1, referencesReturned=0) scala> results.getSearchEntries res8: java.util.List[com.unboundid.ldap.sdk.SearchResultEntry] = [SearchResultEntry(dn='uid=admin,ou=system', messageID=3, attributes={Attribute(name=uid, values={'admin'}), Attribute(name=keyAlgorithm, values={'RSA'}), Attribute(name=sn, values={'administrator'}), Attribute(name=objectClass, values={'person', 'organizationalPerson', 'inetOrgPerson', 'tlsKeyInfo', 'top'}), Attribute(name=displayName, values={'Directory Superuser'}), Attribute(name=cn, values={'system administrator'}), Attribute(name=publicKey, base64Values={'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAJjX2EbUDyBZYFPfsSNMZRsW3mLc/KiKRO6pck3J3eFMKErXA7jxpiho/Eoc6RWgxut2K3aITnQZIl1hHF6Hv30CAwEAAQ=='}), Attribute(name=privateKeyFormat, values={'PKCS#8'}), Attribute(name=publicKeyFormat, values={'X.509'}), Attribute(name=privateKey, ba... scala> results.getSearchEntries.get(0) res11: com.unboundid.ldap.sdk.SearchResultEntry = SearchResultEntry(dn='uid=admin,ou=system', messageID=3, attributes={Attribute(name=uid, values={'admin'}), Attribute(name=keyAlgorithm, values={'RSA'}), Attribute(name=sn, values={'administrator'}), Attribute(name=objectClass, values={'person', 'organizationalPerson', 'inetOrgPerson', 'tlsKeyInfo', 'top'}), Attribute(name=displayName, values={'Directory Superuser'}), Attribute(name=cn, values={'system administrator'}), Attribute(name=publicKey, base64Values={'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAJjX2EbUDyBZYFPfsSNMZRsW3mLc/KiKRO6pck3J3eFMKErXA7jxpiho/Eoc6RWgxut2K3aITnQZIl1hHF6Hv30CAwEAAQ=='}), Attribute(name=privateKeyFormat, values={'PKCS#8'}), Attribute(name=publicKeyFormat, values={'X.509'}), Attribute(name=privateKey, base64Values={'MII...

Compare

scala> con.compare("uid=admin,ou=system", "userPassword", "password") res12: com.unboundid.ldap.sdk.CompareResult = LDAPResult(resultCode=5 (compare false), messageID=4, opType='compare', matchedDN='uid=admin,ou=system') scala> con.compare("uid=admin,ou=system", "uid", "admin") res13: com.unboundid.ldap.sdk.CompareResult = LDAPResult(resultCode=6 (compare true), messageID=5, opType='compare', matchedDN='uid=admin,ou=system')

Get Entry & Attribute Value

scala> val entry = con.getEntry("uid=admin,ou=system") entry: com.unboundid.ldap.sdk.SearchResultEntry = SearchResultEntry(dn='uid=admin,ou=system', messageID=7, attributes={Attribute(name=uid, values={'admin'}), Attribute(name=keyAlgorithm, values={'RSA'}), Attribute(name=sn, values={'administrator'}), Attribute(name=objectClass, values={'person', 'organizationalPerson', 'inetOrgPerson', 'tlsKeyInfo', 'top'}), Attribute(name=displayName, values={'Directory Superuser'}), Attribute(name=cn, values={'system administrator'}), Attribute(name=publicKey, base64Values={'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAJjX2EbUDyBZYFPfsSNMZRsW3mLc/KiKRO6pck3J3eFMKErXA7jxpiho/Eoc6RWgxut2K3aITnQZIl1hHF6Hv30CAwEAAQ=='}), Attribute(name=privateKeyFormat, values={'PKCS#8'}), Attribute(name=publicKeyFormat, values={'X.509'}), Attribute(name=privateKey, base64Values={'MII... scala> entry.getAttribute getAttribute getAttributeValue getAttributeValueAsBoolean getAttributeValueAsDN getAttributeValueAsDate getAttributeValueAsInteger getAttributeValueAsLong getAttributeValueByteArrays getAttributeValueBytes getAttributeValues getAttributes getAttributesWithOptions scala> entry.getAttribute def getAttribute(String): Attribute def getAttribute(String, schema.Schema): Attribute scala> entry.getAttribute("userPassword") res16: com.unboundid.ldap.sdk.Attribute = Attribute(name=userPassword, values={'{SHA}W6ph5Mm5Pz8GgiULbPgzG37mj9g='}) scala> entry.getAttributeValue("userPassword") res17: java.lang.String = {SHA}W6ph5Mm5Pz8GgiULbPgzG37mj9g=

Scala 2.9.0.1 final version released: programming language with parallel features for Java VM

UPDATE: Scala 2.9.0.1 has replaced 2.9.0 as it hot-fixed several important bugs.

Scala 2.9.0 final:

We are happy to announce the release of the new stable release of the Scala distribution. The new Scala 2.9.0 final is available from our Download Page. The Scala 2.9.0 codebase includes several additions, notably the new Parallel Collections, but it also introduces improvements on many existing features, and contains many bug fixes.

Scala 2.9.0 binaries are available for the following libraries:

Of course.

Here are some recommended learning resources on Scala programming language:

Also, the Typesafe Stack is also out, which brings together Scala, Akka, and a few other things to get one up-and-running quickly. Much fun.

On the collection side of things, one of the first questions I saw was: do parallel collections share a common interface with standard collections. The answer is yes, they do, but not one that existed in 2.8.1.

You see, a trouble with parallel collections is that, now that they are available, people will probably be passing them around. If they could be passed to old code -- as it was briefly contemplated -- that old code could crash in mysterious ways. In fact, it happens with REPL itself.

For that reason, ALL of your code comes with a guarantee that it will only accept sequential collections. In other words, Iterable, Seq, Set, etc, they all now share a guarantee to be sequential, which means you cannot pass a parallel sequence to a method expecting Seq.

The parallel collections start with Par: ParIterable, ParSeq, ParSet and ParMap. No ParTraversable for now. These are guaranteed to be parallel. They can be found inside scala.collection.parallel, scala.collection.parallel.immutable, etc.

You can also get a parallel collection just by calling the ".par" method on it, and, similarly, the ".seq" method will return a sequential collection.

Now, if you want your code to not care whether it receives a parallel or sequential collection, you should prefix it with Gen: GenTraversable, GenIterable, GenSeq, etc. These can be either parallel or sequential.

And, now, something fun to try out:

def p[T](coll: collection.GenIterable[T]) = coll foreach println; p(1 to 20); p((1 to 20).par)

 

I highly recommend the following books for more information about Scala programming language:

 

 

Copied mostly verbatim from this announcement and that announcement.