package de.jpaw.bonaparte.refsw.impl; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; 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.apiw.DataWithTrackingW; import de.jpaw.bonaparte.refs.PersistenceException; import de.jpaw.bonaparte.refsw.RefResolver; 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 ConcurrentMap<Long,DataWithTrackingW<DTO, TRACKING>> cache = new ConcurrentHashMap<Long, DataWithTrackingW<DTO, TRACKING>>(1024 * 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 DataWithTrackingW<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(DataWithTrackingW<DTO, TRACKING> dwt, DTO obj) throws PersistenceException; /** Removes an object if it exists. */ protected abstract void uncachedRemove(DataWithTrackingW<DTO, TRACKING> previous); /** Create some object. Returns the object including tracking data, or throws an exception, if the object already exists. */ protected abstract DataWithTrackingW<DTO, TRACKING> uncachedCreate(DTO obj) throws PersistenceException; @Override public final Long getRef(REF refObject) throws PersistenceException { if (refObject == null) return null; Long key = refObject.ret$RefW(); if (key != null) 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 == null) 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 DataWithTrackingW<DTO, TRACKING> getDTONoCacheUpd(Long ref) { // first, try to retrieve a value from the cache, in order to be identity-safe DataWithTrackingW<DTO, TRACKING> value = cache.get(ref); if (value != null) return value; // not here, consult second level (in-memory DB) return getUncached(ref); } @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 == null) return null; DataWithTrackingW<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.longValue(), entityName); } } @Override public final void update(DTO obj) throws PersistenceException { Long key = obj.ret$RefW(); if (key == null) throw new PersistenceException(PersistenceException.NO_PRIMARY_KEY, 0L, entityName); DataWithTrackingW<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 { DataWithTrackingW<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$RefW() == null) throw new PersistenceException(PersistenceException.NO_PRIMARY_KEY, 0L, entityName); DataWithTrackingW<DTO, TRACKING> dwt = uncachedCreate(obj); cache.put(obj.ret$RefW(), dwt); } @Override public TRACKING getTracking(Long ref) throws PersistenceException { DataWithTrackingW<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); } } /** Clears the cache (but not any underlying data storage!). * Used to achieve transaction based caching. */ @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<DataWithTrackingW<DTO, TRACKING>> query(int limit, int offset, SearchFilter filter, List<SortColumn> sortColumns) throws ApplicationException { throw new UnsupportedOperationException(); } }