Implementing Embedded Database for Corporate Developers

There are some corporate developers who would claim they were able to build "massive" distributed systems that were able to serve 100,000 users or so(I could imagine them now flapping their ears). But if you ask them what if someone/SOMETHING suddenly pulls the network cables behind the asses of their servers or a sudden power outage that no UPS can handle left some part of the data center dead and most of the clusters crippled? Huge chances are these morons will give you a blank stare and will argue that those things are absurd and will never happen.

To make it easy for corporate developers to understand some aspects of "mission-critical" elements of an application. First, we look at the RDBMS/JMS application and how to maintain its data integrity in times of any untoward crisis. Suppose we had a transaction that has to be committed across some distant SQL database when all of a sudden the JMS Provider and the remote DB went down probably due to a nuclear attack leaving only less reliable lines available. Nonetheless, data has still to be transmitted to the other end. So the requirements to perform that has to be streamlined as well which means the absence of using SQL databases is inevitable because the transaction due to its emergency nature has to be fast and stable so therefore the use of JDBC is definitely a big minus also, and the only available transmission is via HTTP. Likewise, we should not forget the RDBMS are all down as well, this is the worst possible scenario. And since the JMS is also down transmission to queues will be impossible.

Now, here's one solution for free. Let's introduce the use of SleepyCat's BerkeleDB Java Edition 1.5. There's no need to tell who uses BerkeleyDB because this article is intended to help corporate developer understands the nature of what they're suppose to be doing in times of crisis.

Let's now look at the code (AlarmLogDBEnv.java)that initializes a BerkeleyDB Database Environment. The nature of this code is to write an Alarm message to BerkeleyDB when RDBMS is not available.


import java.io.File;

import org.apache.log4j.Logger;

import com.sleepycat.bind.serial.StoredClassCatalog;
import com.sleepycat.je.Cursor;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentConfig;

/**
* Description:
*
* This is the helper class for utilizing sleepycat embedded db.
*
* @author Jared Odulio
*
*/
public class AlarmLogDBEnv {

private Database alarmlogDB;
private DatabaseConfig dbConfig;
private Environment env;
private EnvironmentConfig envConfig;
private StoredClassCatalog alarmsCatalog;
private Cursor cursor;
private String dbEnv;
private String key;
private DatabaseEntry alarmKey;
private DatabaseEntry alarmData;
private Logger logger;
private File fileEnv;

/**
*
*/
public AlarmLogDBEnv(String dbEnv) {

super();
this.dbEnv = dbEnv;
initialize();

}

/**
* Copy constructor that takes a File object as an
* argument.
*
* @param fileEnv
*/
public AlarmLogDBEnv(File fileEnv){

this.fileEnv = fileEnv;
initWithFile();

}

private void initWithFile(){

logger = Logger.getLogger(this.getClass());

envConfig = new EnvironmentConfig();
envConfig.setAllowCreate(true);
//envConfig.setTransactional(true);

try {

env = new Environment(fileEnv, envConfig);
dbConfig = new DatabaseConfig();
dbConfig.setAllowCreate(true);
//dbConfig.setTransactional(true);
alarmlogDB = env.openDatabase(null, "AlarmLogDB", dbConfig);

} catch (DatabaseException e) {

logger.error(e.toString(), e);

}

}

/**
* Initialize this class with only the String value of the Database
* Environment path as argument.
*
*/
private void initialize(){

logger = Logger.getLogger(this.getClass());

envConfig = new EnvironmentConfig();
envConfig.setAllowCreate(true);
//envConfig.setTransactional(true);

try {

env = new Environment(new File(dbEnv), envConfig);
dbConfig = new DatabaseConfig();
dbConfig.setAllowCreate(true);
//dbConfig.setTransactional(true);
alarmlogDB = env.openDatabase(null, "AlarmLogDB", dbConfig);

} catch (DatabaseException e) {

logger.error(e.toString(), e);

}

}

/**
* @param string
*/
public void setKey(String string) {
key = string;
}


/**
* @return
*/
public Environment getEnv() {
return env;
}

/**
* @return
*/
public Database getAlarmlogDB() {
return alarmlogDB;
}

public void close(){

if (env != null){

try {

alarmlogDB.close();
env.close();

} catch (DatabaseException e) {

logger.error(e.toString(), e);

}
}

}
}


Of course we will need to create a DAO-Pattern-style value object and call it EmbeddedAlarm.java


/**
* Description:
* This is the model used for storing and retrieving alarm log data into the
* Berkeley Embedded DB.
*
* @author Jared Odulio
*
*/
public class EmbeddedAlarm {

private int alarmID;
private int errorID;
private int agentID;
private int moduleID;
private String instanceID;
private String hostAddress;
private int severity;
private String remarks;
private String status;
private long entryDate;
private String userID;
private long clearDate;

/**
* @return
*/
public int getAgentID() {
return agentID;
}

/**
* @return
*/
public int getAlarmID() {
return alarmID;
}

/**
* @return
*/
public long getClearDate() {
return clearDate;
}

/**
* @return
*/
public long getEntryDate() {
return entryDate;
}

/**
* @return
*/
public int getErrorID() {
return errorID;
}

/**
* @return
*/
public String getHostAddress() {
return hostAddress;
}

/**
* @return
*/
public String getInstanceID() {
return instanceID;
}

/**
* @return
*/
public int getModuleID() {
return moduleID;
}

/**
* @return
*/
public String getRemarks() {
return remarks;
}

/**
* @return
*/
public int getSeverity() {
return severity;
}

/**
* @return
*/
public String getUserID() {
return userID;
}

/**
* @param i
*/
public void setAgentID(int i) {
agentID = i;
}

/**
* @param i
*/
public void setAlarmID(int i) {
alarmID = i;
}

/**
* @param l
*/
public void setClearDate(long l) {
clearDate = l;
}

/**
* @param l
*/
public void setEntryDate(long l) {
entryDate = l;
}

/**
* @param i
*/
public void setErrorID(int i) {
errorID = i;
}

/**
* @param string
*/
public void setHostAddress(String string) {
hostAddress = string;
}

/**
* @param i
*/
public void setInstanceID(String string) {
instanceID = string;
}

/**
* @param i
*/
public void setModuleID(int i) {
moduleID = i;
}

/**
* @param string
*/
public void setRemarks(String string) {
remarks = string;
}

/**
* @param i
*/
public void setSeverity(int i) {
severity = i;
}

/**
* @param string
*/
public void setUserID(String string) {
userID = string;
}

/**
* @return
*/
public String getStatus() {
return status;
}

/**
* @param string
*/
public void setStatus(String string) {
status = string;
}

}






This value object needs to be converted in byte arrays so that BerkeleyDB could understand it. So we use our own Custom Binding to do that. AlarmLogBinding.java


import net.smart.umui.logging.model.EmbeddedAlarm;

import com.sleepycat.bind.tuple.TupleBinding;
import com.sleepycat.bind.tuple.TupleInput;
import com.sleepycat.bind.tuple.TupleOutput;

/**
* Description:
*
* This class is used for binding the EmbeddedAlarm class in order to write and read
* complex objects in to BerkeleyDB embedded database.
*
* @author Jared Odulio
*
*/
public class AlarmLogBinding extends TupleBinding {



/* (non-Javadoc)
* @see com.sleepycat.bind.tuple.TupleBinding#entryToObject(com.sleepycat.bind.tuple.TupleInput)
*/
public Object entryToObject(TupleInput ti) {

EmbeddedAlarm alarm = new EmbeddedAlarm();
alarm.setAlarmID(ti.readInt());
alarm.setErrorID(ti.readInt());
alarm.setAgentID(ti.readInt());
alarm.setModuleID(ti.readInt());
alarm.setInstanceID(ti.readString());
alarm.setHostAddress(ti.readString());
alarm.setSeverity(ti.readInt());
alarm.setRemarks(ti.readString());
alarm.setStatus(ti.readString());
alarm.setEntryDate(ti.readLong());
alarm.setUserID(ti.readString());
alarm.setClearDate(ti.readLong());

return alarm;
}

/* (non-Javadoc)
* @see com.sleepycat.bind.tuple.TupleBinding#objectToEntry(java.lang.Object, com.sleepycat.bind.tuple.TupleOutput)
*/
public void objectToEntry(Object obj, TupleOutput to) {

EmbeddedAlarm alarm = (EmbeddedAlarm)obj;

to.writeInt(alarm.getAlarmID());
to.writeInt(alarm.getErrorID());
to.writeInt(alarm.getAgentID());
to.writeInt(alarm.getModuleID());
to.writeString(alarm.getInstanceID());
to.writeString(alarm.getHostAddress());
to.writeInt(alarm.getSeverity());
to.writeString(alarm.getRemarks());
to.writeString(alarm.getStatus());
to.writeLong(alarm.getEntryDate());
to.writeString(alarm.getUserID());
to.writeLong(alarm.getClearDate());


}

}



We also need an enabler class to initialize the Database Environment based on client object's configuration. AlarmLogDB.java



import java.io.File;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;

import net.smart.umui.binding.AlarmLogBinding;
import net.smart.umui.logging.db.exception.AlarmDBException;
import net.smart.umui.logging.model.EmbeddedAlarm;

import org.apache.log4j.Logger;

import com.sleepycat.bind.tuple.TupleBinding;
import com.sleepycat.je.Cursor;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.Environment;
import com.sleepycat.je.LockMode;
import com.sleepycat.je.OperationStatus;

/**
* Description:
*
* This is the BerkeleyDB application class that will use the
* AlarmLogDBEnv class.
*
* @author Jared Odulio
*
*/
public class AlarmLogDB {

private Database alarmLogDB;
private Environment env;
private String dbEnv;
private AlarmLogDBEnv alarmEnv;
private Cursor cursor;
private Logger logger;
private EmbeddedAlarm embedAlarm;
private DatabaseEntry dbEntry;
private DatabaseEntry alarmKey;

private String dbKey;
private TupleBinding alarmBinding;

/**
*
*/
public AlarmLogDB() {

logger = Logger.getLogger(this.getClass());

}

/**
* Initialize the DB for writing or reading functions.
* @param dbEnv
*/
public void setup(String dbEnv){

alarmEnv = new AlarmLogDBEnv(dbEnv);
alarmLogDB = alarmEnv.getAlarmlogDB();
alarmBinding = new AlarmLogBinding();

try {

cursor = alarmLogDB.openCursor(null, null);

logger.info("Cursor has been opened");

} catch (DatabaseException e) {

logger.error(e.toString(), e);

}
}

/**
* Use this method if a File object is to be used instead of the
* String value of the Database Environment path.
*
* @param fileEnv
*/
public void setup(File fileEnv){

alarmEnv = new AlarmLogDBEnv(fileEnv);
alarmLogDB = alarmEnv.getAlarmlogDB();
alarmBinding = new AlarmLogBinding();

try {

cursor = alarmLogDB.openCursor(null, null);

logger.info("Cursor has been opened");

} catch (DatabaseException e) {

logger.error(e.toString(), e);

}

}
/**
* Write Alarm log to embedded db.
*
*/
public void writeAlarm(){

try {

dbEntry = new DatabaseEntry();
alarmKey = new DatabaseEntry(dbKey.getBytes("UTF-8"));
//converts the model to database entry
alarmBinding.objectToEntry(embedAlarm, dbEntry);
cursor.put(alarmKey, dbEntry);

logger.info("Alarm recorded");
cursor.close();
alarmEnv.close();

} catch (UnsupportedEncodingException e) {

logger.error(e.toString(), e);

} catch (DatabaseException de){

logger.error(de.toString(), de);

}

}

/**
* Gets the alarm logs from BerkeleyDB
*
* @return
* @throws AlarmDBException
*/
public List getAlarms() throws AlarmDBException{

List alarmList = new ArrayList();
dbEntry = new DatabaseEntry();
alarmKey = new DatabaseEntry();

try {

while (cursor.getNext(alarmKey, dbEntry, LockMode.DEFAULT) == OperationStatus.SUCCESS){

embedAlarm = (EmbeddedAlarm)alarmBinding.entryToObject(dbEntry);
alarmList.add(embedAlarm);

}

} catch (DatabaseException e) {

throw new AlarmDBException(e);

}

return alarmList;
}

public void deleteAlarms() throws AlarmDBException{

try {
alarmLogDB.truncate(null, false);
} catch (DatabaseException e) {

throw new AlarmDBException(e);
}

}

/**
* Sets the key to be used for the database entry.
*
* @param dbKey
*/
public void setDBEntryKey(String dbKey){
this.dbKey = dbKey;
}

/**
* @param alarm
*/
public void setEmbedAlarm(EmbeddedAlarm alarm) {
embedAlarm = alarm;
}

public void close(){

try {
cursor.close();
} catch (DatabaseException e) {

logger.error(e.toString(), e);
}
alarmEnv.close();

}
}



To test those codes we need to write a couple of classes that we will name TestEmbeddedReader.java and TestEmbeddedWriter.java. For TestEmbeddedWriter.java, heres the code snippet.



import java.io.File;
import java.net.URI;

import org.apache.log4j.Logger;

import net.smart.umui.logging.db.AlarmLogDB;
import net.smart.umui.logging.model.EmbeddedAlarm;

/**
* Description:
*
* Testing writer.
*
* @author Jared Odulio
*
*/
public class TestEmbeddedWriter {

private AlarmLogDB alarmDB;
private EmbeddedAlarm alarmModel;
private File dbenv;
private Logger logger = Logger.getLogger(this.getClass());


/**
*
*/
public TestEmbeddedWriter() {

initialize();

}

private void initialize(){

dbenv = new File("dbEnv");
dbenv.mkdir();

alarmDB = new AlarmLogDB();
alarmDB.setup(dbenv);
alarmModel = new EmbeddedAlarm();
alarmModel.setAgentID(0);
alarmModel.setAlarmID(2);
alarmModel.setClearDate(System.currentTimeMillis());
alarmModel.setEntryDate(System.currentTimeMillis());
alarmModel.setErrorID(1);
alarmModel.setHostAddress("10.121.55.105");
alarmModel.setInstanceID("instanceID");
alarmModel.setModuleID(5);
alarmModel.setRemarks("NO REMARKS");
alarmModel.setSeverity(6);
alarmModel.setStatus("NO STATUS");
alarmModel.setUserID("jared");

alarmDB.setEmbedAlarm(alarmModel);

/*
* If you're going to follow this example think of your own
* unique key. The fully qualified name of your class is definitely
* acceptable.
*/
alarmDB.setDBEntryKey("TestKey3");

}

public void writeToDB(){

alarmDB.writeAlarm();
logger.info("Data has been written");

}

public static void main(String[] args) {

new TestEmbeddedWriter().writeToDB();

}
}



For the TestEmbeddedReader.java.


import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

import org.apache.log4j.Logger;

import net.smart.umui.logging.db.AlarmLogDB;
import net.smart.umui.logging.db.exception.AlarmDBException;
import net.smart.umui.logging.model.EmbeddedAlarm;

/**
* Description:
*
*
* @author Jared Odulio
*
*/
public class TestEmbeddedReader {

private AlarmLogDB alarmLogDB;
private List logs;
private EmbeddedAlarm alarmModel;
private Logger logger = Logger.getLogger(this.getClass());


/**
*
*/
public TestEmbeddedReader() {

initialize();
}

private void initialize(){

File dbenv = new File("dbEnv");
alarmLogDB = new AlarmLogDB();
alarmLogDB.setup(dbenv);
alarmModel = new EmbeddedAlarm();
alarmLogDB.setEmbedAlarm(alarmModel);
alarmLogDB.setDBEntryKey("TestKey");

}

public List readLogs(){

try {

logs = alarmLogDB.getAlarms();
alarmLogDB.deleteAlarms();
alarmLogDB.close();

} catch (AlarmDBException e) {

logger.error(e.toString(), e);

}

return logs;

}

public Logger getLog4j(){
return logger;
}

public static void main(String[] args) {

EmbeddedAlarm alarms = null;
List logs = null;
TestEmbeddedReader reader = new TestEmbeddedReader();
logs = reader.readLogs();
Logger logger = reader.getLog4j();
Iterator it = logs.iterator();
SimpleDateFormat sdf = new SimpleDateFormat("MM/dd/yyyy hh:mm:ss");

while(it.hasNext()){

alarms = (EmbeddedAlarm)it.next();
logger.info("Host Address: " + alarms.getHostAddress());
logger.info("Remarks: " + alarms.getRemarks());
String date = sdf.format(new Date(alarms.getClearDate()));
logger.info("Clear Date: " + date);

}


}

}



Now this is an instant embedded db! all a corporate developer needs to do is download the BerkeleyDB API implementation, modify a few code above that will cause the data to be re-routed from the normal RDBMS/JMS path to the lightweight and fast route of embedded database.

Comments

Popular Posts