/*******************************************************************************
* Copyright (c) 2014 BestSolution.at and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Tom Schindl <tom.schindl@bestsolution.at> - initial API and implementation
*******************************************************************************/
package at.bestsolution.persistence.java.internal;
import java.lang.ref.WeakReference;
import java.sql.Blob;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.Stack;
import java.util.UUID;
import java.util.Vector;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.log4j.Logger;
import org.eclipse.emf.common.notify.Adapter;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.notify.impl.AdapterImpl;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventAdmin;
import com.google.common.base.Objects;
import at.bestsolution.persistence.BasicFuture;
import at.bestsolution.persistence.Callback;
import at.bestsolution.persistence.Consumer;
import at.bestsolution.persistence.Function;
import at.bestsolution.persistence.MappedQuery;
import at.bestsolution.persistence.ObjectChange;
import at.bestsolution.persistence.ObjectChangePersistParticipant;
import at.bestsolution.persistence.ObjectMapper;
import at.bestsolution.persistence.PersistParticipant;
import at.bestsolution.persistence.PersistanceException;
import at.bestsolution.persistence.Registration;
import at.bestsolution.persistence.Session;
import at.bestsolution.persistence.SessionFactory;
import at.bestsolution.persistence.SessionRunnable;
import at.bestsolution.persistence.PersistParticipant.Type;
import at.bestsolution.persistence.compat.CompatSession;
import at.bestsolution.persistence.compat.CompatTransaction;
import at.bestsolution.persistence.java.AfterTxRunnable;
import at.bestsolution.persistence.java.DatabaseSupport;
import at.bestsolution.persistence.java.JDBCConnectionProvider;
import at.bestsolution.persistence.java.JavaObjectMapper;
import at.bestsolution.persistence.java.JavaSession;
import at.bestsolution.persistence.java.JavaSession.ChangeDescription;
import at.bestsolution.persistence.java.ObjectMapperFactoriesProvider;
import at.bestsolution.persistence.java.ObjectMapperFactory;
import at.bestsolution.persistence.java.ObjectMapperFactory.NamedQuery;
import at.bestsolution.persistence.java.ProxyFactory;
import at.bestsolution.persistence.java.RefreshableObjectMapper;
import at.bestsolution.persistence.java.RelationSQL;
import at.bestsolution.persistence.java.SessionCache;
import at.bestsolution.persistence.java.SessionCacheFactory;
import at.bestsolution.persistence.model.LazyEObject;
import at.bestsolution.persistence.model.PersistedEObject;
public class JavaSessionFactory implements SessionFactory {
JDBCConnectionProvider connectionProvider;
ProxyFactory proxyFactory;
SessionCacheFactory cacheFactory;
Map<String, ObjectMapperFactory<?,?>> factories = new HashMap<String, ObjectMapperFactory<?,?>>();
Map<String, ObjectMapperFactory<?,?>> domainFactories = new HashMap<String, ObjectMapperFactory<?,?>>();
Map<String,DatabaseSupport> databaseSupports = new HashMap<String,DatabaseSupport>();
private static final Logger LOGGER = Logger.getLogger(JavaSessionFactory.class);
EventAdmin eventAdmin;
String factoryId;
private Map<String, List<WeakReference<MapperFuture>>> futureMappers = new HashMap<String, List<WeakReference<MapperFuture>>>();
private List<PersistParticipant> persistParticipants = new Vector<PersistParticipant>(); // Use a vector for thread safety
private ThreadLocal<Session> currentSession = new ThreadLocal<Session>();
private ThreadLocal<AtomicInteger> currentSessionUsage = new ThreadLocal<AtomicInteger>() {
protected AtomicInteger initialValue() { return new AtomicInteger(0); }
};
public void registerConfiguration(JDBCConnectionProvider connectionProvider) {
this.connectionProvider = connectionProvider;
}
public void unregisterConfiguration(JDBCConnectionProvider connectionProvider) {
this.connectionProvider = null;
}
@Override
public Registration registerPersistParticipant(
final PersistParticipant participant) {
persistParticipants.add(participant);
return new Registration() {
@Override
public void dispose() {
persistParticipants.remove(participant);
}
};
}
public void unregisterPersistParticipant(PersistParticipant participant) {
persistParticipants.remove(participant);
}
// Not yet added because the potential leak of WeakReferences
// not very high
private void cleanup() {
synchronized (futureMappers) {
Iterator<List<WeakReference<MapperFuture>>> mapIt = futureMappers.values().iterator();
while( mapIt.hasNext() ) {
List<WeakReference<MapperFuture>> next = mapIt.next();
Iterator<WeakReference<MapperFuture>> it = next.iterator();
while( it.hasNext() ) {
if( it.next().get() == null ) {
it.remove();
}
}
if( next.isEmpty() ) {
mapIt.remove();
}
}
}
}
public void registerFuture(MapperFuture f) {
synchronized (futureMappers) {
List<WeakReference<MapperFuture>> list = futureMappers.get(f.clazz.getName());
if( list == null ) {
list = new ArrayList<WeakReference<MapperFuture>>();
futureMappers.put(f.clazz.getName(), list);
}
list.add(new WeakReference<MapperFuture>(f));
}
}
public void registerMapperFactoriesProvider(ObjectMapperFactoriesProvider provider) {
for( Entry<Class<? extends ObjectMapper<?>>, ObjectMapperFactory<?, ?>> e : provider.getMapperFactories().entrySet() ) {
synchronized (factories) {
factories.put(e.getKey().getName(), e.getValue());
domainFactories.put(e.getValue().getEntityType().getName(), e.getValue());
synchronized (futureMappers) {
List<WeakReference<MapperFuture>> list = futureMappers.remove(e.getKey().getName());
if( list != null ) {
Iterator<WeakReference<MapperFuture>> it = list.iterator();
while( it.hasNext() ) {
MapperFuture future = it.next().get();
if( future != null ) {
future.createMapper();
}
}
}
}
}
}
}
public void unregisterMapperFactoriesProvider(ObjectMapperFactoriesProvider provider) {
synchronized (factories) {
factories.keySet().removeAll(provider.getMapperFactories().keySet());
}
}
@Override
public <M extends ObjectMapper<?>> boolean isMapperAvailable(
Class<M> mapper) {
synchronized (factories) {
return factories.containsKey(mapper.getName());
}
}
@Override
public <T> boolean isMapperAvailableForType(Class<T> mapper) {
synchronized (factories) {
return factories.containsKey(mapper.getName()+"Mapper");
}
}
public void registerProxyFactory(ProxyFactory proxyFactory) {
this.proxyFactory = proxyFactory;
}
public void unregisterProxyFactory(ProxyFactory proxyFactory) {
this.proxyFactory = null;
}
public void registerSessionCacheFactory(SessionCacheFactory cacheFactory) {
this.cacheFactory = cacheFactory;
}
public void unregisterSessionCacheFactory(SessionCacheFactory cacheFactory) {
this.cacheFactory = null;
}
public void registerDatabaseSupport(DatabaseSupport databaseSupport) {
databaseSupports.put(databaseSupport.getDatabaseType(), databaseSupport);
}
public void unregisterDatabaseSupport(DatabaseSupport databaseSupport) {
databaseSupports.remove(databaseSupport.getDatabaseType());
}
public void registerEventAdmin(EventAdmin eventAdmin) {
this.eventAdmin = eventAdmin;
}
public void unregisterEventAdmin(EventAdmin eventAdmin) {
this.eventAdmin = null;
}
@Override
public Session createSession() {
return new JavaSessionImpl(JDBCConnectionProvider.DEFAULT_CONFIGURATION,cacheFactory.createCache());
}
@Override
public Session createSession(String configurationId) {
return new JavaSessionImpl(configurationId, cacheFactory.createCache());
}
@Override
public Future<Session> createFutureSession(
Class<ObjectMapper<?>>... dependentMappers) {
List<MapperFuture> list = new ArrayList<MapperFuture>();
final Session session = createSession();
for( Class<ObjectMapper<?>> m : dependentMappers ) {
if( ! isMapperAvailable(m) ) {
list.add(new MapperFuture(session, m));
}
}
return new SessionFuture(session, list);
}
static class SessionFuture extends BasicFuture<Session> {
private final Session session;
private final List<MapperFuture> futureList;
public SessionFuture(Session session, List<MapperFuture> futureList) {
this.session = session;
this.futureList = futureList;
}
@Override
public Session get() throws InterruptedException, ExecutionException {
if( futureList.isEmpty() ) {
return session;
} else {
for( MapperFuture m : futureList ) {
m.get();
}
}
return session;
}
@Override
public Session get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException,
TimeoutException {
long nano = unit.toNanos(timeout);
for( MapperFuture m : futureList ) {
long begin = System.nanoTime();
m.get(nano, TimeUnit.NANOSECONDS);
nano = nano - (System.nanoTime() - begin);
if( nano <= 0 ) {
throw new TimeoutException();
}
}
complete(session);
return super.get();
}
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
for( MapperFuture m : futureList ) {
m.cancel(mayInterruptIfRunning);
}
return super.cancel(mayInterruptIfRunning);
}
}
@Override
public <R> R runWithSession(SessionRunnable<R> runnable) {
Session session = currentSession.get();
if (session == null) {
// create
session = createSession();
currentSession.set(session);
}
// increment
currentSessionUsage.get().incrementAndGet();
try {
return runnable.execute(session);
}
finally {
// decrement
int val = currentSessionUsage.get().decrementAndGet();
// dispose
if (val == 0) {
Session s = currentSession.get();
s.close();
currentSession.set(null);
}
}
}
@Override
public String getFactoryId() {
if( factoryId == null ) {
factoryId = UUID.randomUUID().toString();
}
return factoryId;
}
static final boolean isNewObject(Object idValue) {
return idValue == null || (idValue instanceof Number && ((Number)idValue).longValue() == 0);
}
@Override
public Blob createBlob() {
return new LocalBlob();
}
static class MapperFuture extends BasicFuture<ObjectMapper<?>> {
private final Session session;
private final Class<ObjectMapper<?>> clazz;
public MapperFuture(Session session, Class<ObjectMapper<?>> clazz) {
this.session = session;
this.clazz = clazz;
}
public void createMapper() {
try {
complete(session.createMapper(clazz));
} catch( Throwable t) {
throwExecutionException(t);
}
}
}
class JavaSessionImpl implements JavaSession, CompatSession {
private final String configurationId;
private String id = UUID.randomUUID().toString();
private Map<Class<?>, ObjectMapper<?>> mapperInstances = new HashMap<Class<?>, ObjectMapper<?>>();
private Stack<Connection> transactionConnectionQueue;
private Set<LazyBlob> managedBlobs = new HashSet<LazyBlob>();
private Stack<Transaction> transactionQueue;
private SessionCache sessionCache;
private int changeTrackingCount = 0;
private Map<Transaction, List<RelationSQL>> relationSQLStorage = new HashMap<Session.Transaction, List<RelationSQL>>();
private Map<Transaction, Set<AfterTxRunnable>> afterTransaction = new HashMap<Session.Transaction, Set<AfterTxRunnable>>();
private Map<Transaction, Map<Object, Object>> transactionPrimaryKeyCache = new HashMap<Session.Transaction, Map<Object, Object>>();
private Map<Transaction, Map<Object, Long>> transactionVersionCache = new HashMap<Session.Transaction, Map<Object,Long>>();
private Map<Transaction, Map<Object,Map<EAttribute,Object>>> transactionData = new HashMap<Session.Transaction, Map<Object,Map<EAttribute,Object>>>();
private List<PersistParticipant> participants = new ArrayList<PersistParticipant>();
private Map<Transaction, Set<String>> insertedObjects = new HashMap<Session.Transaction, Set<String>>();
private Map<Transaction, Set<String>> updatedObjects = new HashMap<Session.Transaction, Set<String>>();
private Map<Transaction, Set<String>> deletedObjects = new HashMap<Session.Transaction, Set<String>>();
private Map<Transaction, Set<String>> deletedManyObjects = new HashMap<Session.Transaction, Set<String>>();
private boolean closed = false;
private Adapter objectAdapter = new AdapterImpl() {
@Override
public void notifyChanged(Notification msg) {
handleNotify(msg);
}
};
private Map<EObject, List<FeatureChange>> changeStorage = new HashMap<EObject, List<FeatureChange>>();
public JavaSessionImpl(String configurationId, SessionCache sessionCache) {
this.configurationId = configurationId;
this.sessionCache = sessionCache;
}
@Override
public String getConfigurationId() {
checkValid();
// TODO Auto-generated method stub
return null;
}
@Override
public Date getServerTime() {
checkValid();
Connection connection = checkoutConnection();
try {
return getDatabaseSupport().getServerTime(connection);
} finally {
returnConnection(connection);
}
}
@Override
public void refresh(Object o, RefreshType type) {
checkValid();
final ObjectMapper<EObject> m = createMapperForObject((EObject)o);
if( m != null ) {
if( m instanceof RefreshableObjectMapper ) {
((RefreshableObjectMapper<EObject>)m).refresh((EObject)o, type);
} else {
throw new IllegalArgumentException("Object " + o + " is not refreshable");
}
}
}
@Override
public <O> O get(Class<O> clazz, Object id) {
checkValid();
ObjectMapperFactory<?, ?> factory = domainFactories.get(clazz.getName());
if( factory != null ) {
NamedQuery<O> q = (NamedQuery<O>) factory.createNamedQuery(this, "selectById");
if( q != null ) {
return q.queryForOne(id);
}
throw new IllegalArgumentException("No 'selectById' query available for '"+clazz+"'");
}
throw new IllegalArgumentException("No mapper for '"+clazz+"' is available");
}
@Override
public String getId() {
checkValid();
return id;
}
@Override
public DatabaseSupport getDatabaseSupport() {
checkValid();
return databaseSupports.get(getDatabaseType());
}
@Override
public Registration registerPersistParticipant(
final PersistParticipant participant) {
checkValid();
participants.add(participant);
return new Registration() {
@Override
public void dispose() {
participants.remove(participant);
}
};
}
@Override
public <M extends ObjectMapper<?>> Future<M> createMapperFuture(
Class<M> mapper) {
checkValid();
synchronized (factories) {
if( isMapperAvailable(mapper) ) {
MapperFuture f = new MapperFuture(this, (Class<ObjectMapper<?>>) mapper);
f.createMapper();
return (Future<M>) f;
} else {
new MapperFuture(this, (Class<ObjectMapper<?>>) mapper);
}
}
return null;
}
@Override
public <M extends ObjectMapper<?>> boolean isMapperAvailable(
Class<M> mapper) {
checkValid();
return JavaSessionFactory.this.isMapperAvailable(mapper);
}
@Override
public <T> boolean isMapperAvailableForType(Class<T> type) {
checkValid();
return JavaSessionFactory.this.isMapperAvailableForType(type);
}
@Override
@SuppressWarnings("unchecked")
public <M extends ObjectMapper<?>> M createMapper(Class<M> mapper) {
checkValid();
M m = (M) mapperInstances.get(mapper);
if( m == null ) {
ObjectMapperFactory<?, ?> factory;
synchronized (factories) {
factory = factories.get(mapper.getName());
}
if (factory == null) {
throw new RuntimeException("no factory for " + mapper + " found! Double check your bundle.emap");
}
m = (M) factory.createMapper(this);
mapperInstances.put(mapper, m);
}
return m;
}
@SuppressWarnings("unchecked")
@Override
public <T> ObjectMapper<T> createMapperForType(Class<T> type) {
checkValid();
ObjectMapperFactory<?, ?> factory;
synchronized (domainFactories) {
factory = domainFactories.get(type.getName());
}
if (factory == null) {
throw new RuntimeException("no factory for type " + type + " found! Double check your bundle.emap");
}
return (ObjectMapper<T>) factory.createMapper(this);
}
@SuppressWarnings("unchecked")
@Override
public <O> List<O> queryForList(String fqnMapper, String queryName,
Object... parameters) {
checkValid();
ObjectMapperFactory<?, ?> factory = factories.get(fqnMapper);
if( factory != null ) {
NamedQuery<O> q = (NamedQuery<O>) factory.createNamedQuery(this, queryName);
String[] params = q.getParameterNames();
if( params.length != parameters.length ) {
throw new IllegalArgumentException("The query does not accept parameters. Use Session#mappedQuery to build a dynamic query!");
}
return q.queryForList(parameters);
}
throw new IllegalArgumentException("The mapper '"+fqnMapper+"' is not known.");
}
@SuppressWarnings("unchecked")
@Override
public <O> List<O> queryForList(String fqnMapper, String queryName,
Map<String, Object> parameterMap) {
checkValid();
ObjectMapperFactory<?, ?> factory = factories.get(fqnMapper);
if( factory != null ) {
NamedQuery<O> q = (NamedQuery<O>) factory.createNamedQuery(this, queryName);
String[] params = q.getParameterNames();
if( params.length == 0 && parameterMap.size() > 0 ) {
throw new IllegalArgumentException("The query does not accept parameters. Use Session#mappedQuery to build a dynamic query!");
}
Object[] objs = new Object[params.length];
for( int i = 0; i < params.length; i++ ) {
objs[i] = parameterMap.get(params[i]);
}
return q.queryForList(objs);
}
throw new IllegalArgumentException("The mapper '"+fqnMapper+"' is not known.");
}
@SuppressWarnings("unchecked")
@Override
public <O> O queryForOne(String fqnMapper, String queryName,
Object... parameters) {
checkValid();
NamedQuery<O> q = (NamedQuery<O>) factories.get(fqnMapper).createNamedQuery(this, queryName);
String[] params = q.getParameterNames();
if( params.length != parameters.length ) {
throw new IllegalArgumentException("The query does not accept parameters. Use Session#mappedQuery to build a dynamic query!");
}
return q.queryForOne(parameters);
}
@SuppressWarnings("unchecked")
@Override
public <O> O queryForOne(String fqnMapper, String queryName,
Map<String, Object> parameterMap) {
checkValid();
ObjectMapperFactory<?, ?> factory = factories.get(fqnMapper);
if( factory != null ) {
NamedQuery<O> q = (NamedQuery<O>) factory.createNamedQuery(this, queryName);
String[] params = q.getParameterNames();
if( params.length == 0 && parameterMap.size() > 0 ) {
throw new IllegalArgumentException("The query does not accept parameters. Use Session#mappedQuery to build a dynamic query!");
}
Object[] objs = new Object[params.length];
for( int i = 0; i < params.length; i++ ) {
objs[i] = parameterMap.get(params[i]);
}
return q.queryForOne(objs);
}
throw new IllegalArgumentException("The mapper '"+fqnMapper+"' is not known.");
}
@SuppressWarnings("unchecked")
@Override
public <O> MappedQuery<O> mappedQuery(String fqnMapper, String queryName) {
checkValid();
return (MappedQuery<O>) factories.get(fqnMapper).mappedQuery(this, queryName);
}
@Override
public <O> void preExecuteInsert(ObjectMapper<O> mapper, O object) {
checkValid();
List<PersistParticipant> participants = getAllParticipants();
if( ! participants.isEmpty() ) {
for( PersistParticipant p : participants ) {
Map<String, Object> participate = p.participate(this, at.bestsolution.persistence.PersistParticipant.Type.INSERT, (EObject) object);
if( participate != null ) {
for( Entry<String, Object> e : participate.entrySet() ) {
EStructuralFeature attribute = ((EObject)object).eClass().getEStructuralFeature(e.getKey());
setTransactionAttribute(object, (EAttribute)attribute, e.getValue());
}
}
}
}
Transaction transaction = getTransaction();
Set<String> set = insertedObjects.get(transaction);
if( set == null ) {
set = new HashSet<String>();
insertedObjects.put(transaction, set);
}
set.add(toString(mapper,object));
}
@Override
public <O> void preExecuteUpdate(ObjectMapper<O> mapper, O object) {
checkValid();
List<PersistParticipant> participants = getAllParticipants();
if( ! participants.isEmpty() ) {
for( PersistParticipant p : participants ) {
Map<String, Object> participate = p.participate(this, at.bestsolution.persistence.PersistParticipant.Type.UPDATE, (EObject) object);
if( participate != null ) {
for( Entry<String, Object> e : participate.entrySet() ) {
EStructuralFeature attribute = ((EObject)object).eClass().getEStructuralFeature(e.getKey());
setTransactionAttribute(object, (EAttribute)attribute, e.getValue());
}
}
}
}
Transaction transaction = getTransaction();
Set<String> set = updatedObjects.get(transaction);
if( set == null ) {
set = new HashSet<String>();
updatedObjects.put(transaction, set);
}
set.add(toString(mapper,object));
}
@Override
public <O> void preExecuteDelete(ObjectMapper<O> mapper, O object) {
checkValid();
Transaction transaction = getTransaction();
Set<String> set = deletedObjects.get(transaction);
if( set == null ) {
set = new HashSet<String>();
deletedObjects.put(transaction, set);
}
set.add(toString(mapper,object));
}
@Override
public <P> void preExecuteDeleteById(EClass eClass, Collection<P> keys) {
checkValid();
Transaction transaction = getTransaction();
Set<String> set = deletedObjects.get(transaction);
if( set == null ) {
set = new HashSet<String>();
deletedObjects.put(transaction, set);
}
for( P k : keys ) {
set.add(eClass.getName()+"#"+k);
}
}
@Override
public void preExecuteDeleteMany(EClass eClass) {
checkValid();
Transaction transaction = getTransaction();
Set<String> set = deletedManyObjects.get(transaction);
if( set == null ) {
set = new HashSet<String>();
deletedManyObjects.put(transaction, set);
}
set.add(eClass.getName());
}
private <O> String toString(ObjectMapper<O> mapper, O object) {
return ((EObject)object).eClass().getName()+"#"+getPrimaryKey(mapper, object);
}
@Override
public <O, P> void registerPrimaryKey(O object, P key) {
checkValid();
final boolean isDebug = LOGGER.isDebugEnabled();
if( isDebug ) {
LOGGER.debug("registerPrimaryKey " + object + " => " + key);
}
final Transaction transaction = getTransaction();
if( transaction == null ) {
throw new PersistanceException("Unable to schedule after tx callback without a transaction");
}
Map<Object, Object> map = transactionPrimaryKeyCache.get(transaction);
if (map == null) {
map = new HashMap<Object, Object>();
transactionPrimaryKeyCache.put(transaction, map);
}
map.put(object, key);
}
@SuppressWarnings("unchecked")
private <O,P> P getPrimaryKeyFromTransactionCache(O object) {
Transaction transaction = getTransaction();
if (transaction == null) {
return null;
}
final Map<Object, Object> map = transactionPrimaryKeyCache.get(transaction);
if (map != null) {
return (P) map.get(object);
}
return null;
}
@SuppressWarnings("unchecked")
private <O> long getVersionFromTransactionCache(O object) {
Transaction transaction = getTransaction();
if (transaction == null) {
return -1;
}
final Map<Object, Long> map = transactionVersionCache.get(transaction);
if (map != null) {
Long o = map.get(object);
return o == null ? -1 : o.longValue();
}
return -1;
}
@Override
public <O> void updateVersion(O object, long version) {
checkValid();
Transaction transaction = getTransaction();
if (transaction == null) {
throw new IllegalStateException("Can not be called outside a transaction");
}
Map<Object, Long> map = transactionVersionCache.get(transaction);
if( map == null ) {
map = new HashMap<Object, Long>();
transactionVersionCache.put(transaction, map);
}
map.put(object, version);
}
@Override
public <O,P> P getPrimaryKey(ObjectMapper<O> mapper, O object) {
checkValid();
final boolean isDebug = LOGGER.isDebugEnabled();
if( isDebug ) {
LOGGER.debug("getPrimaryKey " + object);
}
P key = getPrimaryKeyFromTransactionCache(object);
if (key != null) {
if( isDebug ) {
LOGGER.debug(" found key in tx cache => " + key);
}
return key;
}
key = mapper.getPrimaryKeyValue(object);
if( isDebug ) {
LOGGER.debug(" got key from object => " + key);
}
return key;
}
public <O> long getVersion(ObjectMapper<O> mapper, O object) {
checkValid();
final boolean isDebug = LOGGER.isDebugEnabled();
if( isDebug ) {
LOGGER.debug("getVersion " + object);
}
long version = getVersionFromTransactionCache(object);
if (version != -1) {
if( isDebug ) {
LOGGER.debug(" found version in tx cache => " + version);
}
return version;
}
version = getCache().getVersion((EObject)object, mapper.getPrimaryKeyValue(object));
if( isDebug ) {
LOGGER.debug(" got version from object => " + version);
}
return version;
}
@SuppressWarnings("unchecked")
@Override
public <O, P> P getTransactionAttribute(O object, EAttribute attribute) {
checkValid();
final Transaction transaction = getTransaction();
if( transaction == null ) {
return (P) ((EObject)object).eGet(attribute);
}
Map<Object, Map<EAttribute, Object>> map = transactionData.get(transaction);
if( map == null ) {
return (P) ((EObject)object).eGet(attribute);
}
Map<EAttribute, Object> data = map.get(object);
if( data == null || ! data.containsKey(attribute) ) {
return (P) ((EObject)object).eGet(attribute);
}
return (P) data.get(attribute);
}
private <O, P> void setTransactionAttribute(O object,
EAttribute attribute, P value) {
final Transaction transaction = getTransaction();
if( transaction == null ) {
throw new PersistanceException("Unable store the information without a transaction");
}
Map<Object, Map<EAttribute, Object>> map = transactionData.get(transaction);
if( map == null ) {
map = new HashMap<Object, Map<EAttribute,Object>>();
transactionData.put(transaction, map);
}
Map<EAttribute, Object> data = map.get(object);
if( data == null ) {
data = new HashMap<EAttribute, Object>();
map.put(object, data);
}
data.put(attribute, value);
}
@Override
public boolean isTransaction() {
checkValid();
return transactionConnectionQueue != null;
}
@Override
public CompatTransaction beginTransaction() {
checkValid();
final boolean isDebug = LOGGER.isDebugEnabled();
final AtomicBoolean commit = new AtomicBoolean();
final String transactionId = UUID.randomUUID().toString();
final Transaction transaction = new Transaction() {
@Override
public boolean execute() {
return commit.get();
}
};
final Connection connection = startTransaction(isDebug, transactionId, transaction);
return new CompatTransaction() {
@Override
public void rollback() {
run(false);
}
@Override
public void commit() {
run(true);
}
private void run(boolean doCommit) {
commit.set(doCommit);
try {
executeTransaction(isDebug, connection, transaction);
} finally {
postExecuteTransaction(isDebug, connection, transactionId, transaction);
}
}
};
}
@Override
public <A> A adaptTo(Class<A> clazz) {
checkValid();
if( clazz == CompatSession.class ) {
return (A) this;
}
return null;
}
@Override
public <R> R jdbcRun(boolean modify, Function<Connection, R> function) {
checkValid();
if( ! modify || getTransaction() != null ) {
Connection c = checkoutConnection();
try {
return function.execute(c);
} finally {
returnConnection(c);
}
} else {
throw new IllegalStateException("You can only execute jdbc-runnables inside a transaction");
}
}
public Connection startTransaction(boolean isDebug, String transactionId, Transaction transaction) {
checkValid();
if( isDebug ) {
LOGGER.debug("Started transaction '"+transactionId+"'");
}
Connection connection = connectionProvider.checkoutConnection(configurationId);
try {
connection.setAutoCommit(false);
} catch (SQLException e2) {
LOGGER.error("Failed to turn off auto commit", e2);
throw new PersistanceException(e2);
}
if( eventAdmin != null ) {
Map<String, Object> properties = new HashMap<String, Object>();
properties.put(DATA_SESSION_ID, getId());
eventAdmin.sendEvent(new Event(TOPIC_TRANSACTION_START, properties));
}
if( transactionConnectionQueue == null ) {
transactionConnectionQueue = new Stack<Connection>();
}
if( transactionQueue == null ) {
transactionQueue = new Stack<Session.Transaction>();
}
transactionQueue.add(transaction);
transactionConnectionQueue.add(connection);
return connection;
}
private void executeTransaction(boolean isDebug, Connection connection, Transaction transaction) {
if( isDebug ) {
LOGGER.debug("Executing transaction");
}
try {
if( transaction.execute() ) {
if( isDebug ) {
LOGGER.debug("Successfully executed the transaction");
}
try {
List<RelationSQL> list = relationSQLStorage.get(transaction);
if( list != null ) {
if( isDebug ) {
LOGGER.debug("Executing relation sqls: " + list.size());
}
RelationSQL[] tmpList = list.toArray(new RelationSQL[0]);
for( RelationSQL s : tmpList ) {
s.execute();
}
if( isDebug ) {
LOGGER.debug("Finished relational sqls");
}
}
connection.commit();
flushTransactionData(transaction);
// exectue after-tx callbacks
if( isDebug ) {
LOGGER.debug("Executing After-Tx callbacks");
}
int counter = 0;
final Set<AfterTxRunnable> set = afterTransaction.get(transaction);
if (set != null) {
for (AfterTxRunnable r : set) {
r.runAfterTx(this);
counter++;
}
}
if( isDebug ) {
LOGGER.debug("Done executing " + counter + " After-Tx callbacks");
}
if( eventAdmin != null ) {
Map<String, Object> properties = new HashMap<String, Object>();
properties.put(DATA_SESSION_ID, getId());
properties.put(DATA_STATUS, VALUE_COMMIT);
properties.put(DATA_INSERTED_OBJECTS, notNull(insertedObjects.get(transaction)));
properties.put(DATA_UPDATED_OBJECTS, notNull(updatedObjects.get(transaction)));
properties.put(DATA_DELETED_OBJECTS, notNull(deletedObjects.get(transaction)));
properties.put(DATA_DELETED_MANY, notNull(deletedManyObjects.get(transaction)));
eventAdmin.sendEvent(new Event(TOPIC_TRANSACTION_END, properties));
}
} catch( SQLException e ) {
LOGGER.error("Failed to commit transaction",e);
throw new PersistanceException(e);
}
} else {
try {
connection.rollback();
if( eventAdmin != null ) {
Map<String, Object> properties = new HashMap<String, Object>();
properties.put(DATA_SESSION_ID, getId());
properties.put(DATA_STATUS, VALUE_ROLLBACK);
eventAdmin.sendEvent(new Event(TOPIC_TRANSACTION_END, properties));
}
} catch( SQLException e ) {
LOGGER.error("Failed to rollback transaction",e);
throw new PersistanceException(e);
}
}
} catch(Throwable e) {
LOGGER.error("Error while executing transactional code", e);
try {
connection.rollback();
if( eventAdmin != null ) {
Map<String, Object> properties = new HashMap<String, Object>();
properties.put(DATA_SESSION_ID, getId());
properties.put(DATA_STATUS, VALUE_ROLLBACK);
eventAdmin.sendEvent(new Event(TOPIC_TRANSACTION_END, properties));
}
} catch (SQLException e1) {
LOGGER.error("Failed to rollback transaction. Swallowing and rethrowing original connection.",e1);
}
throw e instanceof RuntimeException ? (RuntimeException)e : new RuntimeException(e);
}
}
private void postExecuteTransaction(boolean isDebug, Connection connection, String transactionId, Transaction transaction) {
// clear after-tx
afterTransaction.remove(transaction);
// clear relationSQL
relationSQLStorage.remove(transaction);
// clear transaction primary key cache
transactionPrimaryKeyCache.remove(transaction);
transactionData.remove(transaction);
insertedObjects.remove(transaction);
updatedObjects.remove(transaction);
deletedObjects.remove(transaction);
deletedManyObjects.remove(transaction);
try {
connection.setAutoCommit(true);
} catch (SQLException e) {
LOGGER.error("Failed to set back auto commit", e);
throw new PersistanceException(e);
}
connectionProvider.returnConnection(configurationId, transactionConnectionQueue.pop());
if( transactionConnectionQueue.isEmpty() ) {
transactionConnectionQueue = null;
}
transactionQueue.pop();
if( transactionQueue.isEmpty() ) {
transactionQueue = null;
}
if( isDebug ) {
LOGGER.debug("Finished transaction '"+transactionId+"'");
}
}
@Override
public void runInTransaction(Transaction transaction) {
checkValid();
boolean isDebug = LOGGER.isDebugEnabled();
String transactionId = UUID.randomUUID().toString();
Connection connection = startTransaction(isDebug, transactionId, transaction);
try {
executeTransaction(isDebug, connection, transaction);
} finally {
postExecuteTransaction(isDebug, connection, transactionId, transaction);
}
}
@Override
public void runInTransaction(final TransactionTask task) {
checkValid();
boolean isDebug = LOGGER.isDebugEnabled();
String transactionId = UUID.randomUUID().toString();
Transaction t = new Transaction() {
@Override
public boolean execute() {
return task.run(JavaSessionImpl.this);
}
};
Connection connection = startTransaction(isDebug, transactionId, t);
try {
executeTransaction(isDebug, connection, t);
} finally {
postExecuteTransaction(isDebug, connection, transactionId, t);
}
}
private Set<String> notNull(Set<String> set) {
if( set == null ) {
return Collections.emptySet();
} else {
return Collections.unmodifiableSet(set);
}
}
private void flushTransactionData(Transaction transaction) {
Map<Object, Map<EAttribute, Object>> map = transactionData.get(transaction);
if( map == null ) {
return;
}
for( Entry<Object, Map<EAttribute,Object>> e : map.entrySet() ) {
EObject eo = (EObject) e.getKey();
for( Entry<EAttribute, Object> e2 : e.getValue().entrySet() ) {
try {
eo.eSet(e2.getKey(), e2.getValue());
} catch(Throwable t) {
LOGGER.error("Unable to sync transaction value into the model", t);
}
}
}
}
@Override
public Transaction getTransaction() {
checkValid();
return transactionQueue == null ? null : transactionQueue.peek();
}
@Override
public void scheduleAfterTransaction(AfterTxRunnable r) {
checkValid();
final boolean isDebug = LOGGER.isDebugEnabled();
if( isDebug ) {
LOGGER.debug("schedule After-Tx callback: " + r);
}
final Transaction transaction = getTransaction();
if( transaction == null ) {
throw new PersistanceException("Unable to schedule after tx callback without a transaction");
}
Set<AfterTxRunnable> set = afterTransaction.get(transaction);
if (set == null) {
// we use a linked hash set to avoid duplicates while preserving the order
set = new LinkedHashSet<AfterTxRunnable>();
afterTransaction.put(transaction, set);
}
if (!set.add(r)) {
if( isDebug ) {
LOGGER.debug("! After-Tx callback was not scheduled -> duplicate!");
}
}
}
@Override
public void scheduleRelationSQL(RelationSQL sql) {
checkValid();
boolean isDebug = LOGGER.isDebugEnabled();
if( isDebug ) {
LOGGER.debug("Trying to register: " + sql);
}
Transaction transaction = getTransaction();
if( transaction == null ) {
throw new PersistanceException("Unable to schedule relation sql without a transaction");
}
List<RelationSQL> list = relationSQLStorage.get(transaction);
if( list == null ) {
list = new ArrayList<RelationSQL>();
relationSQLStorage.put(transaction, list);
}
// Check that we are not adding duplicate inserts for relations
for( RelationSQL r : list ) {
if( r.getAction() == sql.getAction() && r.getTableName().equals(sql.getTableName()) ) {
if( r.getSelf() == sql.getSelf() &&
r.getOpposite() == sql.getOpposite() ) {
if( isDebug ) {
LOGGER.debug("Skipping registration because same is already registered");
}
return;
} else if( r.getSelf() == sql.getOpposite() &&
r.getOpposite() == sql.getSelf() ) {
if( isDebug ) {
LOGGER.debug("Skipping registration because opposite is already registered");
}
return;
}
}
}
if( isDebug ) {
LOGGER.debug("Register RelationSQL");
}
list.add(sql);
}
@Override
public boolean isClosed() {
return closed;
}
private void checkValid() {
if( isClosed() ) {
LOGGER.error("Session is already closed. Future version will throw an exception",new Exception());
// throw new IllegalStateException("Session is already closed");
}
}
@Override
public void close() {
checkValid();
try {
// free all session blobs
for (LazyBlob blob : managedBlobs) {
try {
blob.free();
}
catch (SQLException e) {}
}
mapperInstances.clear();
sessionCache.release();
for( EObject eo : changeStorage.keySet() ) {
eo.eAdapters().remove(objectAdapter);
}
changeStorage.clear();
transactionPrimaryKeyCache.clear();
transactionData.clear();
if( transactionConnectionQueue != null ) {
for( Connection c : transactionConnectionQueue ) {
try {
c.rollback();
} catch (SQLException e) {
LOGGER.error("Unable to rollback connection", e);
}
connectionProvider.returnConnection(configurationId,c);
}
transactionConnectionQueue = null;
}
participants.clear();
} finally {
closed = true;
}
}
private List<PersistParticipant> getAllParticipants() {
List<PersistParticipant> l = new ArrayList<PersistParticipant>(participants.size() + persistParticipants.size());
l.addAll(this.participants);
l.addAll(JavaSessionFactory.this.persistParticipants);
return Collections.unmodifiableList(l);
}
@Override
public void clear() {
checkValid();
sessionCache.clear();
changeStorage.clear();
}
@Override
public Connection checkoutConnection() {
checkValid();
if( transactionConnectionQueue != null ) {
return transactionConnectionQueue.peek();
}
return connectionProvider.checkoutConnection(configurationId);
}
@Override
public void returnConnection(Connection connection) {
checkValid();
if( transactionConnectionQueue != null ) {
return;
}
connectionProvider.returnConnection(configurationId,connection);
}
@Override
public String getDatabaseType() {
checkValid();
return connectionProvider.getDatabaseType(configurationId);
}
@Override
public SessionCache getCache() {
checkValid();
return sessionCache;
}
@Override
public ProxyFactory getProxyFactory() {
checkValid();
return proxyFactory;
}
@Override
public Object convertType(Class<?> targetType, Object value) {
checkValid();
if( targetType == Boolean.class ) {
if( value instanceof Number ) {
return ((Number)value).intValue() != 0;
}
} else if( targetType.isEnum() ) {
if( value != null ) {
String v = value.toString().trim();
Class<Enum<?>> c = (Class<Enum<?>>) targetType;
for( Enum<?> e : c.getEnumConstants() ) {
if( e.name().equals(v)) {
return e;
}
}
throw new IllegalArgumentException("Could not map '"+value+"' to Enum '"+targetType+"'");
} else {
return null;
}
} else if( value instanceof Enum<?> ) {
return ((Enum<?>)value).name();
} else if( targetType == Integer.class ) {
if( value != Integer.class && value instanceof Number ) {
return ((Number) value).intValue();
}
}
return value;
}
@Override
public Blob handleBlob(String tableName, String blobColumnName, String idColumnName, ResultSet set) throws SQLException {
checkValid();
// we need to return null instead of creating a LazyBlob for null values
final Blob tempBlob = set.getBlob(blobColumnName);
if (tempBlob == null) {
return null;
}
else {
tempBlob.free();
final LazyBlob blob = new LazyBlob(this, tableName, blobColumnName, idColumnName, set.getObject(idColumnName));
managedBlobs.add(blob);
return blob;
}
}
@Override
public void delete(Object... entities) {
checkValid();
boolean isDebug = LOGGER.isDebugEnabled();
if( isDebug ) {
LOGGER.debug("Start deleting of " + entities.length + " entities");
}
for( Object e : entities ) {
if( isDebug ) {
LOGGER.debug("Deleteing " + e);
}
// if( e instanceof EObject ) {
// EObject eo = (EObject) e;
// ObjectMapperFactory<?, ?> f = factories.get(eo.eClass().getInstanceClassName()+"Mapper");
// if( f == null ) {
// throw new IllegalStateException("There's no mapper known for '"+eo.eClass().getInstanceClassName()+"'");
// }
// ObjectMapper<Object> m = (ObjectMapper<Object>) f.createMapper(this);
// m.delete(e);
// } else {
// throw new IllegalStateException("'"+e.getClass().getName()+"' is not an EObject");
// }
final ObjectMapper<Object> m = createMapperForObject(e);
m.delete(e);
}
if( isDebug ) {
LOGGER.debug("Ended deleting entities");
}
}
@SuppressWarnings("unchecked")
private <O> ObjectMapper<O> createMapperForObject(O object) {
if( object instanceof EObject ) {
EObject eo = (EObject) object;
ObjectMapperFactory<?, ?> f = domainFactories.get(eo.eClass().getInstanceClassName());
if( f == null ) {
throw new IllegalStateException("There's no mapper known for '"+eo.eClass().getInstanceClassName()+"'");
}
return (ObjectMapper<O>) f.createMapper(this);
} else {
throw new IllegalStateException("'"+object.getClass().getName()+"' is not an EObject");
}
}
@Override
public void persist(Object... entities) {
checkValid();
boolean isDebug = LOGGER.isDebugEnabled();
if( isDebug ) {
LOGGER.debug("Start persisting of " + entities.length + " entities");
}
List<EObject> savePlan = new ArrayList<EObject>();
for( Object e : entities ) {
if( e instanceof EObject ) {
savePlan.addAll(buildSavePlan((EObject) e));
} else {
throw new IllegalStateException("'"+e.getClass().getName()+"' is not an EObject");
}
}
Set<Object> processed = new HashSet<Object>();
for( EObject e : savePlan ) {
if( processed.contains(e) ) {
continue;
}
processed.add(e);
if( isDebug ) {
LOGGER.debug("Persisting of " + e);
}
// final EObject eo = (EObject) e;
// final ObjectMapperFactory<?, ?> f = factories.get(eo.eClass().getInstanceClassName()+"Mapper");
// if( f == null ) {
// throw new IllegalStateException("There's no mapper known for '"+eo.eClass().getInstanceClassName()+"'");
// }
// final ObjectMapper<Object> m = (ObjectMapper<Object>) f.createMapper(this);
final ObjectMapper<EObject> m = createMapperForObject(e);
if( ! isValidObject(e, m) ) {
LOGGER.error("The object '"+e+"' is attached to another session! Future E-Map versions will throw an exception", new Exception());
//FIXME
// throw new IllegalStateException("The object '"+e+"' is attached to another session!");
}
// final Object l = m.getPrimaryKeyValue(e);
// WE NEED TO GET THE KEY FROM THE CACHE!
final Object txKey = getPrimaryKeyFromTransactionCache(e);
if (txKey == null) {
final Object l = getPrimaryKey(m, e);
if( isNewObject(l) ) {
LOGGER.debug("New object insert");
m.insert(e);
} else {
LOGGER.debug("Existing object update");
m.update(e);
}
}
else {
LOGGER.debug("skipping, was already inserted in this tx");
}
}
if( isDebug ) {
LOGGER.debug("Finished persisting of entities");
}
}
private boolean isValidObject(EObject eo, ObjectMapper<EObject> m) {
try {
if( eo instanceof PersistedEObject ) {
return isAttached(eo);
} else {
Object primaryKey = m.getPrimaryKeyValue(eo);
if( primaryKey instanceof Number ) {
if( ((Number)primaryKey).longValue() > 0 ) {
return isAttached(eo);
}
} else if( primaryKey != null ) {
return isAttached(eo);
}
return true;
}
} catch(Throwable t) {
LOGGER.error(t.getMessage(), t);
}
return true;
}
private List<EObject> buildSavePlan(EObject sourceObject) {
List<EObject> list = new ArrayList<EObject>();
list.add(sourceObject);
//TODO Move the META stuff to the factory so that we don't need to create an instance
final ObjectMapper<EObject> m = createMapperForObject(sourceObject);
// final ObjectMapperFactory<?, ?> f = factories.get(sourceObject.eClass().getInstanceClassName()+"Mapper");
// if( f == null ) {
// throw new IllegalStateException("There's no mapper known for '"+sourceObject.eClass().getInstanceClassName()+"'");
// }
// //TODO Move the META stuff to the factory so that we don't need to create an instance
// final ObjectMapper<Object> m = (ObjectMapper<Object>) f.createMapper(this);
for( EStructuralFeature rf : ((JavaObjectMapper<?>)m).getReferenceFeatures() ) {
EReference r = (EReference) rf;
if( ! (sourceObject instanceof LazyEObject) || ((LazyEObject)sourceObject).isResolved(r) ) {
EObject refInstance = (EObject) sourceObject.eGet(rf);
if( refInstance != null ) {
ObjectMapperFactory<?, ?> tmpFactory = domainFactories.get(refInstance.eClass().getInstanceClassName());
if( tmpFactory == null ) {
throw new IllegalStateException("There's no mapper known for '"+refInstance.eClass().getInstanceClassName()+"'");
}
ObjectMapper<Object> tmpMapper = (ObjectMapper<Object>) tmpFactory.createMapper(this);
Object tmpValue = tmpMapper.getPrimaryKeyValue(refInstance);
if( isNewObject(tmpValue) ) {
if( LOGGER.isDebugEnabled() ) {
LOGGER.debug("Found reference who not yet has assigned a primary key: "+tmpValue+"");
LOGGER.debug("Saving reference first.");
}
list.addAll(0,buildSavePlan(refInstance));
}
}
}
}
return list;
}
void handleNotify(Notification notification) {
boolean isDebug = LOGGER.isDebugEnabled();
if(changeTrackingCount > 0 ){
if( isDebug ) {
LOGGER.debug("Skip change tracking for '"+notification.getFeature()+"' of '"+notification.getNotifier()+"'" );
}
return;
}
if( isDebug ) {
if (notification.getEventType() == Notification.REMOVING_ADAPTER) {
LOGGER.debug("Adapter removed ("+notification.getNotifier()+")");
}
else {
LOGGER.debug("Attribute '"+notification.getFeature()+"' of '"+notification.getNotifier()+"' is modified");
}
}
if( notification.getEventType() == Notification.SET ) {
if( isDebug ) {
LOGGER.debug("Single valued attribute is to set from '"+notification.getOldValue()+"' to '"+notification.getNewValue()+"'");
}
FeatureChange c = new FeatureChange();
c.feature = (EStructuralFeature) notification.getFeature();
c.type = Type.SET;
c.newValue = notification.getNewValue();
c.oldValue = notification.getOldValue();
List<FeatureChange> list = changeStorage.get(notification.getNotifier());
list.add(c);
} else if(
notification.getEventType() == Notification.ADD ||
notification.getEventType() == Notification.ADD_MANY) {
if( isDebug ) {
LOGGER.debug("Addition on multi value attribute");
}
List<FeatureChange> list = changeStorage.get(notification.getNotifier());
if( notification.getNewValue() instanceof List<?> ) {
for( Object o : (List<Object>)notification.getNewValue() ) {
FeatureChange c = new FeatureChange();
c.feature = (EStructuralFeature) notification.getFeature();
c.type = Type.ADD;
c.newValue = o;
list.add(c);
if( isDebug ) {
LOGGER.debug("The value '"+c.newValue+"' is added");
}
}
} else {
FeatureChange c = new FeatureChange();
c.feature = (EStructuralFeature) notification.getFeature();
c.type = Type.ADD;
c.newValue = notification.getNewValue();
if( isDebug ) {
LOGGER.debug("The value '"+c.newValue+"' is added");
}
list.add(c);
}
} else if( notification.getEventType() == Notification.REMOVE ||
notification.getEventType() == Notification.REMOVE_MANY ) {
if( isDebug ) {
LOGGER.debug("Removal on multi value attribute");
}
List<FeatureChange> list = changeStorage.get(notification.getNotifier());
if( notification.getOldValue() instanceof List<?> ) {
for( Object o : (List<Object>)notification.getOldValue() ) {
FeatureChange c = new FeatureChange();
c.feature = (EStructuralFeature) notification.getFeature();
c.type = Type.REMOVE;
c.oldValue = o;
list.add(c);
if( isDebug ) {
LOGGER.debug("The value '"+c.oldValue+"' is removed");
}
}
} else {
FeatureChange c = new FeatureChange();
c.feature = (EStructuralFeature) notification.getFeature();
c.type = Type.REMOVE;
c.oldValue = notification.getOldValue();
if( isDebug ) {
LOGGER.debug("The value '"+c.oldValue+"' is removed");
}
list.add(c);
}
}
}
@Override
public boolean isAttached(Object o) {
checkValid();
return getCache().isCached((EObject) o);
}
@Override
public void registerObject(Object object, Object id, long version) {
checkValid();
EObject eo = (EObject) object;
if( ! changeStorage.containsKey(eo) ) {
changeStorage.put(eo, new ArrayList<FeatureChange>());
eo.eAdapters().add(objectAdapter);
getCache().putObject(eo,id, version);
}
}
// @Override
// public void updateVersion(Object object, Object id, long version) {
// if( ! getCache().updateVersion((EObject) object, id, version) ) {
// throw new IllegalStateException("Unable to update version of Object '"+object+"'");
// }
// }
@Override
public void unregisterObject(Object object, Object id) {
checkValid();
EObject eo = (EObject) object;
if( changeStorage.remove(eo) != null ) {
eo.eAdapters().remove(objectAdapter);
getCache().evitObject(eo);
}
}
@Override
public void unregisterObject(EClass eClass, Object id) {
checkValid();
EObject object = getCache().getObject(eClass, id);
if (object != null) {
// we need to remove it from our changeStorage
if (changeStorage.remove(object) != null) {
object.eAdapters().remove(objectAdapter);
}
}
getCache().evictObject(eClass, id);
}
@Override
public void unregisterAllObjects(EClass eClass) {
checkValid();
// we need to clean up the changeStorage too
Iterator<EObject> iterator = changeStorage.keySet().iterator();
while (iterator.hasNext()) {
final EObject x = iterator.next();
if (x.eClass() == eClass) {
iterator.remove();
x.eAdapters().remove(objectAdapter);
}
}
getCache().evictObjects(eClass);
}
@Override
public void clearChangeDescription(Object object) {
checkValid();
List<FeatureChange> list = changeStorage.get(object);
if( list != null ) {
list.clear();
}
}
// @Override
// public Registration registerObjectChangePersister(final ObjectChangePersistParticipant participant) {
// return registerPersistParticipant(new PersistParticipant() {
//
// @Override
// public Map<String, Object> participate(Session session, Type type, Object o) {
// JavaSession s = (JavaSession) session;
// if( type == Type.UPDATE ) {
// participant.participate(session, o, (List<? extends ObjectChange<?>>) s.getChangeDescription(o));
// }
// return Collections.emptyMap();
// }
// });
// }
@Override
public List<ChangeDescription> getChangeDescription(Object object) {
checkValid();
List<FeatureChange> list = changeStorage.get(object);
if( list != null ) {
Map<EStructuralFeature, ChangeDescriptionImpl> description = new HashMap<EStructuralFeature, ChangeDescriptionImpl>();
for( FeatureChange c : list ) {
ChangeDescriptionImpl d = description.get( c.feature);
if( d == null ) {
d = new ChangeDescriptionImpl(c.feature);
description.put(c.feature, d);
if( ! c.feature.isMany() ) {
d.oldValue = c.oldValue;
}
}
if( c.feature.isMany() ) {
if( c.type == Type.ADD ) {
if( ! d.removals.remove(c.newValue) ) {
d.additions.add(c.newValue);
}
} else if( c.type == Type.REMOVE ) {
if( ! d.additions.remove(c.oldValue) ) {
d.removals.add(c.oldValue);
}
}
} else {
d.newValue = c.newValue;
}
}
Iterator<ChangeDescriptionImpl> iterator = description.values().iterator();
while( iterator.hasNext() ) {
ChangeDescriptionImpl desc = iterator.next();
if( desc.feature.isMany() ) {
if( desc.removals.isEmpty() && desc.additions.isEmpty() ) {
iterator.remove();
}
} else {
if( Objects.equal(desc.newValue, desc.oldValue) ) {
iterator.remove();
}
}
}
return Collections.unmodifiableList(new ArrayList<JavaSession.ChangeDescription>(description.values()));
}
return Collections.emptyList();
}
@Override
public Boolean runWithoutChangeTracking(Callback<Boolean> runnable) {
checkValid();
boolean isDebug = LOGGER.isDebugEnabled();
if( isDebug ) {
LOGGER.debug("Pauseing change tracking: " + changeTrackingCount);
}
changeTrackingCount++;
try {
return runnable.call();
} finally {
changeTrackingCount--;
if( isDebug ) {
LOGGER.debug("Release change tracking: " + changeTrackingCount);
}
}
}
@Override
public long getMemoryObjectVersion(Object object) {
checkValid();
final ObjectMapper<Object> mapper = createMapperForObject(object);
return sessionCache.getVersion((EObject)object, getPrimaryKey(mapper, object));
}
@Override
public long getPersistedObjectVersion(Object object) {
checkValid();
final ObjectMapper<Object> mapper = createMapperForObject(object);
return mapper.selectVersion(getPrimaryKey(mapper, object));
}
}
public static class ChangeDescriptionImpl implements ChangeDescription, ObjectChange<Object> {
public final EStructuralFeature feature;
public List<Object> additions;
public List<Object> removals;
public Object oldValue;
public Object newValue;
public ChangeDescriptionImpl(EStructuralFeature feature) {
this.feature = feature;
this.additions = new ArrayList<Object>();
this.removals = new ArrayList<Object>();
}
@Override
public String getAttributeName() {
return this.feature.getName();
}
@SuppressWarnings("unchecked")
@Override
public Class<Object> getInstanceType() {
return (Class<Object>) feature.getEType().getInstanceClass();
}
@Override
public boolean isMultiValue() {
return feature.isMany();
}
@Override
public EStructuralFeature getFeature() {
return feature;
}
@Override
public List<Object> getAdditions() {
return additions;
}
@Override
public List<Object> getRemovals() {
return removals;
}
@Override
public Object getNewValue() {
return newValue;
}
@Override
public Object geOldValue() {
return oldValue;
}
}
static class FeatureChange {
EStructuralFeature feature;
Object newValue;
Object oldValue;
Type type;
}
enum Type {
ADD,
REMOVE,
SET
}
}