Friday, February 10, 2012

Springify the LDAP application

       Spring LDAP makes LDAP operations so simple. Spring lets you write the code and structure the functionalities around LDAP just like any other data access functionality. It provides fine wrapped template to work with LDAP just like JDBC or JMS. Let me share some of the basic operations using spring’s LdapTemplate that are most common in any application. Spring source has put together a very nice and detailed documentation, so please refer http://static.springsource.org/spring-ldap/docs/1.3.x/reference/html/ for Spring LDAP and refer to open source LDAP http://www.openldap.org/ for more details.

First thing First
       Let’s categorize these basic LDAP operations into the following
         • Creating an entry or Binding.
         • Deleting an entry or unbinding.
         • Retrieving basic attributes.
         • Retrieving attributes using filter.
         • Retrieving operational attributes.

Create an Entry
        To add an entry into the LDAP all you have to do is to call bind method on ldapTemplate. There are two ways of binding the new entry.
First way is calling the bind(Name dn, Object obj, Attributes attributes). So create the Attributes with attributes to bind. Here you are creating a new person object with attributes to bind.


BasicAttribute personBasicAttribute = new BasicAttribute("objectclass");
personBasicAttribute.add("person");
personAttributes.put(personBasicAttribute);
personAttributes.put("cn", “Vinay Shivaswamy”);
personAttributes.put("description", “some description here”);

Create the DistinguishedName by providing the path in string format.


DistinguishedName newContactDN = new DistinguishedName("ou=users");
newContactDN.add("cn", “Vinay Shivaswamy”);

Now call the bind method by passing the Attributes and DistinguishedName just created.

ldapTemplate.bind(newContactDN, null, personAttributes);

Second way is to call bind(DirContextOperations ctx). So create the DistinguishedName by providing the path in string format.


DistinguishedName newContactDN = new DistinguishedName("ou=users");

Create the DirContextOperations object by passing the DistinguishedName just created. Once context is created its just matter of setting attributes into this context as shown below.

DirContextOperations ctx = new DirContextAdapter(dn);
ctx.setAttributeValue("cn", "Vinay Shivaswamy");
ctx.setAttributeValue("description", "some description");

Now Call the bind method by passing the DirContextOperations just created.
ldapTemplate.bind(ctx);

Delete an entry
       Deleting an entry is lot easier than creating one. All you have to do is to call unbind method as shown below by passing DistinguishedName,

DistinguishedName newContactDN = new DistinguishedName("ou=users");
newContactDN.add("cn",”Vinay Shivaswamy”);
ldapTemplate.unbind(newContactDN);

Retrieving basic attributes
       The basic attributes can be retrieved using lookup or search methods of LdapTemplate. Below is an example of using search to get the list of matching common name attributes only. Search would return a List and if you are interested in retrieving unique matching entry then use searchForObject method instead.

ldapTemplate.search(baseName, "(objectclass=person)", new AttributesMapper() {
public Object mapFromAttributes(Attributes attrs) throws NamingException {
return attrs.get("cn").get();
}
});

If you simply want to retrieve all the available attributes then write your own AttributesMapper implementation, add all the attributes to return.

Retrieving attributes using filter
       Retrieving attributes based of search criteria would be helpful and you can do that with the help of Filter. Spring Ldap filter package provides various supporting filters. Look at the example below to see how easy it is to build and use the filter.


AndFilter andFilter = new AndFilter();
andFilter.and(new EqualsFilter("objectclass","person"));
andFilter.and(new EqualsFilter("cn",”Vinay Shivaswamy”));
andFilter.and(new EqualsFilter("sn",”Shivaswamy”));

Once the filter is built then it is just the matter of calling search by passing the filter to retrieve the matching email attribute,


ldapTemplate.search("baseName", andFilter.encode(),new AttributesMapper() {
public Object mapFromAttributes(Attributes attrs)
throws NamingException {
return attrs.get("email").get();
}
});

Retrieving operational attributes
       Ldap Server maintains many operational attributes internally. Example entryUUID is an operational attribute assigns the Universally Unique Identifier (UUID) to the entry. The createTimestamp, modifyTimestamp are also operational attributes assigned to the entry on create or update. These operational attributes does not belong to an object class and hence they were not returned as part of your search or lookup. You need to explicitly request them by their name in your search or build the custom AttributeMapper implementation with matching attribute names.
Now let’s try to retrieve the entryUUID, first you need to build the search controls like this,


SearchControls controls = new SearchControls();
controls.setSearchScope(SearchControls.SUBTREE_SCOPE);
controls.setReturningObjFlag(false);
controls.setReturningAttributes(new String[]{"entryUUID"});

Once you have search control then it’s simply calling search method just like retrieving any other attributes.


ldapTemplate.search("baseName", "(objectclass=person)", controls, new AttributesMapper() {
public Object mapFromAttributes(Attributes attrs) throws NamingException {
Attribute attrUuid = attrs.get("entryUUID");
return attrUuid;
}});

Here is another way to do the same using ContextMapper,


ldapTemplate.search("baseName","(objectclass=person)", 1, new String[]{"entryUUID"},
new ContextMapper(){
public Object mapFromContext(Object ctx) {
DirContextAdapter context = (DirContextAdapter)ctx;
return context.getStringAttributes("entryUUID");
}
});

Let’s add the filter based off of operational attributes like below,


OrFilter orFilter = new OrFilter();
orFilter.or(new GreaterThanOrEqualsFilter("createTimestamp", "YYYYMMDDHHMMSSZ"));
orFilter.or(new LessThanOrEqualsFilter("modifyTimestamp", "YYYYMMDDHHMMSSZ"));

Now call the above search with the filter


ldapTemplate.search("baseName", orFilter.encode(), controls, new AttributesMapper() {
public Object mapFromAttributes(Attributes attrs) throws NamingException {
Attribute attrUuid = attrs.get("entryUUID");
return attrUuid;
}});

Final Note
       Spring simplifies the Ldap operations indeed. We just saw that but there is lot more to dig into if you need to and I hope this article serves as a quick reference to jump start your exploration. Good luck.

Wednesday, February 1, 2012

MDB listening to Oracle AQ using Sun Adapter



Sometime back I had to work on MDB implementation for JBoss 5.1 to connect to oracle AQ. I implemented the solution using Sun AQ resource Adapter.
The purpose of this article is not to justify this approach versus JBoss recommended approaches like JBoss’s default JMS messaging implementation, ESB and so on but is to make it easy for anyone that decides to go with this approach. As this implementation could easily run into many issues, this article should help you going.
Yes this is time consuming and frustrating due to the facts that,
1.  The official support for this adapter has been stopped.
2.  No supporting documents for this implementation.
3.  There are threads in few forums that talk about this implementation but it is kind of hard to put it all together.
First Thing First
Let’s get started, deploy and run the example to see it working.
1.       Download and execute the scripts from myAQ.sql in your oracle schema
a.       Make sure you have proper privileges as stated in the script file, ask your DBA if you are not one.
b.      Create queue table and queue and start the queue
c.       Important create queue table with pay load type SYS.AQ$_JMS_MESSAGE.
d.      Create a procedure from the above script file to enqueue and dequeue the messages.
e.      Run the enqueue procedure.
f.        Run the dequeue procedure, you will see the output
JMS Message is: Testing my first AQ JMS message
2.       Download the sun AQ adapter oracleAQ.rar from the resources section below and put it under your {JBOSS_INSTALL}\server\default\deploy directory.
3.       Download the myAQ-ds.xml and put it under {JBOSS_INSTALL}\server\default\deploy directory.
a.       Modify the highlighted configuration to suite your environment

<mbean code="org.jboss.resource.deployment.AdminObject" name="jboss.oracleaq:service=Queue,name=DEMO_QUEUE">
   <attribute name="JNDIName">myaq/queue/demo</attribute>
   <depends optional-attribute-name="RARName">jboss.jca:service=RARDeployment,name='oracleaq.rar'</depends>
   <attribute name="Type">javax.jms.Queue</attribute>
   <attribute name="Properties">DestinationProperties=owner\=demouser, name\=DEMO_QUEUE</attribute>
</mbean>


<config-property name="ConnectionFactoryProperties" type="java.lang.String">jdbc_connect_string=jdbc:oracle:thin:demouser/demopassword@<host>:<port>:<sid>,host=<host>,user=demouser,password=demopassword,port=<port>,sid=<sid>,driver=thin
</config-property>

    <config-property name="username" type="java.lang.String">demouser</config-property>

    <config-property name="password" type="java.lang.String">demopassword</config-property>

 
4.       Now download all the dependency jars that are needed. Most of these libraries are part of oracle. You could handle these dependencies in 2 ways
a.       Download the following jar files from your oracle installed directory and copy them directly to {JBOSS_INSTALL}\server\default\lib directory.
aqapi.jar
bcel.jar
connector.jar
javax77.jar
jmxri.jar
oc4jclient.jar
ojdbc14.jar

b.      You could rebuild the downloaded oracleaq.rar file to include all the dependency jars above.
5.       Download the source jar file myAQ.jar and put it under {JBOSS_INSTALL}\server\default\deploy directory. Before copying you need to change some configuration in MDB, so to do that
a.       Import the jar project as EJB jar in to eclipse or any IDE of your choice.
b.      Open the java source file MyAqMessageBean.java and update the highlighted configuration through annotations.
@MessageDriven(

activationConfig = {

@ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"),

@ActivationConfigProperty(propertyName = "connectionFactoryProperties", propertyValue = "jdbc_connect_string=jdbc:oracle:thin:demouser/demopassword@{host:port:sid},host=<host>,user=demouser,password=demopassword,port=<port>,sid=<sid>,driver=thin"),

@ActivationConfigProperty(propertyName="destinationProperties", propertyValue="owner=demouser,name=demo_queue"),

    @ActivationConfigProperty(propertyName="userName", propertyValue="demouser"),

    @ActivationConfigProperty(propertyName="password", propertyValue="demopassword"),

    @ActivationConfigProperty(propertyName="ConnectionFactoryClassName", propertyValue="oracle.jms.AQjmsConnectionFactory"),

    @ActivationConfigProperty(propertyName="QueueConnectionFactoryClassName", propertyValue="oracle.jms.AQjmsQueueConnectionFactory")

})

@ResourceAdapter("oracleaq.rar")

c.       Now export the EJB jar directly in to the JBoss deploy directory.
6.       Now it’s time to start the JBoss server.
7.       Enqueue the message by running the procedure
8.       See the messages were picked by MDB on the JBoss console.

Issues
Here are the few issues that you could avoid though,
1.       Do not use 10g Lite since lite doesn’t come with DBMS_AQADM packages installed and so not able to enqueue or dequeue the message type SYS.AQ$_JMS_MESSAGE. There was an alternate solution suggested in some forum i.e. to run some DBMS-XXX package and I couldn’t get it to run so I decided to go for standard 10g edition as that was the only other option to get it work.
2.       Do not create the queue table with payload message type other than $AQ_JMS_MESSAGE as JBoss would talk to the AQ only if the payload is of this type.
3.       The adapter doesn't support XA connections in MDB.
4.       Now the bean listens to the first message only and any subsequent messages were simply lost. Solution to this is to update couple of files in oracleAQ.rar file. Well you should not run into this issue as I have supplied the updated rar file here. The details of those changes are in reference section.
5.       There is one minor glitch that you have to live with is when you try to stop the JBoss you will see error stack due to the JBoss thread confliction while calling releaseEndpoint method in InboundJmsResource.
6.       I tried this solution in JBoss 7.0.1 and at that time there were some issues related to registering the resource adapter with JBoss. I know recently some of those jira issues around this reported to be closed but I haven’t got back on it.
Conclusion
            This solution works perfectly considering application environment scope and limitations with one exception on shutdown. The community would surely appreciate if you share any resolutions to this issue or even better solution.
References
AQ data source configuration myAQ-ds.xml
Source Code myAQ.jar
Oracle AQ resource adapter oracleaq.rar
Oracle10g AQ SQL myAQ_10g.sql
Oracle AQ resource Adapter source oracleaq-source.rar