Logging database changes in lift-mapper

Here is a little trick to log the changed properties
on the lift-mapper objets.

  • First we define a changeLogger trait that extends Mapper

    
    trait ChangeLogger[T <: Mapper[T] ] {
        def checkChanges(obj: T): Unit = {
            if (obj.dirty_?) {
    
                Log.debug("Object " + obj.getSingleton.dbTableName + " has changed")
    
                obj.formFields.foreach(f => {
                    f.dirty_? match {
                        case true => Log.debug(" ---> Property %s was='%s' and now is = '%s'".format(f.name, f.is.toString, f.was.toString))
                    }
                })
            }
        }
    }
    
    

  • Extend LongKeyedMetaMapper (or any other MetaMapper class) to call ChangeLogger checkChanges method after save (or after delete …)

    
    trait ChangeLoggerMetaMapper[T <: LongKeyedMapper[T]] extends LongKeyedMetaMapper[T]
                                                    with ChangeLogger[T] { self : T =>
        override def afterSave: List[(T) => Unit] = checkChanges _ :: super.afterSave
    }
    
    
    
  • Model object

    
    class Item extends LongKeyedMapper[Item] with IdPK
                                             with ChangeLogger[Item] {
        def getSingleton = Item
    
        object name extends MappedString(this, 100) {
            override def validations = valMinLen(1, "Must be not empty") _ ::
                                       valUnique("Name must be unique") _ ::
                                       super.validations
        }
    }
    
    object Item extends Item with ChangeLoggerMetaMapper[Item] {
        def findByName(name: String): List[Item] = {
            Item.findAll(By(Item.name, name))
        }
    }
    
    
    



The code can be found on http://github.com/jgoday/sample_lift_testing

Documentation for lift-mapper (version lift-1.1-M6) http://scala-tools.org/mvnsites/liftweb-1.1-M6/lift-mapper/scaladocs/index.html

Advertisements

Sample list items with lift

Add/Edit/Remove items in lift sample

  • 1- Create the menu in boot

    in src/main/scala/bootstrap/liftweb/Boot.scala

    
    // List items menu
    val itemsMenu = Menu(Loc("Items", "items" :: "index" :: Nil, "List items"))
    
    // Build SiteMap
    val entries = Menu(Loc("Home", List("index"), "Home")) :: itemsMenu :: Nil
    
  • 2- Define the default database
    The properties in src/main/resources/props/default.props with the connection url

    
    db.driver   = org.h2.Driver
    db.url      = jdbc:h2:~/hello_db
    db.user     =
    db.password =
    

    in src/main/scala/bootstrap/liftweb/Boot.scala

    
    
    DB.defineConnectionManager(DefaultConnectionIdentifier, DefaultDBVendor)
    Schemifier.schemify(true, Log.infoF _, Item)
    
    ...
    
    object DefaultDBVendor extends ConnectionManager {
        def newConnection(name: ConnectionIdentifier): Box[Connection] = {
            try {
                val connectionUrl = Props.get("db.url").openOr("") + "?user=" +
                                    Props.get("db.username").openOr("") + "&password=" +
                                    Props.get("db.password").openOr("")
    
                Class.forName(Props.get("db.driver").open_!)
                val dm = DriverManager.getConnection(connectionUrl)
                return Full(dm)
            }
            catch {
                case e: Exception => {
                    Log.error(e.getMessage);
                    return Empty
                }
            }
        }
    
        def releaseConnection(conn: Connection) = conn.close
    }
      
    
  • 3- Create items/index.html
    in src/main/webapp/items/index.html

    
    <lift:surround with="default" at="content">
        <ul>
            <lift:adminItems.list>
                <li>
                    <item:name />
                </li>
            </lift:adminItems.list>
        </ul>
    </lift:surround>
    
    

    and snippet in src/main/scala/com/sample/snippet/AdminItems.scala

    
    package com.sample.snippet
    
    import scala.xml.{NodeSeq, Text}
    
    import net.liftweb.util.{Helpers, Log}
    import Helpers._
    
    import com.sample.model.Item
    
    class AdminItems {
    
        def list(node: NodeSeq): NodeSeq = {
            Item.findAll match {
                case Nil   => Text("There is no items in db")
                case items => items.flatMap(i => bind("item", node, "name" -> {i.name}))
            }
        }
    ...
    }
    
  • 4- Add item form

    create a template for add/edit items in src/main/webapp/templates-hidden/item_form.html,
    we are going to use the same template for edit and add items,
    using TemplateFinder in AdminItems snippet

    
    <fieldset>
        <legend><itemForm:title /></legend>
        <label for="name">Name</label>
    
        <itemForm:name />
        <itemForm:id />
    
        <itemForm:submit />
    </fieldset>
    
    

    we are going to use two requestvars to decide when to display the edit form or the add form

    
        private object ShowAddItem  extends RequestVar[Int](0)
        private object SelectedItem extends RequestVar[Long](0)
    

    declare a showAddItem and showEditItem function in adminItems snippet

    
        def clear = {
            ShowAddItem(0)
            SelectedItem(0)
        }                  
    
        def showAddItem(node: NodeSeq): NodeSeq = {
            ShowAddItem.get match {                
                case 1 => {                        
                    var name = ""                  
                    val template: NodeSeq = TemplateFinder.findAnyTemplate(ITEM_TEMPLATE :: Nil).openOr(<p></p>)                                                                     
                    val content = bind("itemForm", template, "title"  -> Text("New item"),    
                                                              "name"   -> SHtml.text(name, name = _),                                                                                   
                                                              "id"     -> Text(""),           
                                                              "submit" -> SHtml.submit("save", () => addItem(name)),                                                                    
                                                              "close"  -> SHtml.link("index", () => clear, Text("close")))                                                              
    
                    <div>{content}</div>
                }                       
                case _ => SHtml.link("index", () => ShowAddItem(1), <p>Add</p>)
            }                                                                  
        }                                                                      
    
        def showEditItem(node: NodeSeq): NodeSeq = {
            if (SelectedItem.get > 0) {             
                var id = SelectedItem.get           
                val item = Item.find(id).open_!     
    
                var name = item.name.is
    
                val template = TemplateFinder.findAnyTemplate(ITEM_TEMPLATE :: Nil).openOr(<p></p>) 
                val content  = bind("itemForm", template, "title"  -> Text("Edit item"),      
                                                          "name"   -> SHtml.text(name, name = _),                                                                                       
                                                          "id"     -> Html.hidden(() => {id = id}),                                                                                    
                                                          "submit" -> SHtml.submit("save", () => saveItem(id, name)),                                                                   
                                                          "close"  -> SHtml.link("index", () => clear, Text("close")))                                                                  
    
                <div>{content}</div>
            }                       
            else {                  
                <div></div>
            }                       
        }           
    

    Now we can call the showEditItem and showAddItem snippets functions in items/index view

    
    <lift:surround with="default" at="content">
        <ul>
            <lift:adminItems.list>
                <li>
                    <item:name />
    
                    <item:edit />
                    <item:remove />
                </li>
            </lift:adminItems.list>
        </ul>
    
        <lift:adminItems.showAddItem  form="POST" />
        <lift:adminItems.showEditItem form="POST" />
    </lift:surround>
    
    

    The attribute form=”POST” on the snippet will create a form with method=”POST” automatically arround the snippet content.

    And that’s it, a simple list with liftweb.
    The code can be found on http://github.com/jgoday/sample_lift_testing

Lift testing with dbunit and specs

Here’s a post about Lift and testing,
using scala specs and dbunit.

We can use dbunit to populate a sample database for testing purposes,
in this example we are going to use the embedded h2 database.

  • 1. Create a sample liftweb (1.1-M8) application using maven 2.2

    
    mvn org.apache.maven.plugins:maven-archetype-plugin:2.0-alpha-4:generate        \
        -DarchetypeGroupId=net.liftweb        \
        -DarchetypeArtifactId=lift-archetype-blank        \
        -DarchetypeVersion=1.1-M8        \
        -DremoteRepositories=http://scala-tools.org/repo-releases        \
        -DgroupId=com.sample -DartifactId=hello
    
    

    And add the testing dependencies to pom.xml (specs, h2database and dbunit)

    
    
    <dependency>
        <groupId>org.scala-tools.testing</groupId>
        <artifactId>specs</artifactId>
        <version>1.4.4</version>                  
        <scope>test</scope>
    </dependency>                                 
    
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <version>1.2.125</version>   
        <scope>test</scope>     
    </dependency>                     
    
    <dependency>
        <groupId>org.dbunit</groupId>
        <artifactId>dbunit</artifactId>
        <version>2.4.7</version>
        <scope>test</scope>
    </dependency>
    
    
  • 2. Create a sample model vo (Item with name and id)

    in src/main/scala/com/sample/model/Item.scala,
    a sample object with a string attribute name that cannot be empty.

    
    package com.sample.model
    
    import net.liftweb.mapper.{By, IdPK,
                               LongKeyedMapper, LongKeyedMetaMapper,
                               MappedString}
    
    class Item extends LongKeyedMapper[Item] with IdPK {
        def getSingleton = Item
    
        object name extends MappedString(this, 100) {
            override def validations = valMinLen(1, "Must be not empty") _ ::
                                       super.validations
        }
    }
    
    object Item extends Item with LongKeyedMetaMapper[Item] {
        def findByName(name: String): List[Item] = {
            Item.findAll(By(Item.name, name))
        }
    }
    
    

    We can test the compilation to let maven download the dependencies
    without testing : mvn package -Dmaven.test.skip=true

  • 3. Create a sample Specs test file for item

    in src/test/scala/com/sample/model/ItemSpecs.scala,

    
    package com.sample.model
    
    import org.specs.Specification
    import org.specs.runner.JUnit4
    
    class ItemSpecsAsTest extends JUnit4(ItemSpecs)
    
    object ItemSpecs extends Specification {
        "Item" should {                     
            "do nothing wrong" in {
                true must beTrue
            }
        }
    }
    
    
  • 4. Create a database util class to initialize database and populate data

    in src/test/scala/com/sample/utils/DBUtil.scala,

    it will initialize the default database connection identifier using the properties file in
    src/test/resources (by default src/test/resources/props/test.default.props)

    and will let us choose a dbunit dataset file to populate the test data using
    DBUtil.setupDB(“dbunit_database_file.xml”)

    
    package com.sample.utils                                                     
    
    import net.liftweb.mapper.{DB,
                              ConnectionIdentifier, DefaultConnectionIdentifier,
                              StandardDBVendor}                                 
    import net.liftweb.util.{Log, Props}                                        
    
    import org.dbunit.dataset.{IDataSet}
    import org.dbunit.dataset.xml.{XmlDataSet}
    import org.dbunit.database.{DatabaseConfig, DatabaseConnection}
    import org.dbunit.operation.{DatabaseOperation}                
    
    object DBUtil {
        private var dataset: IDataSet      = null
        private var dbunitConnection: DatabaseConnection = null
    
        lazy val connectionIdentifier: () => ConnectionIdentifier = {
            () => DefaultConnectionIdentifier                        
        }                                                            
    
        def initialize = {
            DB.defineConnectionManager(connectionIdentifier(),
                                      new StandardDBVendor(Props.get("db.driver").openOr(""),
                                                           Props.get("db.url").openOr(""),   
                                                           Props.get("db.user"),             
                                                           Props.get("db.password")))        
        }                                                                                    
    
        def setupDB(filename: String) = {
            this.dataset = new XmlDataSet(this.getClass().getClassLoader().getResourceAsStream(filename))
    
            DB.use(connectionIdentifier()) {
                conn => {
                    this.dbunitConnection = new DatabaseConnection(conn)
                    DatabaseOperation.CLEAN_INSERT.execute(this.dbunitConnection, this.dataset)
                }
            }
        }
    
        def shutdownDB = {
            DB.use(connectionIdentifier()) {
                conn => {
                    try {
                        DatabaseOperation.DELETE.execute(this.dbunitConnection, this.dataset)
                    }
                    catch {
                        case e: Exception => Log.error(e.getMessage)
                    }
                }
            }
    
        }
    }
    
    
  • 5. Now we can do some more interesting test

    using a sample dataset in src/test/resources/dbunit/item_test.xml,
    the lift Shemifier class will create the database table on the beggining of the test
    and destroy it at the end.

    
    package com.sample.model                                                        
    
    import net.liftweb.mapper.{Schemifier}
    import net.liftweb.util.{Log}         
    
    import com.sample.utils.DBUtil
    
    import org.specs.Specification
    import org.specs.runner.JUnit4
    
    class ItemSpecsAsTest extends JUnit4(ItemSpecs)
    
    object ItemSpecs extends Specification {
        "Item" should {                     
            doFirst {                       
                DBUtil.initialize           
                Schemifier.schemify(true, Log.infoF _ , Item)
                DBUtil.setupDB("dbunit/item_test.xml")       
            }                                                
    
            "save without problem" in {
                val item = new Item
                item.name("item name")
    
                item.save must beTrue
                (Item.findAll.length == 3) must beTrue
            }
    
            "find by name" in {
                val items = Item.findByName("Item 1")
                items.length must_== 1
            }
    
            "delete without problem" in {
                val items = Item.findByName("item name")
    
                items.length must_== 1
                items(0).delete_! must beTrue
                Item.findAll.length must_== 2
            }
    
            doLast {
                DBUtil.shutdownDB
                Schemifier.destroyTables_!!(Log.infoF _, Item)
            }
        }
    }
    
    
    

Simple and funny 🙂

The sample code can be found in
http://github.com/jgoday/sample_lift_testing

Lift-LDAP module updated

lift-ldap
sample_lift_ldap

Some code cleanup …
Now it’s easier to create the user model object and customize it.

  • The LDAPProtoUser defines now the ldapRoles SessionVar to get the user roles or groups.
    Also defines the rolesSearchFilter and rolesNameRegex to search and get the roles/groups.

    
    trait LDAPProtoUser[T <: LDAPProtoUser[T]] extends MegaProtoUser[T] {
        self: T =<
        /**
         * User Roles LDAP search filter
         */
        def rolesSearchFilter: String = "(&(objectclass=groupofnames)(member=%s))"
    
        /**
         * Regular expression to get user roles names
         */
        def rolesNameRegex = ".*cn=(.[^,]*),ou=.*"
    
        object ldapRoles extends SessionVar[List[String]](List())
    
        override def getSingleton: MetaLDAPProtoUser[T]
    
        object uid extends MappedString(this, 64) {
            override def dbIndexed_? = true
        }
    
        object dn extends MappedString(this, 64) {
            override def dbIndexed_? = true
        }
    
        object cn extends MappedString(this, 64) {
            override def dbIndexed_? = true
        }
    
        def getRoles: List[String] = {
            return ldapRoles.get
        }
    
        def setRoles(userDn: String, ldapVendor: LDAPVendor): AnyRef = {
            def getGroupNameFromDn(dn: String): String = {
                val regex = new Regex(rolesNameRegex)
    
                val regex(groupName) = dn
                return groupName
            }
    
            // Search for user roles
            val filter = rolesSearchFilter.format(userDn)
    
            val groups = ldapVendor.search(filter)
            groups.foreach(g => {
                ldapRoles.set(ldapRoles.get + getGroupNameFromDn(g))
            })
        }
    }
    
    
    
  • The user model object only has to redefine the roles search (the ldap search sentence or implement a setRoles function to realize the custom search)

    
    class User extends LDAPProtoUser[User] {
        def getSingleton = User
    }
    
    object User extends User with MetaLDAPProtoUser[User] {
        override def screenWrap = Full(<lift:surround with="default" at="content">
                       <lift:bind />
        </lift:surround>)
    }
    
    

    Overriding default values …

    
    object User extends User with MetaLDAPProtoUser[User] {
        override def loginErrorMessage: String = "'%s' is not a valid user or password does not match"
        override def ldapUserSearch: String = "(&(objectClass=inetOrgPerson)(uid=%s))"
    
        override def rolesNameRegex: String = ".*cn=(.[^,]*),.*"
        override def rolesSearchFilter: String = "(&(objectclass=groupofnames)(!(cancellationdate=*))(member=%s))"
    
        override def screenWrap = Full(<lift:surround with="default" at="content">
                       <lift:bind />
        </lift:surround>)
    }
    

Now have to remove some unused imports 🙂

Lift LDAP

One of the requisites to start using Lift at my work, was to use LDAP authentification.
So i wrote a little module lift-ldap for that and a sample app, it was damn simple !

To use the module,

  • 1. lift-ldap requirements in maven pom.xml
    
    <dependency>
        <groupId>net.liftweb</groupId>
        <artifactId>lift-ldap</artifactId>
        <version>1.0.0</version>
    </dependency>
    
  • 2. Create the user object in src/scala/com/sample/model/User.scala

    
    package com.sample.model
    
    import scala.util.matching.{Regex}
    import scala.xml.{NodeSeq}
    
    // lift ldap
    import net.liftweb.ldap.{LDAPProtoUser, MetaLDAPProtoUser, LDAPVendor, SimpleLDAPVendor}
    
    import net.liftweb.common.{Box, Full}
    import net.liftweb.http.{S, SessionVar}
    import net.liftweb.mapper.{KeyedMetaMapper}
    
    object roles extends SessionVar[List[String]](List())
    
    class User extends LDAPProtoUser[User] {
        def getSingleton = User
    
        def getRoles: List[String] = {
            return roles.get
        }
    }
    
    object User extends User with MetaLDAPProtoUser[User] {
    
        override def screenWrap = Full(
                       
        )
    
        override def dbTableName = "tmp_users"
    
        override def login : NodeSeq = {
            val groupNameRx = new Regex(".*cn=(.*),ou=.*")
    
            def getGroupNameFromDn(dn: String): String = {
                val groupNameRx(groupName) = dn
                return groupName
            }
    
            def setRoles(userDn: String, ldapVendor: LDAPVendor): AnyRef = {
                // buscamos o grupo do usuario
                val filter = "(&(objectclass=groupofnames)(member=" + userDn + "))"
    
                val groups = ldapVendor.search(filter)
                groups.foreach(g => {
                    roles.set(roles.get + getGroupNameFromDn(g))
                })
            }
    
            login(setRoles _)
        }
    }
    
    

    The User object has to provide a setRoles function to the LDAPVendor (when do login),
    so we can customize the way in which we retrieve the credentials from LDAP (from a group of names or a custom object)

  • 3. Initialize the LDAP configuration in Boot.scala (src/main/scala/bootstrap/liftweb/Boot.scala)

    
    We can pass a properties file to the SimpleLDAPVendor
    SimpleLDAPVendor.parameters = () =>
                SimpleLDAPVendor.parametersFromStream(
                    this.getClass().getClassLoader().getResourceAsStream("ldap.properties"))
    
    or just manually :
    SimpleLDAPVendor.parameters = () => Map("ldap.url"  -> "ldap://localhost",
                                            "ldap.base" -> "dc=company,dc=com",
                                            "ldap.userName" -> "...",
                                            "ldap.password" -> "...")
                
    
  • 4. A LoginUtils class (src/main/scala/com/sample/lib/LoginUtil.scala)

    To determine when the user is logged or have some credentials

  • 5. Create the security rules in Boot

    
    
        LiftRules.dispatch.prepend(NamedPF("Login Validation") {
            case Req("group_required" :: page, extension, _) if !LoginUtil.hasAuthority_?("sample_group") =>
                    LoginUtil.redirectIfLogged("/login/group_not_allowed")
            case Req("login_required" :: page , extension, _) if (!LoginUtil.isLogged) =>
                    () => Full(RedirectResponse("/user_mgt/login"))
        })
    

And that’s it 🙂

Scala and ldap

Playing around with Scala, a very nice jvm language!,
found Lift (web framework),
looks simple and funny !

I was looking for ldap authentication with lift,
i wasn’t able to find nothing :(.

So i started to play with it,
Here, I install an openldap server and some simple scala code to play with it.

  • Install openldap and some sample data (using arch linux)

    
    1- sudo pacman -S openldap
    2- slappasswd -h {MD5} -s password (generates ldap password to use it later in config and user ldif file)
       {MD5}X03MO1qnZdYdgyfeuILPmQ==
    
  • Generate the ldap structure in initial_structure.ldif file

    
    dn: dc=company,dc=com
    dc: company
    description: LDAP Main object
    objectClass: organization
    objectClass: dcObject
    o: company.com
    
    dn: ou=Users,dc=company,dc=com
    ou: Users
    objectClass: organizationalUnit
    
    dn: ou=Groups,dc=company,dc=com
    ou: Groups
    objectClass: top
    objectClass: organizationalUnit
    
    dn: cn=main_group,ou=Groups,dc=company,dc=com
    gidNumber: 2000
    objectClass: posixGroup
    objectClass: top
    cn: main_group
    
    dn: cn=secondary_group,ou=Groups,dc=company,dc=com
    gidNumber: 2001
    objectClass: posixGroup
    objectClass: top
    cn: secondary_group
    
  • Configure ldap server in /etc/openldap/slapd.conf

    
    include         /etc/openldap/schema/core.schema
    include         /etc/openldap/schema/cosine.schema
    include         /etc/openldap/schema/courier.schema
    include         /etc/openldap/schema/inetorgperson.schema
    include         /etc/openldap/schema/nis.schema
    
    allow bind_v2
    password-hash {md5}
    
    pidfile   /var/run/slapd.pid
    argsfile  /var/run/slapd.args
    
    database        bdb
    suffix          "dc=company,dc=com"
    rootdn          "cn=admin,dc=company,dc=com"
    rootpw          {MD5}X03MO1qnZdYdgyfeuILPmQ==
    
    directory       /var/lib/openldap/openldap-data
    index   objectClass     eq
    index   uid     eq
    
  • Populate initial ldap structure

    
    sudo /usr/sbin/slapadd -l initial_structure.ldif
    
  • Populate user and group data, for example in file users.ldif

    
    dn: uid=sample_user_1,ou=Users,dc=company,dc=com
    objectClass: top
    objectClass: person
    objectClass: organizationalPerson
    objectClass: inetOrgPerson
    objectClass: posixAccount
    objectClass: shadowAccount
    uid: sample_user_1
    cn: Test User
    sn: User
    givenName: Test
    userPassword: {MD5}X03MO1qnZdYdgyfeuILPmQ==
    loginShell: /bin/bash
    uidNumber: 10000
    gidNumber: 2000
    homeDirectory: /home/users/test/
    
  • Starts ldap server and add users.ldif

    
    sudo /etc/rc.d/sldap start
    ldapadd -x -D "cn=admin,dc=company,dc=com" -f users.ldif -W
    
  • Test ldap server searching for sample_user_1 user
    
    ldapsearch -x -D "cn=admin,dc=company,dc=com" -b "dc=company,dc=com" "(&(uid=sample_user_1))" -W
    


  • And now scala code 🙂
    
    import java.io.FileInputStream
    import java.util.{Hashtable, Properties}
    
    import javax.naming.Context
    import javax.naming.directory.{BasicAttributes, SearchControls}
    import javax.naming.ldap.{LdapName, InitialLdapContext}
    
    import scala.collection.jcl.{MapWrapper}
    import scala.util.logging.{Logged, ConsoleLogger}
    
    implicit def convert(javaMap: Hashtable[String, String]) = {
        Map.empty ++ new MapWrapper[String, String]() {
            def underlying = javaMap
        }
    } 
    
    type StringMap = Map[String, String]
    
    val DEFAULT_URL = "localhost"
    val DEFAULT_BASE_DN = ""
    val DEFAULT_USER = ""
    val DEFAULT_PASSWORD = ""
    
    object SimpleLDAPSearch {
        lazy val ldap: LDAPSearch = {
            if (properties() == null) {
                val p = new Properties()
                p.load(new FileInputStream(propertiesFile()))
    
                // automatically calls convert(javaMap: Hashtable[String, String])
                properties = () => p.asInstanceOf[StringMap]
            }
    
            new LDAPSearch(properties()) with ConsoleLogger
        }
    
        var properties: () => StringMap = () => null
        var propertiesFile: () => String = {
            () => "DEFAULT_PROPERTIES_FILE.properties"
        }
    }
    
    class LDAPSearch(parameters: StringMap) extends Logged {
        lazy val initialContext = getInitialContext(parameters)
    
        def search(filter: String) : List[String] = {
            log("--> LDAPSearch.search: Searching for '%s'".format(filter))
    
            var list = List[String]()
    
            val ctx = initialContext
    
            if (!ctx.isEmpty) {
                val result = ctx.get.search(parameters.getOrElse("ldap.base", DEFAULT_BASE_DN),
                                            filter,
                                            getSearchControls())
    
                while(result.hasMore()) {
                    var r = result.next()
                    list = list ::: List(r.getName)
                }
            }
    
            return list
        }
    
        def bindUser(dn: String, password: String) : Boolean = {
            log("--> LDAPSearch.bindUser: Try to bind user '%s'".format(dn))
    
            var result = false
    
            try {
                var env = new Hashtable[String, String]()
                env.put(Context.PROVIDER_URL, parameters.getOrElse("ldap.url", DEFAULT_URL))
                env.put(Context.SECURITY_AUTHENTICATION, "simple")
                env.put(Context.SECURITY_PRINCIPAL, dn + "," + parameters.get("ldap.base"))
                env.put(Context.SECURITY_CREDENTIALS, password)
                env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory")
    
                var ctx = Some(new InitialLdapContext(env, null))
    
                result = !ctx.isEmpty
                ctx.get.close
            }
            catch {
                case e: Exception => println(e)
            }
    
            log("--> LDAPSearch.bindUser: Bind successfull ? %s".format(result))
    
            return result
        }
    
        private def getInitialContext(props: StringMap) : Option[InitialLdapContext] = {
    
            log("--> LDAPSearch.getInitialContext: Get initial context from '%s'".format(props.get("ldap.url")))
    
            var env = new Hashtable[String, String]()
            env.put(Context.PROVIDER_URL, props.getOrElse("ldap.url", DEFAULT_URL))
            env.put(Context.SECURITY_AUTHENTICATION, "simple")
            env.put(Context.SECURITY_PRINCIPAL, props.getOrElse("ldap.userName", DEFAULT_USER))
            env.put(Context.SECURITY_CREDENTIALS, props.getOrElse("ldap.password", DEFAULT_PASSWORD))
            env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory")
    
            return Some(new InitialLdapContext(env, null))
        }
    
        private def getSearchControls() : SearchControls = {
            val searchAttributes = new Array[String](1)
            searchAttributes(0) = "cn"
    
            val constraints = new SearchControls()
            constraints.setSearchScope(SearchControls.SUBTREE_SCOPE)
            constraints.setReturningAttributes(searchAttributes)
            return constraints
        }
    }
    
    // SimpleLDAPSearch.propertiesFile = () => "ldap.properties"
    
    SimpleLDAPSearch.properties = () => {
        Map("ldap.url" -> "ldap://localhost",
            "ldap.userName" -> "cn=admin,dc=company,dc=com",
            "ldap.password" -> "password",
            "ldap.base" -> "dc=company,dc=com")
    }
    
    val list1 = SimpleLDAPSearch.ldap.search("(uid=sample_user_1)")
    println(SimpleLDAPSearch.ldap.bindUser(list1(0), "password"))
    

    The code is not exactly perfect,
    but shows how simple can scala be.

Here’s my favourites lines of code :

  • Automatically convert types

    
    implicit def convert(javaMap: Hashtable[String, String]) = {
        Map.empty ++ new MapWrapper[String, String]() {
            def underlying = javaMap
        }
    }
    

    To automatically convert a java.util.Hashtable (java.util.Properties) into a scala Map,
    Example :

    
    val hashtable: java.util.Hastable[String, String] = new java.util.Hashtable[String, String]()
    hashtable.put("some_key", "some_value")
    
    val map = hashtable.asInstanceOf[Map[String, String]]
    
  • A var that contains a method that returns the ldap properties file

    
    object SimpleLDAPSearch {
        var propertiesFile: () => String = {
            () => "DEFAULT_PROPERTIES_FILE.properties"
        }
    ..
    // The SimpleLDAPSearch singleton propertiesFile method can be override in any moment
    SimpleLDAPSearch.propertiesFile = () => "/tmp/ldap.properties"
    

akonadi with postgresql, part 3

Now, allowing to change database driver (MySQL or postgreSQL) from the akonadi kcm module.

The patch akonadi database driver patch

snapshot63

snapshot64