package org.epics.archiverappliance.config.persistence;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import jdbm.PrimaryTreeMap;
import jdbm.RecordManager;
import jdbm.RecordManagerFactory;
import org.apache.log4j.Logger;
import org.epics.archiverappliance.config.ConfigPersistence;
import org.epics.archiverappliance.config.ConfigService;
import org.epics.archiverappliance.config.PVTypeInfo;
import org.epics.archiverappliance.config.UserSpecifiedSamplingParams;
import org.epics.archiverappliance.config.exception.ConfigException;
import org.epics.archiverappliance.utils.ui.JSONDecoder;
import org.epics.archiverappliance.utils.ui.JSONEncoder;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;
/**
* Uses JDBM2 as a persistence layer; probably not for production installations as JDBM2 does not support concurrent access and so on.
* To set the path to the JDBM2 file, use the environment variable ARCHAPPL_PERSISTENCE_LAYER_JDBM2FILENAME.
* This defaults to <code>./archapplconfig.jdbm2</code>
* To use this persistence layer, use
* <pre>
* export ARCHAPPL_PERSISTENCE_LAYER="org.epics.archiverappliance.config.persistence.JDBM2Persistence"
* export ARCHAPPL_PERSISTENCE_LAYER_JDBM2FILENAME="/scratch/Archiver/persistence.jdbm2"
* </pre>
* @author mshankar
*
*/
public class JDBM2Persistence implements ConfigPersistence {
private static Logger logger = Logger.getLogger(JDBM2Persistence.class.getName());
public static final String ARCHAPPL_JDBM2_FILENAME = ConfigService.ARCHAPPL_PERSISTENCE_LAYER + "_JDBM2FILENAME";
private String pathToConfigData = "./archapplconfig.jdbm2";
private ConcurrentHashMap<String, PVTypeInfo> cachedTypeInfos = new ConcurrentHashMap<String, PVTypeInfo>();
public JDBM2Persistence() throws ConfigException {
String pathFromEnv = System.getProperty(ARCHAPPL_JDBM2_FILENAME);
if(pathFromEnv == null) {
pathFromEnv = System.getenv(ARCHAPPL_JDBM2_FILENAME);
}
if(pathFromEnv != null) {
pathToConfigData = pathFromEnv;
}
logger.info("Loading JDBM2 data from " + pathToConfigData);
try {
preLoadTypeInfos();
} catch(IOException ex) {
throw new ConfigException("Exception preloading pvTypeInfos", ex);
}
logger.info("Done caching " + cachedTypeInfos.size() + " pvTypeInfos from " + pathToConfigData);
}
@Override
public List<String> getTypeInfoKeys() throws IOException {
return getKeys("TypeInfo");
}
@Override
public PVTypeInfo getTypeInfo(String pvName) throws IOException {
if(cachedTypeInfos.containsKey(pvName)) return cachedTypeInfos.get(pvName);
logger.debug("Getting typeinfo for pv " + pvName + " from db instead of cache");
return getValueForKey("TypeInfo", pvName, new PVTypeInfo(), PVTypeInfo.class);
}
@Override
public void putTypeInfo(String pvName, PVTypeInfo typeInfo) throws IOException {
cachedTypeInfos.put(pvName, typeInfo);
putValueForKey("TypeInfo", pvName, typeInfo, PVTypeInfo.class);
}
@Override
public void deleteTypeInfo(String pvName) throws IOException {
logger.debug("Removing typeinfo for pv " + pvName + " from db and cache");
if(cachedTypeInfos.containsKey(pvName)) cachedTypeInfos.remove(pvName);
removeKey("TypeInfo", pvName);
}
@Override
public List<String> getArchivePVRequestsKeys() throws IOException {
return getKeys("ArchivePVRequests");
}
@Override
public UserSpecifiedSamplingParams getArchivePVRequest(String pvName) throws IOException {
return getValueForKey("ArchivePVRequests", pvName, new UserSpecifiedSamplingParams(), UserSpecifiedSamplingParams.class);
}
@Override
public void putArchivePVRequest(String pvName, UserSpecifiedSamplingParams userParams) throws IOException {
putValueForKey("ArchivePVRequests", pvName, userParams, UserSpecifiedSamplingParams.class);
}
@Override
public void removeArchivePVRequest(String pvName) throws IOException {
removeKey("ArchivePVRequests", pvName);
}
@Override
public List<String> getExternalDataServersKeys() throws IOException {
return getKeys("ExternalDataServers");
}
@Override
public String getExternalDataServer(String serverId) throws IOException {
return getStringValueForKey("ExternalDataServers", serverId);
}
@Override
public void putExternalDataServer(String serverId, String serverInfo) throws IOException {
putStringValueForKey("ExternalDataServers", serverId, serverInfo);
}
@Override
public void removeExternalDataServer(String serverId, String serverInfo) throws IOException {
removeKey("ExternalDataServers", serverId);
}
@Override
public List<String> getAliasNamesToRealNamesKeys() throws IOException {
return getKeys("AliasNamesToRealNames");
}
@Override
public String getAliasNamesToRealName(String pvName) throws IOException {
return getStringValueForKey("AliasNamesToRealNames", pvName);
}
@Override
public void putAliasNamesToRealName(String pvName, String realName) throws IOException {
putStringValueForKey("AliasNamesToRealNames", pvName, realName);
}
@Override
public void removeAliasName(String pvName, String realName) throws IOException {
removeKey("AliasNamesToRealNames", pvName);
}
private synchronized List<String> getKeys(String recordName) throws IOException {
RecordManager recMan = null;
try {
recMan = RecordManagerFactory.createRecordManager(pathToConfigData);
PrimaryTreeMap<String,String> typeInfos = recMan.treeMap(recordName);
List<String> typeInfoKeys = new LinkedList<String>(typeInfos.keySet());
logger.debug(recordName + " returns " + typeInfoKeys.size() + " keys");
return typeInfoKeys;
} finally {
if(recMan != null) { try { recMan.close(); recMan = null; } catch(Exception ex) {} }
}
}
private synchronized <T> T getValueForKey(String recordName, String key, T obj, Class<T> clazz) throws IOException {
RecordManager recMan = null;
try {
recMan = RecordManagerFactory.createRecordManager(pathToConfigData);
PrimaryTreeMap<String,String> map = recMan.treeMap(recordName);
String jsonStr = map.get(key);
if(jsonStr != null) {
JSONObject jsonObj = (JSONObject) JSONValue.parse(jsonStr);
JSONDecoder<T> decoder = JSONDecoder.getDecoder(clazz);
decoder.decode(jsonObj, obj);
return obj;
}
} catch(Exception ex) {
throw new IOException(ex);
} finally {
if(recMan != null) { try { recMan.close(); recMan = null; } catch(Exception ex) {} }
}
return null;
}
private synchronized String getStringValueForKey(String recordName, String key) throws IOException {
RecordManager recMan = null;
try {
recMan = RecordManagerFactory.createRecordManager(pathToConfigData);
PrimaryTreeMap<String,String> map = recMan.treeMap(recordName);
return map.get(key);
} catch(Exception ex) {
throw new IOException(ex);
} finally {
if(recMan != null) { try { recMan.close(); recMan = null; } catch(Exception ex) {} }
}
}
private synchronized <T> void putValueForKey(String recordName, String key, T obj, Class<T> clazz) throws IOException {
if(key == null || key.equals("")) throw new IOException("key cannot be null when persisting " + recordName);
if(obj == null || obj.equals("")) throw new IOException("value cannot be null when persisting " + recordName);
RecordManager recMan = null;
try {
recMan = RecordManagerFactory.createRecordManager(pathToConfigData);
PrimaryTreeMap<String,String> map = recMan.treeMap(recordName);
JSONEncoder<T> encoder = JSONEncoder.getEncoder(clazz);
JSONObject jsonObj = encoder.encode(obj);
String jsonStr = jsonObj.toJSONString();
map.put(key, jsonStr);
} catch(Exception ex) {
throw new IOException(ex);
} finally {
if(recMan != null) { try { recMan.close(); recMan = null; } catch(Exception ex) {} }
}
}
private synchronized void putStringValueForKey(String recordName, String key, String value) throws IOException {
if(key == null || key.equals("")) throw new IOException("key cannot be null when persisting " + recordName);
if(value == null || value.equals("")) throw new IOException("value cannot be null when persisting " + recordName);
RecordManager recMan = null;
try {
recMan = RecordManagerFactory.createRecordManager(pathToConfigData);
PrimaryTreeMap<String,String> map = recMan.treeMap(recordName);
map.put(key, value);
} catch(Exception ex) {
throw new IOException(ex);
} finally {
if(recMan != null) { try { recMan.close(); recMan = null; } catch(Exception ex) {} }
}
}
private synchronized void removeKey(String recordName, String key) throws IOException {
RecordManager recMan = null;
try {
recMan = RecordManagerFactory.createRecordManager(pathToConfigData);
PrimaryTreeMap<String,String> map = recMan.treeMap(recordName);
map.remove(key);
} catch(Exception ex) {
throw new IOException(ex);
} finally {
if(recMan != null) { try { recMan.close(); recMan = null; } catch(Exception ex) {} }
}
}
/**
* Optimization to make the performance test start faster.
* @return
* @throws IOException
*/
private synchronized <T> T preLoadTypeInfos() throws IOException {
RecordManager recMan = null;
try {
recMan = RecordManagerFactory.createRecordManager(pathToConfigData);
PrimaryTreeMap<String,String> map = recMan.treeMap("TypeInfo");
List<String> pvNames = new LinkedList<String>(map.keySet());
JSONDecoder<PVTypeInfo> decoder = JSONDecoder.getDecoder(PVTypeInfo.class);
for(String pvName : pvNames) {
String jsonStr = map.get(pvName);
if(jsonStr != null) {
JSONObject jsonObj = (JSONObject) JSONValue.parse(jsonStr);
PVTypeInfo obj = new PVTypeInfo();
decoder.decode(jsonObj, obj);
cachedTypeInfos.put(pvName, obj);
if(logger.isDebugEnabled()) logger.debug("Caching typeInfo for PV " + pvName);
}
}
} catch(Exception ex) {
throw new IOException(ex);
} finally {
if(recMan != null) { try { recMan.close(); recMan = null; } catch(Exception ex) {} }
}
return null;
}
}