/**
* Copyright (C) 2001-2004 France Telecom R&D
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.objectweb.speedo.mapper.lib;
import org.objectweb.fractal.api.control.BindingController;
import org.objectweb.jorm.api.PAccessor;
import org.objectweb.jorm.api.PBinding;
import org.objectweb.jorm.api.PException;
import org.objectweb.jorm.api.PExceptionNoDSI;
import org.objectweb.jorm.naming.api.PName;
import org.objectweb.perseus.persistence.api.ConnectionHolder;
import org.objectweb.perseus.persistence.api.NoDSIPersistenceException;
import org.objectweb.perseus.persistence.api.PersistenceException;
import org.objectweb.perseus.persistence.api.State;
import org.objectweb.perseus.persistence.api.StorageManager;
import org.objectweb.perseus.persistence.api.WorkingSet;
import org.objectweb.speedo.api.ExceptionHelper;
import org.objectweb.speedo.mapper.api.JormFactory;
import org.objectweb.speedo.mim.api.HomeItf;
import org.objectweb.speedo.mim.api.LifeCycle;
import org.objectweb.speedo.mim.api.PersistentObjectItf;
import org.objectweb.speedo.mim.api.StateItf;
import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
/**
* This class is an implementation of the StorageManager interface based on
* Jorm.The single hypothesis concerns the architecture of the CacheEntry:
* - the CacheEntry implements the PBinding interface
* - the obj parameters implement StateItf and PAceessor.
*
* @author S.Chassande-Barrioz
*/
public class JormStorageManager
implements StorageManager, BindingController {
public final static String JORM_FACTORY_BINDING = "jorm-factory";
public final static String LOGGER_NAME = "org.objectweb.jorm.storageManager";
protected JormFactory jormFactory = null;
protected Logger logger = null;
/**
* listes for each working set the persistent object removed in other
* working set. When a working set want to read a persistent object from
* the data support, a prefetch buffer can be used. But the prefetch buffer
* can contains a removed object.
*/
protected Map ws2removedpo = new HashMap();
//IMPLEMENTATION OF THE UserBindingControler INTERFACE //
//------------------------------------------------------//
public String[] listFc() {
return new String[] { JORM_FACTORY_BINDING };
}
public Object lookupFc(String s) {
if (JORM_FACTORY_BINDING.equals(s)) {
return jormFactory;
}
return null;
}
public void bindFc(String s, Object o) {
if ("logger".equals(s)) {
logger = (Logger) o;
} else if (JORM_FACTORY_BINDING.equals(s)) {
jormFactory = (JormFactory) o;
}
}
public void unbindFc(String s) {
if (JORM_FACTORY_BINDING.equals(s)) {
jormFactory = null;
}
}
//IMPLEMENTATION OF THE StorageManager INTERFACE //
//-----------------------------------------------//
public Object export(ConnectionHolder context, Object obj) throws PersistenceException {
PBinding pb = (PBinding) obj;
try {
boolean flushed = false;
if (pb.getStatus() == PBinding.LIFECYCLE_DELTOWRITE) {
pb.write(context, (PAccessor)
context.getWorkingSet().lookup(pb.getPName()));
flushed = true;
}
Object oid = pb.export(context);
if (!flushed) {
State s = context.getWorkingSet().lookup(oid);
if (s != null) {
PBinding oldObj = (PBinding) s.getCacheEntry().getCeObject();
if (oldObj != obj) {
oldObj.write(context, (PAccessor) s);
}
}
}
return oid;
} catch (PException e) {
throw new PersistenceException(e);
}
}
public Object export(ConnectionHolder context, Object obj, Object hints) throws PersistenceException {
PBinding pb = (PBinding) obj;
try {
boolean flushed = false;
if (pb.getStatus() == PBinding.LIFECYCLE_DELTOWRITE) {
pb.write(context, (PAccessor)
context.getWorkingSet().lookup(pb.getPName()));
flushed = true;
}
Object oid = pb.export(context, hints);
if (!flushed) {
State s = context.getWorkingSet().lookup(oid);
if (s != null) {
PBinding oldObj = (PBinding) s.getCacheEntry().getCeObject();
if (oldObj != obj) {
oldObj.write(context, (PAccessor) s);
}
}
}
return oid;
} catch (PException e) {
throw new PersistenceException(e);
}
}
public void unexport(ConnectionHolder context, Object oid) throws PersistenceException {
if (logger.isLoggable(BasicLevel.DEBUG)) {
logger.log(BasicLevel.DEBUG,
"unexport ctx=" + context + " / oid=" + oid);
}
try {
((PName) oid).unexport(context);
} catch (PException e) {
throw new PersistenceException(e);
}
registerUnexport(context.getWorkingSet(), oid);
}
public void unexport(ConnectionHolder context, Object oid, Object hints) throws PersistenceException {
if (logger.isLoggable(BasicLevel.DEBUG)) {
logger.log(BasicLevel.DEBUG,
"unexport(hints) ctx=" + context + " / oid=" + oid);
}
try {
((PName) oid).unexport(context, hints);
} catch (PException e) {
throw new PersistenceException(e);
}
registerUnexport(context.getWorkingSet(), oid);
}
public void read(ConnectionHolder context, Object oid, State obj) throws PersistenceException {
if (logger.isLoggable(BasicLevel.DEBUG))
logger.log(BasicLevel.DEBUG, "read ctx=" + context
+ " / oid=" + oid
+ " / obj.class=" + obj.getClass().getName());
StateItf state = (StateItf) obj;
PersistentObjectItf pb = (PersistentObjectItf) state.getSpeedoPO();
int speedoStatus = state.speedoGetStatus();
if (pb.getStatus() == PBinding.LIFECYCLE_DELTOWRITE
&& speedoStatus != LifeCycle.PERSISTENT_DELETED
&& speedoStatus != LifeCycle.PERSISTENT_NEW_DELETED) {
throw new PersistenceException(
"Concurrency problem, transaction must be rolledback");
}
try {
pb.read(context, state);
context.getWorkingSet().bind(state, oid, WorkingSet.UNKNOWN_INTENTION);
//modified with rebind
state.indexFieldModified(Integer.MAX_VALUE, true);
pb.speedoGetHome().sendEvent(HomeItf.POST_LOAD, pb, null);
} catch (PExceptionNoDSI e) {
if (logger.isLoggable(BasicLevel.DEBUG)) {
logger.log(BasicLevel.DEBUG, "read ==> NO DSI");
}
throw new NoDSIPersistenceException(e);
} catch (PException e) {
Exception ie = ExceptionHelper.getNested(e);
logger.log(BasicLevel.ERROR, "read ctx=" + context
+ " / oid=" + oid
+ " / obj.class=" + obj.getClass().getName(), ie);
throw new PersistenceException(ie);
}
}
public void read(WorkingSet ws, Object oid, State obj, boolean forUpdate) throws PersistenceException {
Object conn = ws.getConnectionHolder().getCHConnectionForRead();
if (logger.isLoggable(BasicLevel.DEBUG))
logger.log(BasicLevel.DEBUG, "read conn=" + conn
+ " / oid=" + oid
+ " / obj.class=" + obj.getClass().getName()
+ " / tx=" + ws);
StateItf state = (StateItf) obj;
PersistentObjectItf pb = (PersistentObjectItf) state.getSpeedoPO();
int speedoStatus = state.speedoGetStatus();
if (pb.getStatus() == PBinding.LIFECYCLE_DELTOWRITE
&& speedoStatus != LifeCycle.PERSISTENT_DELETED
&& speedoStatus != LifeCycle.PERSISTENT_NEW_DELETED) {
throw new PersistenceException(
"Concurrency problem, transaction must be rolledback");
}
Object ctx = usePrefetchBuffer(ws, oid) ? ws : null;
try {
pb.read(conn, state, ctx, forUpdate);
ws.bind(state, oid, WorkingSet.UNKNOWN_INTENTION);
//modified with rebind
state.indexFieldModified(Integer.MAX_VALUE, true);
pb.speedoGetHome().sendEvent(HomeItf.POST_LOAD, pb, null);
} catch (PExceptionNoDSI e) {
if (logger.isLoggable(BasicLevel.DEBUG)) {
logger.log(BasicLevel.DEBUG, "read ==> NO DSI");
}
throw new NoDSIPersistenceException(e);
} catch (PException e) {
Exception ie = ExceptionHelper.getNested(e);
logger.log(BasicLevel.ERROR, "read conn=" + conn
+ " / oid=" + oid
+ " / obj.class=" + obj.getClass().getName()
+ " / tx=" + ws, ie);
throw new PersistenceException(ie);
}
}
public void write(ConnectionHolder context, Object oid, State obj) throws PersistenceException {
PersistentObjectItf pb = (PersistentObjectItf) obj.getCacheEntry();
if (logger.isLoggable(BasicLevel.DEBUG)) {
logger.log(BasicLevel.DEBUG, "write ctx=" + context
+ " / oid=" + oid
+ " / obj.class=" + obj.getClass().getName()
+ " / binding.status=" + pb.getStatus());
}
int s = pb.getStatus();
int preEvent = 0;
int postEvent = 0;
switch(s) {
case PBinding.LIFECYCLE_ACTIVEFORIO:
preEvent = HomeItf.PRE_UPDATE;
postEvent = HomeItf.POST_UPDATE;
break;
case PBinding.LIFECYCLE_NEWTOWRITE:
preEvent = HomeItf.PRE_CREATE;
postEvent = HomeItf.POST_CREATE;
break;
case PBinding.LIFECYCLE_DELTOWRITE:
preEvent = HomeItf.PRE_DELETE;
postEvent = HomeItf.POST_DELETE;
break;
}
if (preEvent != 0 && postEvent != 0) {
pb.speedoGetHome().sendEvent(preEvent, pb, null);
try {
pb.write(context, (PAccessor) obj);
} catch (PExceptionNoDSI e) {
throw new NoDSIPersistenceException(e);
} catch (PException e) {
throw new PersistenceException(e);
}
pb.speedoGetHome().sendEvent(postEvent, pb, null);
}
}
public void beginWS(WorkingSet ws) {
synchronized(ws2removedpo) {
ws2removedpo.put(ws, new RemovedPOMemento(ws));
if (logger.isLoggable(BasicLevel.DEBUG)) {
logger.log(BasicLevel.DEBUG, "Begin of WS"
+ "\n\t-ws=" + ws);
}
}
}
/**
* forget the working set
*/
public void endWS(WorkingSet ws) {
final boolean debug = logger.isLoggable(BasicLevel.DEBUG);
synchronized(ws2removedpo) {
RemovedPOMemento mem = (RemovedPOMemento) ws2removedpo.remove(ws);
StringBuffer sb = null;
if (mem.removedPOFromWS != null) {
for (Iterator it = ws2removedpo.values().iterator(); it.hasNext();) {
RemovedPOMemento _mem = (RemovedPOMemento) it.next();
_mem.addRemovedPOFromOtherWS(mem.removedPOFromWS);
if (debug) {
if (sb == null) {
sb = new StringBuffer();
sb.append("Add\n\t-oids= ");
sb.append(mem.removedPOFromWS);
}
sb.append("\n\t-ws= " + _mem.ws);
}
}
if (debug && sb != null) {
logger.log(BasicLevel.DEBUG, sb.toString());
}
}
}
}
private void registerUnexport(WorkingSet ws, Object oid) {
synchronized(ws2removedpo) {
RemovedPOMemento rpo = (RemovedPOMemento) ws2removedpo.get(ws);
if (rpo != null) {
rpo.registerRemovedPOFromWS(oid);
if (logger.isLoggable(BasicLevel.DEBUG)) {
logger.log(BasicLevel.DEBUG, "Register the removed object:"
+ "\n\t-oid=" + oid
+ "\n\t-ws=" + ws);
}
} else {
logger.log(BasicLevel.WARN,
"No POM registerd for adding the removed PO:"
+ "\n\t-oid=" + oid
+ "\n\t-ws=" + ws);
}
}
}
/**
* check if the persistent object has not been removed in another context.
* Indeed in case of Jorm uses prefetch buffers, the persistent object will
* be loaded even if it has been removed from the data base.
**/
private boolean usePrefetchBuffer(WorkingSet ws, Object oid) {
synchronized(ws2removedpo) {
RemovedPOMemento rpo = (RemovedPOMemento) ws2removedpo.get(ws);
if (rpo == null) {
StringBuffer sb = new StringBuffer();
sb.append("Internal Warning: No POM found for checking removed POs then Speedo does not use prefetchbuffer optimisation, and reloads data from the database");
sb.append("\n\t-oid=").append(oid);
sb.append("\n\t-ws=").append(ws);
Exception e = new Exception(sb.toString());
logger.log(BasicLevel.WARN, e.getMessage(), e);
return false;
} else {
boolean res = !rpo.isRemoved(oid);
if (logger.isLoggable(BasicLevel.DEBUG)) {
logger.log(BasicLevel.DEBUG,(res ? "": "NOT " ) + "USE prefetch buffer"
+ "\n\t-oid=" + oid
+ "\n\t-ws= " + ws);
}
return res;
}
}
}
}
/**
* This structure registers the removed objects from a particular working set.
* So an instance of this class has to be associated to a working set.
*
* @author S.Chassande-Barrioz
*/
class RemovedPOMemento {
/**
* contains the identifiers of persitent objects which have been removed
* from the working set linked to this.
* this field is used at the end of a working set to transmit the list of
* removed object from this working set.
*/
public HashSet removedPOFromWS = null;
/**
* contains the identifiers of persistent objects which have been removed
* from the working set linked to this or from another working set.
* this field is used to choose if it is possible to use a prefetch buffer
* for a given identifier
*/
public HashSet removedPO = null;
public final WorkingSet ws;
public RemovedPOMemento(WorkingSet _ws) {
this.ws = _ws;
}
/**
* register the unexport of a persistent object from the linked working set
* @param oid is the identifier of the unexported persistent object
*/
public void registerRemovedPOFromWS(Object oid) {
if (removedPOFromWS == null) {
removedPOFromWS = new HashSet();
}
removedPOFromWS.add(oid);
if (removedPO == null) {
removedPO = new HashSet();
}
removedPO.add(oid);
}
/**
* Keeps in memory that persistent objects has been removed from another
* working set
* @param oids is the set of identifier of persistent object which have
* been unexported in another working set.
*/
public void addRemovedPOFromOtherWS(Set oids) {
if (removedPO == null) {
removedPO = new HashSet();
}
removedPO.addAll(oids);
}
/**
* Indicates if a persistent object has been removed from the current
* working set or another.
* @param oid is the identifier of the persistent object which could be
* previously removed.
*/
public boolean isRemoved(Object oid) {
return removedPO != null && removedPO.contains(oid);
}
}