package de.jpaw.bonaparte.refsp.impl;
import java.util.List;
//import net.openhft.koloboke.collect.map.hash.HashLongObjMap;
//import net.openhft.koloboke.collect.map.hash.HashLongObjMaps;
import de.jpaw.bonaparte.core.ObjectValidationException;
import de.jpaw.bonaparte.pojos.api.AbstractRef;
import de.jpaw.bonaparte.pojos.api.SearchFilter;
import de.jpaw.bonaparte.pojos.api.SortColumn;
import de.jpaw.bonaparte.pojos.api.TrackingBase;
import de.jpaw.bonaparte.pojos.apip.DataWithTrackingP;
import de.jpaw.bonaparte.refs.PersistenceException;
import de.jpaw.bonaparte.refsp.RefResolver;
import de.jpaw.primitivecollections.HashMapPrimitiveLongObject;
import de.jpaw.util.ApplicationException;
import de.jpaw.util.ByteBuilder;
//TODO FIXME: check nochange columns in update method
/**
* An abstract class which implements the common functionality of a RefResolver for off heap key value stores. The topics are:
*
* The first topic is operation of a first level cache (on heap) for data objects. Similar to the JPA entity manager, its task is to provide a unique identity
* for subsequent queries to the same object within a single transaction. It also improves read performance when the data object is of significant size, because
* no repeated deserializations have to be done. No caching is performed on index values, because the index is assumed to be small and the overhead of cache
* operation may be higher than actual updates or lookups itself.
*
* The second aspect is the maintenance of change tracking fields for audit purposes. The tracking fields are available in read/write mode to the application,
* most operations work on the business fields only (DTO). Tracking data is provided upon request, and in that case, a read-only copy is created and handed
* back.
*
* @author Michael Bischoff
*
* @param <REF>
* @param <DTO>
* @param <TRACKING>
*/
public abstract class AbstractRefResolver<REF extends AbstractRef, DTO extends REF, TRACKING extends TrackingBase> implements RefResolver<REF, DTO, TRACKING> {
// private HashLongObjMap<DataWithTrackingP<DTO, TRACKING>> cache = HashLongObjMaps.newMutableMap(1024 * 1024);
private HashMapPrimitiveLongObject<DataWithTrackingP<DTO, TRACKING>> cache = new HashMapPrimitiveLongObject<DataWithTrackingP<DTO, TRACKING>>(1024);
protected ByteBuilder builder;
protected String entityName;
protected int indexHash(int off) {
int hash = 1;
final byte[] buffer = builder.getCurrentBuffer();
while (off < builder.length()) {
hash = 31 * hash + buffer[off++];
}
return hash;
}
/** Look up a primary key by some unique index. */
protected abstract long getUncachedKey(REF refObject) throws PersistenceException;
/** Return an object stored in the DB by its primary key. */
protected abstract DataWithTrackingP<DTO, TRACKING> getUncached(long ref);
/** Update some object fwt to have obj as the data portion. (Update tracking and then update the DB and possibly indexes.) */
protected abstract void uncachedUpdate(DataWithTrackingP<DTO, TRACKING> dwt, DTO obj) throws PersistenceException;
/** Removes an object if it exists. */
protected abstract void uncachedRemove(DataWithTrackingP<DTO, TRACKING> previous);
/** Create some object. Returns the object including tracking data, or throws an exception, if the object already exists. */
protected abstract DataWithTrackingP<DTO, TRACKING> uncachedCreate(DTO obj) throws PersistenceException;
@Override
public final long getRef(REF refObject) throws PersistenceException {
if (refObject == null)
return 0;
long key = refObject.ret$RefP();
if (key > 0)
return key;
// shortcuts not possible, try the local reverse cache
// key = indexCache.get(refObject);
// if (key > 0)
// return key;
// not in cache either, consult second level (in-memory DB)
key = getUncachedKey(refObject);
if (key == 0L)
throw new PersistenceException(PersistenceException.NO_RECORD_FOR_INDEX, 0L, entityName, refObject.ret$PQON(), refObject.toString());
// if (key > 0)
// indexCache.put(refObject, key);
return key;
}
/** return data for a key. Returns null if no record exists. */
protected final DataWithTrackingP<DTO, TRACKING> getDTONoCacheUpd(long ref) {
// first, try to retrieve a value from the cache, in order to be identity-safe
DataWithTrackingP<DTO, TRACKING> value = cache.get(ref);
if (value != null)
return value;
// not here, consult second level (in-memory DB)
value = getUncached(ref);
return value;
}
@Override
public final DTO getDTO(REF refObject) throws PersistenceException {
if (refObject == null)
return null;
return getDTO(getRef(refObject));
}
@Override
public final DTO getDTO(long ref) throws PersistenceException {
if (ref <= 0L)
return null;
DataWithTrackingP<DTO, TRACKING> value = getDTONoCacheUpd(ref);
if (value != null) {
cache.put(ref, value);
return value.getData();
} else {
throw new PersistenceException(PersistenceException.RECORD_DOES_NOT_EXIST, ref, entityName);
}
}
@Override
public final void update(DTO obj) throws PersistenceException {
long key = obj.ret$RefP();
if (key <= 0)
throw new PersistenceException(PersistenceException.NO_PRIMARY_KEY, 0L, entityName);
DataWithTrackingP<DTO, TRACKING> dwt = getDTONoCacheUpd(key);
if (dwt == null)
throw new PersistenceException(PersistenceException.RECORD_DOES_NOT_EXIST, key, entityName);
uncachedUpdate(dwt, obj);
// it's already in the cache, and the umbrella object hasn't changed, so no cache update required
}
@Override
public final void remove(long key) throws PersistenceException {
DataWithTrackingP<DTO, TRACKING> value = getDTONoCacheUpd(key);
if (value != null) {
// must remove it
cache.remove(key);
uncachedRemove(value);
}
}
@Override
public void create(DTO obj) throws PersistenceException {
if (obj.ret$RefP() <= 0)
throw new PersistenceException(PersistenceException.NO_PRIMARY_KEY, 0L, entityName);
DataWithTrackingP<DTO, TRACKING> dwt = uncachedCreate(obj);
cache.put(obj.ret$RefP(), dwt);
}
@Override
public TRACKING getTracking(long ref) throws PersistenceException {
DataWithTrackingP<DTO, TRACKING> dwt = getDTONoCacheUpd(ref);
if (dwt == null)
throw new PersistenceException(PersistenceException.RECORD_DOES_NOT_EXIST, ref, entityName);
try {
return (TRACKING) dwt.getTracking().ret$FrozenClone();
} catch (ObjectValidationException e) {
throw new RuntimeException(e);
}
}
@Override
public final void clear() {
cache.clear();
}
@Override
public List<Long> queryKeys(int limit, int offset, SearchFilter filter, List<SortColumn> sortColumns) throws ApplicationException {
throw new UnsupportedOperationException();
}
@Override
public List<DataWithTrackingP<DTO, TRACKING>> query(int limit, int offset, SearchFilter filter, List<SortColumn> sortColumns)
throws ApplicationException {
throw new UnsupportedOperationException();
}
}