* 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 PersistenceContext {
/**
* Map used hold caches. One cache per bean type.
*/
private final HashMap, ClassContext> typeCache = new HashMap<>();
private final Monitor monitor = new Monitor();
private int putCount;
/**
* Create a new PersistenceContext.
*/
public DefaultPersistenceContext() {
}
/**
* Create as a shallow copy with initial or types that have not been added to.
*/
private DefaultPersistenceContext(DefaultPersistenceContext parent, boolean initial) {
for (Map.Entry, ClassContext> entry : parent.typeCache.entrySet()) {
typeCache.put(entry.getKey(), entry.getValue().copy(initial));
}
}
/**
* Return the initial shallow copy with each ClassContext noting it's initialSize (to detect additions).
*/
@Override
public PersistenceContext forIterate() {
return new DefaultPersistenceContext(this, true);
}
/**
* Return a shallow copy including each ClassContext that has had no additions (still at initialSize).
*/
@Override
public PersistenceContext forIterateReset() {
return new DefaultPersistenceContext(this, false);
}
public boolean resetLimit() {
synchronized (monitor) {
if (putCount < 100) {
return false;
}
putCount = 0;
for (ClassContext value : typeCache.values()) {
if (value.resetLimit()) {
return true;
}
}
// checking after another 100 puts
return false;
}
}
/**
* Set an object into the PersistenceContext.
*/
@Override
public void put(Class> rootType, Object id, Object bean) {
synchronized (monitor) {
putCount++;
getClassContext(rootType).put(id, bean);
}
}
@Override
public Object putIfAbsent(Class> rootType, Object id, Object bean) {
synchronized (monitor) {
putCount++;
return getClassContext(rootType).putIfAbsent(id, bean);
}
}
/**
* Return an object given its type and unique id.
*/
@Override
public Object get(Class> rootType, Object id) {
synchronized (monitor) {
return getClassContext(rootType).get(id);
}
}
@Override
public WithOption getWithOption(Class> rootType, Object id) {
synchronized (monitor) {
return getClassContext(rootType).getWithOption(id);
}
}
/**
* Return the number of beans of the given type in the persistence context.
*/
@Override
public int size(Class> rootType) {
synchronized (monitor) {
ClassContext classMap = typeCache.get(rootType);
return classMap == null ? 0 : classMap.size();
}
}
/**
* Clear the PersistenceContext.
*/
@Override
public void clear() {
synchronized (monitor) {
typeCache.clear();
}
}
@Override
public void clear(Class> rootType) {
synchronized (monitor) {
ClassContext classMap = typeCache.get(rootType);
if (classMap != null) {
classMap.clear();
}
}
}
@Override
public void deleted(Class> rootType, Object id) {
synchronized (monitor) {
ClassContext classMap = typeCache.get(rootType);
if (classMap != null && id != null) {
classMap.deleted(id);
}
}
}
@Override
public void clear(Class> rootType, Object id) {
synchronized (monitor) {
ClassContext classMap = typeCache.get(rootType);
if (classMap != null && id != null) {
classMap.remove(id);
}
}
}
@Override
public String toString() {
synchronized (monitor) {
return typeCache.toString();
}
}
private ClassContext getClassContext(Class> rootType) {
return typeCache.computeIfAbsent(rootType, k -> new ClassContext());
}
private static class ClassContext {
private final Map