libreccm-legacy/ccm-core/src/com/arsdigita/persistence/DataObjectImpl.java

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();
}
}