/**
* Copyright 2010 Molindo GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package at.molindo.esi4j.module.hibernate;
import java.io.Serializable;
import java.util.Date;
import java.util.IdentityHashMap;
import java.util.Map.Entry;
import org.hibernate.CacheMode;
import org.hibernate.FlushMode;
import org.hibernate.ObjectNotFoundException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.metadata.ClassMetadata;
import org.hibernate.proxy.HibernateProxy;
import at.molindo.esi4j.chain.Esi4JBatchedEntityResolver;
import at.molindo.esi4j.chain.Esi4JEntityTask;
import at.molindo.esi4j.chain.Esi4JSessionEntityResolver;
import at.molindo.esi4j.ex.EntityNotResolveableException;
import at.molindo.esi4j.mapping.ObjectKey;
import at.molindo.utils.collections.ArrayUtils;
import at.molindo.utils.collections.ClassMap;
public class HibernateEntityResolver implements Esi4JBatchedEntityResolver, Esi4JSessionEntityResolver {
private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(HibernateEntityResolver.class);
private final SessionFactory _sessionFactory;
private final ClassMap<String> _entityNames = ClassMap.create();
private final ThreadLocal<Session> _localSession = new ThreadLocal<>();
/**
* this is an optimization while bulk resolving. Use {@link Session#load(Class, Serializable)} to resolve and fetch
* later using configured bulk fetching.
*/
private final ThreadLocal<EntityBatchResolve> _batchResolve = new ThreadLocal<>();
public HibernateEntityResolver(final SessionFactory sessionFactory) {
if (sessionFactory == null) {
throw new NullPointerException("sessionFactory");
}
for (final Entry<String, ClassMetadata> e : sessionFactory.getAllClassMetadata().entrySet()) {
final Class<?> mappedClass = e.getValue().getMappedClass();
if (mappedClass != null) {
_entityNames.put(mappedClass, e.getKey());
}
}
_sessionFactory = sessionFactory;
}
/**
* must be called within originating session
*/
@Override
public ObjectKey toObjectKey(final Object entity) {
final SessionFactory factory = getSessionFactory();
final Session session = getCurrentSession(factory);
final String entityName = _entityNames.find(entity.getClass());
final ClassMetadata meta = factory.getClassMetadata(entityName);
final Class<?> type = meta.getMappedClass();
final Serializable id = meta.getIdentifier(entity, (SessionImplementor) session);
final Long version = toLongVersion(meta.getVersion(entity));
return new ObjectKey(type, id, version);
}
private Long toLongVersion(final Object version) {
if (version instanceof Number) {
return ((Number) version).longValue();
} else if (version instanceof Date) {
return ((Date) version).getTime();
} else if (version != null) {
log.warn("unexpected version type " + version.getClass().getName());
}
return null;
}
/**
* must be called within originating session
*/
@Override
public Object replaceEntity(final Object entity) {
return toObjectKey(entity);
}
@Override
public void startResolveSession() {
Session session = _localSession.get();
if (session != null) {
log.warn("session already open, now closing first");
closeResolveSession();
session = null;
}
session = getNewSession(getSessionFactory());
session.setDefaultReadOnly(true);
session.setCacheMode(CacheMode.GET);
session.setFlushMode(FlushMode.MANUAL);
session.beginTransaction();
_localSession.set(session);
}
@Override
public void closeResolveSession() {
final Session session = _localSession.get();
if (session != null) {
session.getTransaction().commit();
session.clear();
session.close();
_localSession.set(null);
} else {
log.warn("session not open");
}
}
@Override
public Object resolveEntity(final Object replacedEntity) {
if (replacedEntity instanceof ObjectKey) {
final ObjectKey key = (ObjectKey) replacedEntity;
final Session session = _localSession.get();
if (session == null) {
throw new IllegalStateException("no session available");
}
final EntityBatchResolve batchResolve = _batchResolve.get();
// ignore version, use latest, use load for batch fetching
Object resolvedEntity;
if (batchResolve != null) {
resolvedEntity = session.load(key.getType(), key.getId());
batchResolve.resolved(resolvedEntity);
} else {
resolvedEntity = session.get(key.getType(), key.getId());
}
if (resolvedEntity == null) {
log.error("can't resolve object " + key);
}
return resolvedEntity;
} else {
// not replaced
return replacedEntity;
}
}
@Override
public void resolveEntities(final Esi4JEntityTask[] tasks) {
if (!ArrayUtils.empty(tasks)) {
final EntityBatchResolve batchResolve = new EntityBatchResolve(tasks.length);
_batchResolve.set(batchResolve);
try {
for (int i = 0; i < tasks.length; i++) {
final Esi4JEntityTask task = tasks[i];
if (task != null) {
batchResolve.task(task);
try {
task.resolveEntity(this);
} catch (final EntityNotResolveableException e) {
// this should never happen or something is wrong
log.warn("can't resolve entity although proxy expected");
tasks[i] = null;
}
}
}
} finally {
_batchResolve.remove();
}
batchResolve.resolve(tasks);
}
}
protected Session getCurrentSession(final SessionFactory factory) {
return factory.getCurrentSession();
}
protected Session getNewSession(final SessionFactory factory) {
return factory.openSession();
}
public final SessionFactory getSessionFactory() {
return _sessionFactory;
}
/**
* collect entities created by {@link Session#load(Class, Serializable)} and load them, giving Hibernate a change to
* use it's bulk configuration to reduce number of queries.
*/
private final class EntityBatchResolve {
private final IdentityHashMap<Esi4JEntityTask, Object> _resolved;
private Esi4JEntityTask _task;
private EntityBatchResolve(final int expectedMaxSize) {
_resolved = new IdentityHashMap<>(expectedMaxSize);
}
public void resolve(final Esi4JEntityTask tasks[]) {
for (int i = 0; i < tasks.length; i++) {
final Esi4JEntityTask task = tasks[i];
if (task != null) {
final Object resolved = _resolved.get(task);
if (resolved instanceof HibernateProxy) {
try {
((HibernateProxy) resolved).getHibernateLazyInitializer().initialize();
} catch (final ObjectNotFoundException e) {
log.debug("can't initialize proxy, removing task");
tasks[i] = null;
}
}
}
}
}
private void task(final Esi4JEntityTask task) {
_task = task;
}
private void resolved(final Object resolvedEntity) {
if (_task == null) {
throw new IllegalStateException("no entity expected");
}
_resolved.put(_task, resolvedEntity);
_task = null;
}
}
}