package com.ikokoon.serenity.persistence;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.log4j.Logger;
import com.ikokoon.serenity.model.Composite;
import com.ikokoon.serenity.model.Package;
import com.ikokoon.serenity.model.Project;
import com.ikokoon.toolkit.Toolkit;
/**
* This is the in memory database. Several options were explored including DB4O, Neodatis, JPA, SQL, and finally none were performant enough. As well
* as that several hybrids were investigated like including a l1 cache and a l2 cache, but the actual persistence in the case of JPA and SQL was just
* too slow. This class does everything in memory and commits the data finally to the under lying database once the processing is finished.
*
* @author Michael Couck
* @since 11.10.09
* @version 01.00
*/
public final class DataBaseRam extends DataBase {
private Logger logger = Logger.getLogger(this.getClass());
/** The database file for access. */
private String dataBaseFile;
/** The underlying persistence database to the file system. */
private IDataBase dataBase;
/** The closed flag. */
private boolean closed = true;
/** This is the index of packages, classes, methods and lines. */
private transient volatile List<Composite<?, ?>> index = new ArrayList<Composite<?, ?>>();
/**
* Constructor takes the underlying database that will commit the data to the file system, the listener for when the database closes we can
* release the resources and whether to create a new database or open an old one.
*
* @param dataBase
* the underlying database that will actually persist the objects to the file system
* @param dataBaseListener
* the listener to close the database and release resources
*/
DataBaseRam(String dataBaseFile, IDataBase dataBase) {
logger.info("Opening RAM database with " + dataBase + " underneath.");
this.dataBaseFile = dataBaseFile;
this.dataBase = dataBase;
index.clear();
closed = false;
}
/**
* {@inheritDoc}
*/
public synchronized final <E extends Composite<?, ?>> E persist(E composite) {
setIds(composite);
return composite;
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
public synchronized final <E extends Composite<?, ?>> E find(Class<E> klass, Long id) {
return (E) search(klass, index, id);
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
public synchronized final <E extends Composite<?, ?>> E find(Class<E> klass, List<?> parameters) {
Long id = Toolkit.hash(parameters.toArray());
return (E) search(klass, index, id);
}
/**
* {@inheritDoc}
*/
public synchronized final <E extends Composite<?, ?>> List<E> find(Class<E> klass) {
return find(klass, 0, Integer.MAX_VALUE);
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
public <E extends Composite<?, ?>> List<E> find(Class<E> klass, int start, int end) {
List<E> list = new ArrayList<E>();
int counter = 0;
for (Composite<?, ?> composite : index) {
if (klass.isInstance(composite)) {
list.add((E) composite);
if (counter++ >= end) {
break;
}
}
}
if (list == null || list.size() == 0) {
// Try to find some in the underlying database
if (this.dataBase != null) {
list = this.dataBase.find(klass, start, end);
if (list != null && list.size() > 0) {
for (Composite composite : list) {
insert(index, composite);
}
}
}
}
return list;
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
public synchronized final <E extends Composite<?, ?>> E remove(Class<E> klass, Long id) {
Composite<?, ?> composite = find(klass, id);
if (composite != null) {
Composite<?, ?> parent = composite.getParent();
if (parent != null) {
List<?> children = parent.getChildren();
if (children != null) {
children.remove(composite);
}
}
composite.setParent(null);
if (!index.remove(composite)) {
logger.warn("Didn't remove composite with id : " + id + ", because it wasn't in the index.");
}
}
if (this.dataBase != null) {
this.dataBase.remove(klass, id);
}
return (E) composite;
}
/**
* {@inheritDoc}
*/
public synchronized final boolean isClosed() {
if (dataBase != null) {
if (!closed && dataBase.isClosed()) {
closed = true;
}
}
return closed;
}
/**
* {@inheritDoc}
*/
public synchronized final void close() {
if (closed) {
logger.info("User tried to close the database again");
return;
}
logger.info("Comitting and closing the database");
try {
logger.info("Persisting index : " + dataBase);
if (dataBase != null) {
for (Composite<?, ?> composite : index) {
if (Package.class.isInstance(composite) || Project.class.isInstance(composite)) {
logger.debug("Persisting : " + composite);
dataBase.persist(composite);
}
}
dataBase.close();
} else {
logger.warn("Persistence database was null : " + this);
}
index.clear();
IDataBaseEvent dataBaseEvent = new DataBaseEvent(this, IDataBaseEvent.Type.DATABASE_CLOSE);
IDataBase.DataBaseManager.fireDataBaseEvent(dataBaseFile, dataBaseEvent);
} catch (Exception e) {
logger.error("Exception comitting and closing the database", e);
}
closed = true;
}
/**
* This method sets the ids in a graph of objects. The objects need to be stored, perhaps using the top level object in the hierarchy, then the
* database is consulted for it's uid for the object. The uid is set in the field that has the Identifier annotation on the setter method for the
* field.
*
* @param <T>
* the type of object
* @param composite
* the object to set the id for
*/
@SuppressWarnings("unchecked")
synchronized final <T> void setIds(Composite<?, ?> composite) {
if (composite == null) {
return;
}
super.setId(composite);
// Insert the object into the index
insert(index, composite);
logger.debug("Persisted object : " + composite);
if (composite instanceof com.ikokoon.serenity.model.Class) {
String name = ((com.ikokoon.serenity.model.Class) composite).getName();
if (name.indexOf('/') > -1) {
logger.warn("Invalid class name : " + name);
Thread.dumpStack();
}
}
List<Composite<?, ?>> children = (List<Composite<?, ?>>) composite.getChildren();
for (Composite<?, ?> child : children) {
setIds(child);
}
}
/**
* A binary search through the index of composites.
*
* @param <E>
* the type of composite
* @param klass
* the class to search for
* @param index
* the index of composites
* @param id
* the id of the composite to get
* @return the composite from the index, or the underlying database, or null if no such composites exists
*/
@SuppressWarnings("unchecked")
final <E extends Composite<?, ?>> E search(Class klass, List<Composite<?, ?>> index, long id) {
int low = 0;
int high = index.size() - 1;
while (low <= high) {
int mid = (low + high) >>> 1;
Composite<?, ?> composite = index.get(mid);
long midVal = composite.getId();
if (midVal < id) {
low = mid + 1;
} else if (midVal > id) {
high = mid - 1;
} else {
return (E) composite;
}
}
// Look for the object in the underlying database
if (this.dataBase != null) {
Composite composite = this.dataBase.find(klass, id);
if (composite != null) {
insert(index, composite);
}
return (E) composite;
}
return null;
}
/**
* Insert the composite into the index at the correct index.
*
* @param index
* the index of composites
* @param toInsert
* the composite to insert into the index
*/
final void insert(List<Composite<?, ?>> index, Composite<?, ?> toInsert) {
boolean inserted = false;
if (index.size() == 0) {
index.add(toInsert);
inserted = true;
} else {
long key = toInsert.getId();
int low = 0;
int high = index.size();
while (low <= high) {
int mid = (low + high) >>> 1;
if (mid >= index.size()) {
index.add(mid, toInsert);
inserted = true;
break;
}
Composite<?, ?> composite = index.get(mid);
long midVal = composite.getId();
if (midVal < key) {
int next = mid + 1;
if (index.size() > next) {
Composite<?, ?> nextComposite = index.get(next);
long nextVal = nextComposite.getId();
if (nextVal > key) {
index.add(next, toInsert);
inserted = true;
break;
}
}
low = mid + 1;
} else if (midVal > key) {
int previous = mid - 1;
if (previous >= 0) {
Composite<?, ?> previousComposite = index.get(previous);
long previousVal = previousComposite.getId();
if (previousVal < key) {
index.add(mid, toInsert);
inserted = true;
break;
}
} else {
index.add(0, toInsert);
inserted = true;
break;
}
high = mid - 1;
} else {
break;
}
}
}
logger.debug("Inserted : " + toInsert + " - " + inserted);
}
public <E extends Composite<?, ?>> List<E> find(Class<E> klass, Map<String, ?> parameters) {
throw new RuntimeException("Not implempented.");
}
}