Synchronizing objects using file-based locks

The problem

IDM does not provide a way to synchronize objects if the threads are running under the same context. API call checkoutObject() or checkoutView() will not return "object lock by another user" if it's the same user making the second call. Test this by starting two instance of Internet Explorer and log in to IDM under the same user, say 'configurator'. Then try editing user 'john.smith' in both browsers, you will not get any error. But if you log in as different user and try this again, you will get an error like "object john.smith locked by user configurator".

In some instances there is a need to lock the object even if the two threads are running under the same context. For example two workflows that need to update the same object. That is actually addressed in the Sun IDM FAQ ("How to handle concurrent updates to a user object within a workflow?") and the FAQ proposes that one workflow checks the presence of other workflows, and take other extra measures. However the FAQ's answer is a little bit hard to understand and does not provide an example implementation.

This article shows another approach : creating our own locking library.

Directory-based synchronization library

The solution is to create our own synchronization library using directories as locks. The reason directories are used is that locks need to be persistent and OS independent.

A reference implementation using Java and Xpress is show below.

Note 1 : the reason why there is a mix of Java and Xpress is that at first I tried writing the whole thing in Xpress using embedded JavaScript to create and delete directories. However JavaScript had trouble deleting directories so I replaced it with Java.

Note 2 : the library does not return any error if the lock (directory) could not be acquired (created) because in my deployment if that scenario happens it means another workflow did not clean up properly, in that case just get the lock.

Note 3 : creating file and directories might require tweaking the Java security permissions. It might be a good idea to put the directories in the application server temporary folder

Java class

import com.sun.idm.logging.trace.Trace;
import com.sun.idm.logging.trace.TraceManager;
import java.io.File;
import java.io.IOException;
 
/**
 * FileIO.java
 *
 * Some file operations that are just easier to do in Java than JavaScript
 * (also, JavaScript has trouble with deleting directories)
 *  
 * @author vinhant@zerointech.com
 *
 */
public class FileIO
{
    public static boolean mkdirs(String path)
    {
        TRACE.entry1("mkdirs", path);
        boolean success = false;
        if (path != null || path.length() > 0)
        {
            File file = new File(path);
            success = file.mkdirs();
        }
        TRACE.exit1("mkdirs", success);
        return success;
    }
 
    public static boolean delete(String filename)
    {
        TRACE.entry1("delete", filename);
        boolean success = false;
        if (filename != null || filename.length() > 0)
        {
            File file = new File(filename);
            success = file.delete();
        }
        TRACE.exit1("delete", success);
        return success;
    }
 
    public static String removeNonAlphaNum(String input)
    {
        TRACE.entry1("removeNonAlphaNum", input);
        String output = null;
        if (input != null || input.length() > 0)
        {
            StringBuffer sb = new StringBuffer();
            for (int i = 0; i < input.length(); i++)
            {
                char c = input.charAt(i);
                if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
                        || (c >= '0' && c <= '9'))
                {
                    sb.append(c);
                }
            }
            output = sb.toString();
        }
        TRACE.exit1("removeNonAlphaNum", output);
        return output;
    }
 
    private static final Trace TRACE = TraceManager.getTrace("FileIO");
}

XPRESS rule library

<?xml version='1.0' encoding='UTF-8'?>
<!--
RuleLibrary-Lock.xml

Author: vinhant@zerointech.com

-->
<!DOCTYPE Configuration PUBLIC 'waveset.dtd' 'waveset.dtd'>
<Configuration authType='EndUserLibrary' name='Lock'>
    <Extension>
        <Library>
            <Rule authType='EndUserRule' name='AcquireLock'>
                <RuleArgument name="lock"/>
                <RunAsUser>
                    <ObjectRef type='User' id='#ID#Configurator' name='Configurator'/>
                </RunAsUser>
                <Description></Description>
                <block>
                    <defvar name='counter'>
                        <i>0</i>
                    </defvar>
                    <defvar name='max'>
                        <i>120</i>
                    </defvar>
                    <while>
                        <isFalse>
                            <invoke name='mkdirs' class='FileIO'>
                                <ref>lock</ref>
                            </invoke>
                        </isFalse>
                        <invoke class='java.lang.Thread' name='sleep'>
                            <new class='java.lang.Long'>
                                <s>200</s>
                            </new>
                        </invoke>
                        <set name='counter'>
                            <add>
                                <ref>counter</ref>
                                <i>1</i>
                            </add>
                        </set>
                        <cond>
                            <eq>
                                <ref>counter</ref>
                                <ref>max</ref>
                            </eq>
                            <break/>
                        </cond>
                    </while>
                </block>
            </Rule>

            <Rule authType='EndUserRule' name='ReleaseLock'>
                <RuleArgument name="lock"/>
                <RunAsUser>
                    <ObjectRef type='User' id='#ID#Configurator' name='Configurator'/>
                </RunAsUser>
                <Description></Description>
                <block>
                    <invoke name='delete' class='FileIO'>
                        <ref>lock</ref>
                    </invoke>
                </block>
            </Rule>
        </Library>
    </Extension>
    <MemberObjectGroups>
        <ObjectRef type='ObjectGroup' id='#ID#All' name='All'/>
    </MemberObjectGroups>
</Configuration>

Vinh-An Trinh
Originally posted in the Sun IDM forum 12/29/2006
http://forum.java.sun.com/thread.jspa?threadID=5120027&messageID=9417040#9417040