blob: 212a7ef37538a8092b5017bc513971d514b08a50 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.openjpa.kernel;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import org.apache.openjpa.lib.util.Localizer;
import org.apache.openjpa.lib.util.ReferenceHashSet;
import org.apache.openjpa.lib.util.collections.AbstractReferenceMap;
import org.apache.openjpa.util.Exceptions;
import org.apache.openjpa.util.UserException;
/**
* Cache of managed objects. Must be static for serialization reasons.
*/
class ManagedCache implements Serializable {
private static final long serialVersionUID = 1L;
private static final Localizer _loc =
Localizer.forPackage(ManagedCache.class);
private Map<Object,StateManagerImpl> _main; // oid -> sm
private Map<Object,StateManagerImpl> _conflicts = null; // conflict oid -> new sm
private Map<Object,StateManagerImpl> _news = null; // tmp id -> new sm
private Collection<StateManagerImpl> _embeds = null; // embedded/non-persistent sms
private Collection<StateManagerImpl> _untracked = null; // hard refs to untracked sms
private BrokerImpl broker;
/**
* Constructor; supply primary cache map.
*/
ManagedCache(BrokerImpl broker) {
this.broker = broker;
_main = (Map<Object, StateManagerImpl>) broker.newManagedObjectCache();
}
/**
* Return the instance for the given oid, optionally allowing
* new instances.
*/
public StateManagerImpl getById(Object oid, boolean allowNew) {
if (oid == null)
return null;
// check main cache for oid
StateManagerImpl sm = _main.get(oid);
StateManagerImpl sm2;
if (sm != null) {
// if it's a new instance, we know it's the only match, because
// other pers instances override new instances in _cache
if (sm.isNew() && !sm.isDeleted())
return (allowNew) ? sm : null;
if (!allowNew || !sm.isDeleted())
return sm;
// sm is deleted; check conflict cache
if (_conflicts != null) {
sm2 = _conflicts.get(oid);
if (sm2 != null)
return sm2;
}
}
// at this point sm is null or deleted; check the new cache for
// any matches. this allows us to match app id objects to new
// instances without permanent oids
if (allowNew && _news != null && !_news.isEmpty()) {
sm2 = _news.get(oid);
if (sm2 != null)
return sm2;
}
return sm;
}
/**
* Call this method when a new state manager initializes itself.
*/
public void add(StateManagerImpl sm) {
if (!sm.isIntercepting()) {
if (_untracked == null)
_untracked = new HashSet<>();
_untracked.add(sm);
}
if (!sm.isPersistent() || sm.isEmbedded()) {
if (_embeds == null)
_embeds = new ReferenceHashSet(AbstractReferenceMap.ReferenceStrength.WEAK);
_embeds.add(sm);
return;
}
// initializing new instance; put in new cache because won't have
// permanent oid yet
if (sm.isNew()) {
if (_news == null)
_news = new HashMap<>();
_news.put(sm.getId(), sm);
return;
}
// initializing persistent instance; put in main cache
StateManagerImpl orig = _main.put(sm.getObjectId(), sm);
if (orig != null) {
_main.put(sm.getObjectId(), orig);
throw new UserException(_loc.get("dup-load", sm.getObjectId(),
Exceptions.toString(orig.getManagedInstance())))
.setFailedObject(sm.getManagedInstance());
}
}
/**
* Remove the given state manager from the cache when it transitions
* to transient.
*/
public void remove(Object id, StateManagerImpl sm) {
// if it has a permanent oid, remove from main / conflict cache,
// else remove from embedded/nontrans cache, and if not there
// remove from new cache
StateManagerImpl orig;
if (sm.getObjectId() != null) {
orig = _main.remove(id);
if (orig != sm) {
if (orig != null)
_main.put(id, orig); // put back
if (_conflicts != null) {
orig = _conflicts.remove(id);
if (orig != null && orig != sm)
_conflicts.put(id, orig); // put back
}
}
} else if ((_embeds == null || !_embeds.remove(sm))
&& _news != null) {
orig = _news.remove(id);
if (orig != null && orig != sm)
_news.put(id, orig); // put back
}
if (_untracked != null)
_untracked.remove(sm);
}
/**
* An embedded or nonpersistent managed instance has been persisted.
*/
public void persist(StateManagerImpl sm) {
if (_embeds != null)
_embeds.remove(sm);
}
/**
* A new instance has just been assigned a permanent oid.
*/
public void assignObjectId(Object id, StateManagerImpl sm) {
// if assigning oid, remove from new cache and put in primary; may
// not be in new cache if another new instance had same id
StateManagerImpl orig = null;
if (_news != null) {
orig = _news.remove(id);
if (orig != null && orig != sm)
_news.put(id, orig); // put back
}
// put in main cache, but make sure we don't replace another
// instance with the same oid
orig = _main.put(sm.getObjectId(), sm);
if (orig != null) {
_main.put(sm.getObjectId(), orig);
if (!orig.isDeleted())
throw new UserException(_loc.get("dup-oid-assign",
sm.getObjectId(),
Exceptions.toString(sm.getManagedInstance())))
.setFailedObject(sm.getManagedInstance());
// same oid as deleted instance; put in conflict cache
if (_conflicts == null)
_conflicts = new HashMap<>();
_conflicts.put(sm.getObjectId(), sm);
}
}
/**
* A new instance has committed; recache under permanent oid.
*/
public void commitNew(Object id, StateManagerImpl sm) {
// if the id didn't change, the instance was already assigned an
// id, but it could have been in conflict cache
StateManagerImpl orig;
if (sm.getObjectId() == id) {
orig = (_conflicts == null) ? null : _conflicts.remove(id);
if (orig == sm) {
orig = _main.put(id, sm);
if (orig != null && !orig.isDeleted()) {
_main.put(sm.getObjectId(), orig);
throw new UserException(_loc.get("dup-oid-assign",
sm.getObjectId(), Exceptions.toString(
sm.getManagedInstance())))
.setFailedObject(sm.getManagedInstance())
.setFatal(true);
}
}
return;
}
// oid changed, so it must previously have been a new instance
// without an assigned oid. remove it from the new cache; ok if
// we end up removing another instance with same id
if (_news != null)
_news.remove(id);
// and put into main cache now that id is assigned
orig = _main.put(sm.getObjectId(), sm);
if (orig != null && orig != sm && !orig.isDeleted()) {
// put back orig and throw error
_main.put(sm.getObjectId(), orig);
throw new UserException(_loc.get("dup-oid-assign",
sm.getObjectId(), Exceptions.toString(sm.getManagedInstance())))
.setFailedObject(sm.getManagedInstance()).setFatal(true);
}
}
/**
* Return a copy of all cached persistent objects.
*/
public Collection<StateManagerImpl> copy() {
// proxies not included here because the state manager is always
// present in other caches too
int size = _main.size();
if (_conflicts != null)
size += _conflicts.size();
if (_news != null)
size += _news.size();
if (_embeds != null)
size += _embeds.size();
if (size == 0)
return Collections.EMPTY_LIST;
List<StateManagerImpl> copy = new ArrayList<>(size);
for (StateManagerImpl sm : _main.values())
copy.add(sm);
if (_conflicts != null && !_conflicts.isEmpty())
for (StateManagerImpl sm : _conflicts.values())
copy.add(sm);
if (_news != null && !_news.isEmpty())
for (StateManagerImpl sm : _news.values())
copy.add(sm);
if (_embeds != null && !_embeds.isEmpty())
for (StateManagerImpl sm : _embeds)
copy.add(sm);
return copy;
}
/**
* Clear the cache.
*/
public void clear() {
_main = (Map<Object, StateManagerImpl>) broker.newManagedObjectCache();
if (_conflicts != null)
_conflicts = null;
if (_news != null)
_news = null;
if (_embeds != null)
_embeds = null;
if (_untracked != null)
_untracked = null;
}
/**
* Clear new instances without permanent oids.
*/
public void clearNew() {
if (_news != null)
_news = null;
}
void dirtyCheck() {
if (_untracked == null)
return;
for (StateManagerImpl sm : _untracked)
sm.dirtyCheck();
}
}