All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.ebeaninternal.server.transaction.DefaultPersistenceContext Maven / Gradle / Ivy

There is a newer version: 15.8.1
Show newest version
package io.ebeaninternal.server.transaction;

import io.ebean.bean.EntityBean;
import io.ebeaninternal.api.SpiBeanType;
import io.ebeaninternal.api.SpiBeanTypeManager;
import io.ebeaninternal.api.SpiPersistenceContext;

import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.*;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Default implementation of PersistenceContext.
 * 

* Ensures only one instance of a bean is used according to its type and unique * id. *

* PersistenceContext lives on a Transaction and as such is expected to only * have a single thread accessing it at a time. This is not expected to be used * concurrently. *

* Duplicate beans are ones having the same type and unique id value. These are * considered duplicates and replaced by the bean instance that was already * loaded into the PersistenceContext. */ public final class DefaultPersistenceContext implements SpiPersistenceContext { private final HashMap, ClassContext> typeCache = new HashMap<>(); private final ReentrantLock lock = new ReentrantLock(); private final ReferenceQueue queue = new ReferenceQueue<>(); /** * When we are inside an iterate loop, we will add only WeakReferences. This * allows the JVM GC to collect beans, which are not referenced elsewhere. In * normal operation, we will use hard references, to avoid performance impact */ private int iterateDepth; /** * Create a new PersistenceContext. */ public DefaultPersistenceContext() { } @Override public void beginIterate() { lock.lock(); try { iterateDepth++; } finally { lock.unlock(); } } @Override public void endIterate() { lock.lock(); try { iterateDepth--; expungeStaleEntries(); // when leaving the iterator, cleanup. } finally { lock.unlock(); } } @Override public void put(Class rootType, Object id, Object bean) { lock.lock(); try { expungeStaleEntries(); classContext(rootType).useReferences(iterateDepth > 0).put(id, bean); } finally { lock.unlock(); } } @Override public Object putIfAbsent(Class rootType, Object id, Object bean) { lock.lock(); try { expungeStaleEntries(); return classContext(rootType).useReferences(iterateDepth > 0).putIfAbsent(id, bean); } finally { lock.unlock(); } } /** * Return an object given its type and unique id. */ @Override public Object get(Class rootType, Object id) { lock.lock(); try { expungeStaleEntries(); return classContext(rootType).get(id); } finally { lock.unlock(); } } @Override public WithOption getWithOption(Class rootType, Object id) { lock.lock(); try { expungeStaleEntries(); return classContext(rootType).getWithOption(id); } finally { lock.unlock(); } } @Override public int size(Class rootType) { lock.lock(); try { expungeStaleEntries(); ClassContext classMap = typeCache.get(rootType); return classMap == null ? 0 : classMap.size(); } finally { lock.unlock(); } } @Override public void clear() { lock.lock(); try { typeCache.clear(); expungeStaleEntries(); } finally { lock.unlock(); } } @Override public void clear(Class rootType) { lock.lock(); try { ClassContext classMap = typeCache.get(rootType); if (classMap != null) { classMap.clear(); } expungeStaleEntries(); } finally { lock.unlock(); } } @Override public void deleted(Class rootType, Object id) { lock.lock(); try { ClassContext classMap = typeCache.get(rootType); if (classMap != null && id != null) { classMap.deleted(id); } expungeStaleEntries(); } finally { lock.unlock(); } } @Override public void clear(Class rootType, Object id) { lock.lock(); try { ClassContext classMap = typeCache.get(rootType); if (classMap != null && id != null) { classMap.remove(id); } expungeStaleEntries(); } finally { lock.unlock(); } } @Override public List dirtyBeans(SpiBeanTypeManager manager) { lock.lock(); try { expungeStaleEntries(); List list = new ArrayList<>(); for (ClassContext classContext : typeCache.values()) { classContext.dirtyBeans(manager, list); } return list; } finally { lock.unlock(); } } /** * When there is a queue, poll it to remove stale entries from the map. Note: * This is always done AFTER useReferences was called with * true. Polling an empty queue has no performance impact. */ private void expungeStaleEntries() { Reference ref; while ((ref = queue.poll()) != null) { ((BeanRef) ref).expunge(); } } @Override public String toString() { lock.lock(); try { expungeStaleEntries(); return typeCache.toString(); } finally { lock.unlock(); } } private ClassContext classContext(Class rootType) { return typeCache.computeIfAbsent(rootType, k -> new ClassContext(k, queue)); } private static class ClassContext { private final Map map = new HashMap<>(); private final Class rootType; private final ReferenceQueue queue; private Set deleteSet; private boolean useReferences; private int weakCount; private ClassContext(Class rootType, ReferenceQueue queue) { this.rootType = rootType; this.queue = queue; } /** * When called with "true", initialize referenceQueue and store BeanRefs instead * of real object references. */ private ClassContext useReferences(boolean useReferences) { this.useReferences = useReferences; return this; } @Override public String toString() { return "size:" + map.size() + " (" + weakCount + " weak)"; } private Object get(Object id) { Object ret = map.get(id); if (ret instanceof BeanRef) { return ((BeanRef) ret).get(); } else { return ret; } } private WithOption getWithOption(Object id) { if (deleteSet != null && deleteSet.contains(id)) { return WithOption.DELETED; } Object bean = get(id); return (bean == null) ? null : new WithOption(bean); } private Object putIfAbsent(Object id, Object bean) { Object existingValue = get(id); if (existingValue != null) { // it is not absent return existingValue; } // put the new value and return null indicating the put was successful put(id, bean); return null; } private void put(Object id, Object bean) { Object existing; if (useReferences) { weakCount++; existing = map.put(id, new BeanRef(this, id, bean, queue)); } else { existing = map.put(id, bean); } if (existing instanceof BeanRef) { // when a BeanRef is replaced, its expunge() must NOT remove an entry ((BeanRef) existing).setReplaced(); weakCount--; } } private int size() { return map.size(); } private void clear() { map.clear(); weakCount = 0; } private void remove(Object id) { Object ret = map.remove(id); if (ret instanceof BeanRef) { weakCount--; } } private void deleted(Object id) { if (deleteSet == null) { deleteSet = new HashSet<>(); } deleteSet.add(id); remove(id); } /** * Add the dirty beans to the list. */ void dirtyBeans(SpiBeanTypeManager manager, List list) { final SpiBeanType beanType = manager.beanType(rootType); for (Object value : map.values()) { if (value instanceof BeanRef) { value = ((BeanRef) value).get(); if (value == null) continue; } EntityBean bean = (EntityBean) value; if (bean._ebean_getIntercept().isDirty() || beanType.isToManyDirty(bean)) { list.add(value); } } } } private static class BeanRef extends WeakReference { private final ClassContext classContext; private final Object key; private boolean replaced; private BeanRef(ClassContext classContext, Object key, Object referent, ReferenceQueue q) { super(referent, q); this.classContext = classContext; this.key = key; } private void setReplaced() { replaced = true; } private void expunge() { if (!replaced) { classContext.remove(key); } } } }