592 lines
19 KiB
Java
Executable File
592 lines
19 KiB
Java
Executable File
/*
|
|
* Copyright (C) 2003-2004 Red Hat Inc. All Rights Reserved.
|
|
*
|
|
* 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.1 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 com.arsdigita.persistence;
|
|
|
|
import com.arsdigita.persistence.metadata.ObjectType;
|
|
import com.arsdigita.persistence.metadata.Property;
|
|
import com.redhat.persistence.PropertyMap;
|
|
import com.redhat.persistence.ProtoException;
|
|
import com.redhat.persistence.Session;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.HashMap;
|
|
import java.util.HashSet;
|
|
import java.util.Iterator;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
|
|
import org.apache.log4j.Logger;
|
|
|
|
/**
|
|
* DataObjectImpl
|
|
*
|
|
* @author Rafael H. Schloming <rhs@mit.edu>
|
|
* @version $Id: DataObjectImpl.java 287 2005-02-22 00:29:02Z sskracic $
|
|
**/
|
|
class DataObjectImpl implements DataObject {
|
|
|
|
final static Logger s_log = Logger.getLogger(DataObjectImpl.class);
|
|
private Session m_ssn;
|
|
private OID m_oid;
|
|
private List m_observers = new ArrayList();
|
|
private Map m_disconnect = null;
|
|
private boolean m_manualDisconnect = false;
|
|
private Throwable m_invalidStack = null;
|
|
private boolean m_valid = true;
|
|
// originating transaction has terminated
|
|
private boolean m_transactionDone = false;
|
|
// package-scoped, written and read by Session
|
|
PropertyMap p_pMap;
|
|
// package-scoped, read/written by Session
|
|
com.redhat.persistence.metadata.ObjectType p_objectType;
|
|
|
|
private final class ObserverEntry {
|
|
|
|
private DataObserver m_observer;
|
|
private Set m_firing = new HashSet();
|
|
private Map m_waiting = new HashMap();
|
|
|
|
private ObserverEntry(DataObserver observer) {
|
|
m_observer = observer;
|
|
}
|
|
|
|
public DataObserver getObserver() {
|
|
return m_observer;
|
|
}
|
|
|
|
public boolean isFiring(DataEvent event) {
|
|
return m_firing.contains(event);
|
|
}
|
|
|
|
public boolean isWaiting(DataEvent event) {
|
|
return m_waiting.containsValue(event);
|
|
}
|
|
|
|
public void setFiring(DataEvent event) {
|
|
// unschedule event from others
|
|
if (isWaiting(event)) {
|
|
for (Iterator it = m_waiting.entrySet().iterator();
|
|
it.hasNext();) {
|
|
Map.Entry me = (Map.Entry) it.next();
|
|
DataEvent value = (DataEvent) me.getValue();
|
|
if (value.equals(event)) {
|
|
m_waiting.remove(me.getKey());
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
m_firing.add(event);
|
|
DataObjectImpl.this.m_firing = ObserverEntry.this;
|
|
}
|
|
|
|
public void scheduleEvent(DataEvent now, DataEvent waiting) {
|
|
if (!isFiring(waiting)) {
|
|
m_waiting.put(now, waiting);
|
|
}
|
|
}
|
|
|
|
public DataEvent clearFiring(DataEvent event) {
|
|
m_firing.remove(event);
|
|
DataObjectImpl.this.m_firing = null;
|
|
return (DataEvent) m_waiting.remove(event);
|
|
}
|
|
|
|
public int hashCode() {
|
|
return m_observer.hashCode();
|
|
}
|
|
|
|
public boolean equals(Object other) {
|
|
if (other instanceof ObserverEntry && other != null) {
|
|
return m_observer.equals(((ObserverEntry) other).m_observer);
|
|
} else {
|
|
return super.equals(other);
|
|
}
|
|
}
|
|
|
|
public String toString() {
|
|
return "Observer: " + m_observer;
|
|
}
|
|
}
|
|
|
|
DataObjectImpl(ObjectType type) {
|
|
m_oid = new OID(type);
|
|
}
|
|
|
|
DataObjectImpl(OID oid) {
|
|
m_oid = oid;
|
|
}
|
|
|
|
void setSession(Session ssn) {
|
|
m_ssn = ssn;
|
|
}
|
|
|
|
private Session getSsn() {
|
|
// disconnected data objects should use the session of the current
|
|
// thread
|
|
if (isDisconnected()) {
|
|
throw new IllegalStateException(
|
|
"There was an error in disconnected object implementation. "
|
|
+ "Disconnected data object can not access session.");
|
|
}
|
|
|
|
// this checks that nondisconnected objects are not being used
|
|
// across threads. It also checks to see if
|
|
// SessionManager.getSession(), which may happen during initialization
|
|
// bootstrapping.
|
|
if (!isDisconnected()
|
|
&& m_ssn != null
|
|
&& SessionManager.getSession() != null
|
|
&& m_ssn != SessionManager.getSession().getProtoSession()) {
|
|
throw new PersistenceException("This data object: (" + this
|
|
+ ") is being accessed from "
|
|
+ "another thread before its originating transaction has "
|
|
+ "terminated.");
|
|
}
|
|
|
|
return m_ssn;
|
|
}
|
|
|
|
private com.redhat.persistence.metadata.Property convert(String property) {
|
|
return C.prop(m_ssn.getRoot(), getObjectType().getProperty(property));
|
|
}
|
|
|
|
public com.arsdigita.persistence.Session getSession() {
|
|
if (isDisconnected()) {
|
|
return SessionManager.getSession();
|
|
}
|
|
return com.arsdigita.persistence.Session.getSessionFromProto(getSsn());
|
|
}
|
|
|
|
public ObjectType getObjectType() {
|
|
return m_oid.getObjectType();
|
|
}
|
|
|
|
public OID getOID() {
|
|
return m_oid;
|
|
}
|
|
|
|
public Object get(String property) {
|
|
validate();
|
|
Property prop = getObjectType().getProperty(property);
|
|
if (prop == null) {
|
|
StringBuilder builder = new StringBuilder();
|
|
Iterator properties = getObjectType().getProperties();
|
|
while (properties.hasNext()) {
|
|
if (builder.length() > 0) {
|
|
builder.append(", ");
|
|
}
|
|
|
|
builder.append(((Property) properties.next()).getName());
|
|
}
|
|
|
|
throw new PersistenceException(String.format(
|
|
"no such property: %s for %s. Available properties: %s",
|
|
property.toString(),
|
|
this.toString(),
|
|
builder.toString()));
|
|
}
|
|
if (prop.isCollection()) {
|
|
if (isDisconnected()) {
|
|
return new DataAssociationImpl(SessionManager.getSession(), this,
|
|
prop);
|
|
} else {
|
|
return new DataAssociationImpl(getSession(), this, prop);
|
|
}
|
|
}
|
|
|
|
if (prop.isKeyProperty()) {
|
|
return m_oid.get(property);
|
|
} else if (m_oid.isInitialized()) {
|
|
if (isDisconnected()) {
|
|
doDisconnect();
|
|
|
|
Object obj = m_disconnect.get(prop);
|
|
|
|
if (m_disconnect.containsKey(prop)) {
|
|
if (!(obj instanceof DataObjectImpl)
|
|
|| (((DataObjectImpl) obj).isValid())) {
|
|
return obj;
|
|
}
|
|
}
|
|
|
|
obj = get(SessionManager.getSession().getProtoSession(),
|
|
convert(property));
|
|
|
|
if (obj instanceof DataObjectImpl) {
|
|
DataObjectImpl dobj = (DataObjectImpl) obj;
|
|
dobj.disconnect();
|
|
if (!dobj.isValid()) {
|
|
throw new IllegalStateException("got invalid data object from session: "
|
|
+ obj);
|
|
}
|
|
}
|
|
m_disconnect.put(prop, obj);
|
|
return obj;
|
|
} else {
|
|
Object result = get(getSsn(), convert(property));
|
|
if (result instanceof DataObjectImpl) {
|
|
DataObjectImpl dobj = (DataObjectImpl) result;
|
|
if (dobj.isDisconnected()) {
|
|
result = getSession().retrieve(dobj.getOID());
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private void doDisconnect() {
|
|
if (m_disconnect != null) {
|
|
return;
|
|
}
|
|
m_disconnect = new HashMap();
|
|
// access the session directly as part of disconnection
|
|
if (m_ssn.isDeleted(this)) {
|
|
return;
|
|
}
|
|
if (!m_manualDisconnect) {
|
|
if (s_log.isDebugEnabled()) {
|
|
s_log.debug("autodisconnect: " + getOID(), new Throwable());
|
|
}
|
|
}
|
|
|
|
com.redhat.persistence.Session ssn =
|
|
SessionManager.getSession().
|
|
getProtoSession();
|
|
|
|
for (Iterator it = getObjectType().getProperties();
|
|
it.hasNext();) {
|
|
Property p = (Property) it.next();
|
|
if (!p.isCollection()
|
|
&& !p.isKeyProperty()
|
|
&& p.getType().isSimple()) {
|
|
m_disconnect.put(p, get(ssn, C.prop(m_ssn.getRoot(), p)));
|
|
}
|
|
}
|
|
|
|
// access the session directly as part of disconnection
|
|
m_ssn.releaseObject(this);
|
|
}
|
|
|
|
public void set(String property, Object value) {
|
|
validateWrite();
|
|
// all entry points for empty strings need to be converted to null
|
|
if ("".equals(value)) {
|
|
value = null;
|
|
}
|
|
try {
|
|
Property prop = getObjectType().getProperty(property);
|
|
if (prop == null) {
|
|
throw new PersistenceException("no such property: " + property
|
|
+ " for " + this);
|
|
}
|
|
if (prop.isKeyProperty()) {
|
|
m_oid.set(property, value);
|
|
if (m_oid.isInitialized()) {
|
|
getSsn().create(this);
|
|
}
|
|
} else {
|
|
getSsn().set(this, convert(property), value);
|
|
}
|
|
} catch (ProtoException pe) {
|
|
throw PersistenceException.newInstance(pe);
|
|
}
|
|
}
|
|
|
|
public boolean isNew() {
|
|
validate();
|
|
if (isDisconnected()) {
|
|
return false;
|
|
}
|
|
// handle calls to isNew before key is set
|
|
return !m_oid.isInitialized() || (getSsn().isNew(this) && !getSsn().
|
|
isPersisted(this));
|
|
}
|
|
|
|
public boolean isDeleted() {
|
|
validate();
|
|
if (isDisconnected()) {
|
|
return false;
|
|
}
|
|
return getSsn().isDeleted(this);
|
|
}
|
|
|
|
public boolean isCommitted() {
|
|
validate();
|
|
if (isDisconnected()) {
|
|
return false;
|
|
}
|
|
return m_oid.isInitialized() && !getSsn().isNew(this);
|
|
}
|
|
|
|
public boolean isDisconnected() {
|
|
return m_manualDisconnect || m_transactionDone || !isValid();
|
|
}
|
|
|
|
void invalidate(boolean connectedOnly, boolean error) {
|
|
if (!isValid()) {
|
|
return;
|
|
}
|
|
|
|
// access the session directly as part of disconnection
|
|
if (error || (!connectedOnly && m_ssn.isModified(this))) {
|
|
m_valid = false;
|
|
if (s_log.isDebugEnabled()) {
|
|
m_invalidStack = new Throwable();
|
|
}
|
|
} else if (connectedOnly && m_manualDisconnect) {
|
|
doDisconnect();
|
|
}
|
|
|
|
m_transactionDone = true;
|
|
}
|
|
|
|
public void disconnect() {
|
|
if (!m_oid.isInitialized()) {
|
|
throw new PersistenceException("can't disconnect uninitialized: "
|
|
+ this);
|
|
}
|
|
|
|
m_manualDisconnect = true;
|
|
m_ssn.releaseObject(this);
|
|
}
|
|
|
|
public boolean isModified() {
|
|
validate();
|
|
if (isDisconnected()) {
|
|
return false;
|
|
}
|
|
return !getSsn().isFlushed(this);
|
|
}
|
|
|
|
public boolean isPropertyModified(String name) {
|
|
validate();
|
|
if (isDisconnected()) {
|
|
return false;
|
|
}
|
|
return !getSsn().isFlushed(this, convert(name));
|
|
}
|
|
|
|
/**
|
|
* False means that the originating transaction had an error or this
|
|
* object was modified and the originating transaction was
|
|
* aborted. Invalid data objects are not allowed to interact with any
|
|
* session.
|
|
*/
|
|
public boolean isValid() {
|
|
return m_valid;
|
|
}
|
|
|
|
private void validate() {
|
|
if (!isValid()) {
|
|
if (s_log.isDebugEnabled()) {
|
|
s_log.debug("invalid data object invalidated at: ",
|
|
m_invalidStack);
|
|
}
|
|
throw new PersistenceException("invalid data object: " + this);
|
|
}
|
|
}
|
|
|
|
private void validateWrite() {
|
|
validate();
|
|
if (isDisconnected()) {
|
|
throw new PersistenceException("can not write to disconnected data object: "
|
|
+ this);
|
|
}
|
|
}
|
|
|
|
public void delete() {
|
|
validateWrite();
|
|
try {
|
|
getSsn().delete(this);
|
|
getSsn().flush();
|
|
getSsn().assertFlushed(this);
|
|
} catch (ProtoException pe) {
|
|
throw PersistenceException.newInstance(
|
|
String.format("on oid '%s'", m_oid.toString()), pe);
|
|
}
|
|
}
|
|
|
|
public void specialize(String subtypeName) {
|
|
validate();
|
|
ObjectType subtype =
|
|
getSession().getMetadataRoot().getObjectType(subtypeName);
|
|
|
|
if (subtype == null) {
|
|
throw new PersistenceException("No such type: " + subtypeName);
|
|
}
|
|
|
|
specialize(subtype);
|
|
}
|
|
|
|
public void specialize(ObjectType subtype) {
|
|
p_pMap = null;
|
|
p_objectType = null;
|
|
|
|
validate();
|
|
m_oid.specialize(subtype);
|
|
}
|
|
|
|
public void save() {
|
|
validateWrite();
|
|
try {
|
|
if (getSsn().isDeleted(this)) {
|
|
throw new PersistenceException("can't save a deleted object");
|
|
}
|
|
|
|
getSession().m_beforeFP.fireNow(new BeforeSaveEvent(this));
|
|
|
|
if (!getSsn().isFlushed(this)) {
|
|
s_log.debug("Flushing...");
|
|
getSsn().flush();
|
|
s_log.debug("Checking flushing...");
|
|
assertFlushed();
|
|
} else {
|
|
// with no changes on the object fire after save directly
|
|
s_log.debug("Direct save...");
|
|
getSession().m_afterFP.fireNow(new AfterSaveEvent(this));
|
|
}
|
|
} catch (ProtoException pe) {
|
|
throw PersistenceException.newInstance(pe);
|
|
}
|
|
}
|
|
|
|
private void assertFlushed() {
|
|
// m_ssn.assertFlushed(this) doesn't work because of '~' properties
|
|
for (Iterator it = getObjectType().getProperties();
|
|
it.hasNext();) {
|
|
Property p = (Property) it.next();
|
|
s_log.debug(String.format(
|
|
"Asserting that property '%s' is flushed...", p.getName()));
|
|
if (!getSsn().isFlushed(this, C.prop(m_ssn.getRoot(), p))) {
|
|
// use m_ssn to generate the exception
|
|
getSsn().assertFlushed(this);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void addObserver(DataObserver observer) {
|
|
validate();
|
|
if (observer == null) {
|
|
throw new IllegalArgumentException("Can't add a null observer.");
|
|
}
|
|
ObserverEntry entry = new ObserverEntry(observer);
|
|
if (!m_observers.contains(entry)) {
|
|
if (m_firing != null) {
|
|
throw new IllegalStateException("Can't add a new observer from within another "
|
|
+ "observer.\n"
|
|
+ "Trying to add: " + observer
|
|
+ "\n" + "Currently firing: "
|
|
+ m_firing + "\n"
|
|
+ "Current observers: "
|
|
+ m_observers);
|
|
}
|
|
m_observers.add(entry);
|
|
}
|
|
}
|
|
private ObserverEntry m_firing = null;
|
|
|
|
void scheduleObserver(DataEvent event) {
|
|
for (Iterator it = m_observers.iterator(); it.hasNext();) {
|
|
ObserverEntry entry = (ObserverEntry) it.next();
|
|
DataObserver observer = entry.getObserver();
|
|
if (event instanceof AfterEvent) {
|
|
entry.scheduleEvent(((AfterEvent) event).getBefore(), event);
|
|
}
|
|
}
|
|
}
|
|
|
|
void fireObserver(DataEvent event) {
|
|
for (int i = 0; i < m_observers.size(); i++) {
|
|
ObserverEntry entry = (ObserverEntry) m_observers.get(i);
|
|
final DataObserver observer = entry.getObserver();
|
|
if (entry.isFiring(event)) {
|
|
if (s_log.isDebugEnabled()) {
|
|
s_log.debug("isFiring: " + event);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (event instanceof AfterEvent) {
|
|
AfterEvent ae = (AfterEvent) event;
|
|
if (entry.isFiring(ae.getBefore())) {
|
|
entry.scheduleEvent(ae.getBefore(), event);
|
|
continue;
|
|
}
|
|
} else if (event instanceof BeforeEvent) {
|
|
BeforeEvent be = (BeforeEvent) event;
|
|
if (entry.isFiring(be.getAfter())) {
|
|
entry.scheduleEvent(be.getAfter(), event);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
try {
|
|
// after events never delay firing
|
|
if (event instanceof BeforeEvent) {
|
|
DataEvent waiting = entry.clearFiring(event);
|
|
if (waiting != null) {
|
|
fireObserver(waiting);
|
|
}
|
|
}
|
|
|
|
entry.setFiring(event);
|
|
|
|
event.invoke(observer);
|
|
|
|
DataEvent waiting = entry.clearFiring(event);
|
|
if (waiting != null) {
|
|
fireObserver(waiting);
|
|
}
|
|
} finally {
|
|
entry.clearFiring(event);
|
|
}
|
|
}
|
|
}
|
|
|
|
private Object get(Session s,
|
|
com.redhat.persistence.metadata.Property p) {
|
|
try {
|
|
return s.get(this, p);
|
|
} catch (ProtoException pe) {
|
|
throw PersistenceException.newInstance(pe);
|
|
}
|
|
}
|
|
|
|
public boolean equals(Object o) {
|
|
if (o instanceof DataObject) {
|
|
return m_oid.equals(((DataObject) o).getOID());
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public int hashCode() {
|
|
return m_oid.hashCode();
|
|
}
|
|
|
|
public String toString() {
|
|
return m_oid.toString();
|
|
}
|
|
}
|