How to load from database and reset Configuration mapping in Web application

499 views Asked by At

I need to load Configuration mapping from the database. The configuration mapping is represented as

   Hashtable<String, Hashtable<String,String>>

We cannot load the mapping on the start since we depend on another war application for database connection. So, the mapping is loaded on the first call to configuration in the class(singleton) ResourcesStorage implementing ServletContextListener. At the same time the reset JSP calls static reset() method in this listener. I added synchronization using reentrant lock. I am locking only resetting because different threads should be able to retrieve data simultaneously. However, this does not work well.

I am getting

IllegalMonitorStateException  on notFull.await();   
java.lang.IllegalMonitorStateException
        at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:127)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1175)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.fullyRelease(AbstractQueuedSynchronizer.java:1606)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:1922)

Is my design correct?

import java.util.Hashtable;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class ResourcesStorage implements ServletContextListener {

private static ResourcesStorage instance;
protected static Log log = LogFactory.getLog(ResourcesStorage.class);
protected Hashtable<String, Hashtable<String,String>> DBsettings =new Hashtable    <String, Hashtable<String,String>>();
private ServletContext context = null;
private boolean isLoading = false;

 public synchronized boolean isLoading() {
    return isLoading;
}

public  synchronized void setLoading(boolean isLoading) {
    this.isLoading = isLoading;
}

private Lock lock = new ReentrantLock();
private Condition notFull = lock.newCondition();

public void contextDestroyed(ServletContextEvent arg0) {}

public void contextInitialized(ServletContextEvent event) {

    this.context = event.getServletContext();
        //initialize the static reference _instance
    instance=this;
    reloadAllSettings();
}

public  Hashtable<String, Hashtable<String,String>> getDBSettings()
{
    return DBsettings;
}
public static String getSettings(String groupName, String keyName)
{
    ResourcesStorage instance = ResourcesStorage.getInstance();
    Hashtable<String, Hashtable<String,String>> dbsettings = instance.getDBSettings();
    Hashtable<String,String> group =(Hashtable<String,String>) dbsettings.get(groupName);
    if(group!=null && !group.isEmpty())
        return group.get(keyName);
    else
        return null;
}

public String getValueSettings (String groupName, String keyName)
{
    try 
    {
        while (isLoading() == false)
        {
             notFull.await();
        }
        Hashtable<String,String> group =(Hashtable<String,String>) DBsettings.get(groupName);
        if(group!=null && !group.isEmpty())
        {
            return group.get(keyName);
        }
        else
        {
            return null;
        }
    }
    catch (Exception e)
    {
        log.error("getValueSetting", e);    
    }
    return null;
}

public static void reSet(){
    ResourcesStorage instance = ResourcesStorage.getInstance();
    instance.reloadAllSettings();
}

public void reloadAllSettings(){
    lock.lock();
    setLoading(true);
    try
    {
// .....            
//getting resources from the database 
        setLoading(false);
        notFull.signal();
    } catch (Exception e) {
//...
    }
    finally
    {
        lock.unlock();
    }
}

public static ResourcesStorage getInstance() 
{
    return instance;
}

public ServletContext getContext() {
    return context;
}

public void setContext(ServletContext context) {
    this.context = context;
}
}
2

There are 2 answers

2
pamphlet On BEST ANSWER

Suggestions:

Either the settings have been loaded or they haven't. If they have been loaded, use them.

If they haven't been loaded, call a synchronized method. This is your only required locking mechanism. In the method check to see if they have been loaded (due to race conditions), if they have, return. Otherwise go get them, while you block the other threads that are waiting.

Make sure you keep your settings reference in a volatile variable.

Edit:

My suggestions do not support having multiple threads loading the same data at the same time. This is intentional.

0
vanOekel On

You can benefit from an AtomicReference in cases where multiple threads access (common) configuration data that can be refreshed at any given moment. Refreshing usually happens by one thread (after a certain event or a certain period), but if multiple threads can refresh at the same time, you need some extra locking (see code example below).

The threads getting the configuration data must re-use the returned instance of the configuration data where necessary to prevent mixing old configuration data with new configuration data. This usually means creating a separate class with the configuration data and methods that use the configuration data. But you'll need that anyway to work with the AtomicReference.

The following code example should give you an idea about how to use an AtomicReference:

import java.util.HashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;

public class Q20708889 {

    private static final Object reloadLock = new Object();
    private static final AtomicInteger reloadCount = new AtomicInteger();

    private AtomicReference<DbSettings> dbSettingsRef = new AtomicReference<DbSettings>();

    public DbSettings getDbSettings() {

        DbSettings dbs = dbSettingsRef.get();
        if (dbs == null) {
            dbs = reload();
        }
        return dbs;
    }

    public DbSettings reload() {

        DbSettings dbs = null;
        int rld = reloadCount.get();
        synchronized (reloadLock) {
            if (rld < reloadCount.get()) {
                // Reload was already done
                dbs = dbSettingsRef.get();
            } else {
                reloadCount.incrementAndGet();
                dbs = new DbSettings();
                dbs.load();
                dbSettingsRef.set(dbs);
            }
        }
        return dbs;
    }

    class DbSettings {

        private HashMap<String, HashMap<String, String>> theSettings;

        public void load() {

            theSettings = new HashMap<String, HashMap<String,String>>();
            // getting resources from the database
        }

        public String getValueSettings(String groupName, String keyName) {

            String value = null;
            HashMap<String, String> group = theSettings.get(groupName);
            if (group != null && !group.isEmpty()) {
                value = group.get(keyName);
            }
            return value;
        }
    }

}