package student.web;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.AbstractMap.SimpleEntry;
import student.web.internal.ApplicationSupportStrategy;
import student.web.internal.LocalityService;
import student.web.internal.ObjectFieldExtractor;
import student.web.internal.PersistentStorageManager;
import student.web.internal.ReadOnlySet;
import student.web.internal.converters.AliasService;
/**
*
* This is an abstract map representing all of the general functionality for
* maps that create projections on a particular persistence layer. Note that
* some of these functions return the results from the entire persistence layer
* without respect to the generic type of this map. However, some of the
* operations do respect the generic type of the implementing map. To implement
* this map, the client only needs to implement a constructor that informs the
* super class of the generic type (to prevent type erasure) and the directory
* to treat as the base directory in the persistence store. The directory is non
* absolute and is based on the configured datastore directory.
*
* @author mjw87
*
* @param <T> The type of objects stored in the map.
*/
public abstract class AbstractPersistentMap<T>
implements PersistentMap<T>
{
// /**
// * Separator character for keywords within a persisted object id. This is
// * used for conversion to a remote map.
// */
// protected static final String SEPARATOR = "-.-";
// /**
// * Timestamp of the last time the keyset of the persistence library was
// * retrieved.
// */
// private long idSetTimestamp = 0L;
/**
* The class type this map is projecting onto the persistence store.
*/
protected Class<T> typeAware;
/**
* The class loader to use when looking up classes of objects being loaded.
*/
protected ClassLoader loader;
// /**
// * The latest obtained keyset from the persistence store.
// */
// protected HashSet<String> idSet = new HashSet<String>();
// private ReadOnlyHashSet<String> snapshotIds;
// private long snapshotTimestamp = 0L;
/**
* The cached context map for objects retrieved from the store. It is used
* to reconstitute objects.
*/
private Map<String, PersistentStorageManager.StoredObject> context;
/**
* An extractor object for extracting fields from objects.
*/
private ObjectFieldExtractor extractor = new ObjectFieldExtractor(
LocalityService.getSupportStrategy().getReflectionProvider());
/**
* The instance of the persistence store this Map is projecting on.
*/
private PersistentStorageManager PSM;
private ApplicationSupportStrategy support;
/**
* Get the cache ID for this map.
* @return This map's cache ID.
*/
protected abstract String getCacheId(String uniqueId);
/**
* Create a new map.
* @param directoryName The directory name to use to back this map.
* @param typeAware The type of objects stored in this map.
*/
protected AbstractPersistentMap(String uniqueId, String directoryName, Class<T> typeAware )
{
init( uniqueId, directoryName, typeAware );
loader = typeAware.getClassLoader();
}
/**
* Create a new map.
* @param directoryName The directory name to use to back this map.
* @param typeAware The type of objects stored in this map.
* @param loader The class loader to use when looking up classes of
* loaded objects.
*/
protected AbstractPersistentMap(String uniqueId, String directoryName, Class<T> typeAware, ClassLoader loader)
{
init(uniqueId, directoryName, typeAware);
this.loader = loader;
}
private void init(String uniqueId, String directoryName, Class<T> typeAware )
{
PSM = PersistentStorageManager.getInstance( directoryName );
support = LocalityService.getSupportStrategy();
if ( support.getPersistentCache( getCacheId(uniqueId) ) != null )
{
context = support.getPersistentCache( getCacheId(uniqueId) );
}
else
{
context = support.initPersistentCache( getCacheId(uniqueId));
}
this.typeAware = typeAware;
AliasService.addAliasClass( typeAware );
}
public T remove( Object key )
{
assert key != null : "An key cannot be null";
assert key instanceof String : "Persistence maps only allows for keys of type String";
String objectId = (String)key;
assert objectId.length() > 0 : "An key cannot be an empty string";
T previousValue = getPrevious( (String)key );
removePersistentObject( objectId );
return previousValue;
}
public T put( String key, T value )
{
assert key != null : "An objectId cannot be null";
assert key.length() > 0 : "An objectId cannot be an empty string";
assert !( value instanceof Class ) : "The object to store cannot "
+ "be a class; perhaps you wanted "
+ "to provide an instance of this class instead?";
T previousValue = getPrevious( key );
setPersistentObject( key, value );
return previousValue;
}
private T getPrevious(String key)
{
PersistentStorageManager.StoredObject cached = context.get(key);
T previousValue = null;
if (cached != null)
{
try
{
@SuppressWarnings("unchecked")
T pv = (T)cached.value();
previousValue = pv;
}
catch (ClassCastException e)
{
// Can't cast!
}
}
if (cached == null)
{
PersistentStorageManager.StoredObject previous = PSM.getPersistentObject( key, context,
typeAware.getClassLoader() );
if ( previous != null )
{
// if ( previous.value().getClass().equals( typeAware ) )
if (typeAware.isInstance(previous.value()))
{
@SuppressWarnings("unchecked")
T pv = (T)previous.value();
previousValue = pv;
}
}
}
return previousValue;
}
public void putAll( Map<? extends String, ? extends T> externalMap )
{
for ( Map.Entry<? extends String, ? extends T> entry : externalMap.entrySet() )
{
setPersistentObject( entry.getKey(), entry.getValue() );
}
}
public T get( Object key )
{
assert key != null : "An objectId cannot be null";
assert key instanceof String : "Persistence maps only allows for keys of type String";
String objectId = (String)key;
assert objectId.length() > 0 : "An objectId cannot be an empty string";
T foundObject = getPersistentObject( objectId );
if ( context.get( key ) != null
&& PSM.hasFieldSetChanged( (String)key, context.get( key )
.timestamp() ) )
{
PSM.refreshPersistentObject( (String)key, context,
context.get( key ),
typeAware.getClassLoader() );
}
return foundObject;
}
//
public boolean containsKey( Object key )
{
assert key instanceof String : "Persistence maps only allows for keys of type String";
String objectId = (String)key;
return PSM.hasFieldSetFor( objectId, null );
}
public int size()
{
return PSM.getAllIds().size();
}
public boolean isEmpty()
{
return PSM.getAllIds().isEmpty();
}
//
public boolean containsValue( Object value )
{
for ( String id : PSM.getAllIds() )
{
T persistedObject = getPersistentObject( id );
// Just incase the store shifted under us
if ( persistedObject != null && persistedObject.equals( value ) )
{
return true;
}
}
return false;
}
//
public void clear()
{
context.clear();
for ( String id : PSM.getAllIds() )
{
removePersistentObject( id );
}
PSM.flushCache();
}
public Set<String> keySet()
{
return PSM.getAllIds();
}
public Collection<T> values()
{
Set<T> valueSet = new HashSet<T>();
for ( String key : PSM.getAllIds() )
{
T lookup = getPersistentObject( key );
// Just incase the persistence store moved under us
if ( lookup != null )
valueSet.add( lookup );
}
return valueSet;
}
public Set<Entry<String, T>> entrySet()
{
HashSet<Entry<String, T>> valueSet = new HashSet<Entry<String, T>>();
for ( String id : PSM.getAllIds() )
{
T lookup = getPersistentObject( id );
// Just incase the persistence store moved under us
if ( lookup != null )
{
valueSet.add( new SimpleEntry<String, T>( id, lookup ) );
}
}
return new ReadOnlySet<Entry<String, T>>(valueSet);
}
/**
* Look up the persistent object with the given ID.
* @param objectId The object ID to look up.
* @return The object associated with the given ID.
*/
protected T getPersistentObject( String objectId )
{
T result = null;
PersistentStorageManager.StoredObject latest = context.get( objectId );
if ( latest != null
&& !PSM.hasFieldSetChanged( objectId, latest.timestamp() ) )
{
// if ( latest.value().getClass().equals( typeAware ) )
if( typeAware.isInstance( latest.value() ))
{
result = returnAsType(typeAware, latest.value());
}
}
else
{
if ( loader == null )
{
loader = this.getClass().getClassLoader();
}
latest = PSM.getPersistentObject( objectId, context, loader );
if ( latest != null )
{
context.put( objectId, latest );
result = returnAsType( typeAware, latest.value() );
if ( result != latest.value() )
{
latest.setValue( result );
}
}
}
return result;
}
private <V> V returnAsType( Class<V> t, Object value )
{
if ( value == null )
{
return null;
}
if ( value instanceof TreeMap && !TreeMap.class.isAssignableFrom( t ) )
{
@SuppressWarnings("unchecked")
Map<String, Object> valueAsMap = (Map<String, Object>)value;
value = extractor.fieldMapToObject(t, valueAsMap);
}
if ( t.isAssignableFrom( value.getClass() ) )
{
@SuppressWarnings("unchecked")
V valueAsV = (V)value;
return valueAsV;
}
return null;
}
/**
* Set/store the persistent object for a given ID.
* @param <ObjectType> The type of object to store.
* @param objectId The ID to associate the object with.
* @param object The object to store.
*/
protected <ObjectType> void setPersistentObject(
String objectId,
ObjectType object)
{
try
{
PersistentStorageManager.StoredObject latest = context.get( objectId );
if ( latest != null )
{
latest.setValue( object );
if ( loader == null )
loader = this.getClass().getClassLoader();
PSM.storePersistentObjectChanges( objectId, context, latest, loader );
context.put( objectId, latest );
}
else
{
latest = PSM.storePersistentObject( objectId, context, object );
context.put( objectId, latest );
}
}
catch ( RuntimeException e )
{
PSM.removeFieldSet( objectId );
throw e;
}
}
/**
* Remove a persistent object from the store.
* @param objectId The ID of the object to remove.
*/
protected void removePersistentObject( String objectId )
{
PSM.removeFieldSet( objectId );
}
}