org.myrian.persistence.Session.java Source code

Java tutorial

Introduction

Here is the source code for org.myrian.persistence.Session.java

Source

/*
 * 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 org.myrian.persistence;

import org.myrian.persistence.common.Path;
import org.myrian.persistence.metadata.Adapter;
import org.myrian.persistence.metadata.Alias;
import org.myrian.persistence.metadata.Link;
import org.myrian.persistence.metadata.Mapping;
import org.myrian.persistence.metadata.MetadataException;
import org.myrian.persistence.metadata.ObjectMap;
import org.myrian.persistence.metadata.ObjectType;
import org.myrian.persistence.metadata.Property;
import org.myrian.persistence.metadata.Role;
import org.myrian.persistence.metadata.Root;
import org.myrian.persistence.oql.*;
import org.myrian.persistence.oql.Expression;
import org.myrian.persistence.oql.Query;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.log4j.Logger;
import org.apache.commons.collections.map.AbstractReferenceMap;
import org.apache.commons.collections.map.ReferenceMap;
import org.apache.commons.collections.map.ReferenceIdentityMap;

/**
 * A Session object provides the primary means for client Java code to
 * interact with the persistence layer. This code is either Java code using
 * the persistence layer to implement object persistence, or Java code working
 * with persistent objects.
 *
 * @author <a href="mailto:rhs@mit.edu">rhs@mit.edu</a>
 **/

public class Session {

    static final Logger LOG = Logger.getLogger(Session.class);

    private static final PersistentObjectSource POS = new PersistentObjectSource();

    // XXX: link attribute hack
    public static final String LINK_ASSOCIATION = "assoc";

    private final Root m_root;
    private final Engine m_engine;
    private final QuerySource m_qs;

    private final List m_modified = new ArrayList();
    private final Map m_keyodata = new ReferenceMap(AbstractReferenceMap.HARD, AbstractReferenceMap.WEAK);
    private final Map m_objodata = new ReferenceIdentityMap(AbstractReferenceMap.WEAK, AbstractReferenceMap.HARD,
            true);

    private EventStream m_events = new EventStream(this);

    private Set m_violations = new HashSet();

    private final Set m_beforeDelete = new HashSet();
    private final Set m_afterActivate = new HashSet();
    private final Set m_beforeFlush = new HashSet();
    private final Set m_afterFlush = new HashSet();

    private Event m_beforeFlushMarker = null;

    private Map m_attrs = new HashMap();

    public Session(Root root, Engine engine, QuerySource source) {
        m_root = root;
        m_engine = engine;
        m_engine.setSession(this);
        m_qs = source;
        m_qs.setSession(this);
    }

    public Root getRoot() {
        return m_root;
    }

    Engine getEngine() {
        return m_engine;
    }

    public QuerySource getQuerySource() {
        return m_qs;
    }

    EventStream getEventStream() {
        return m_events;
    }

    public Object retrieve(PropertyMap keys) {
        Adapter ad = m_root.getAdapter(keys.getObjectType());
        Object key = ad.getSessionKey(keys);
        ObjectData odata = getObjectDataByKey(key);

        if (odata != null && odata.isLoaded()) {
            if (odata.isDeleted() || odata.isNot()) {
                return null;
            }

            ObjectType requested = keys.getObjectType();
            ObjectType current = getObjectType(odata.getObject());

            if (current.isSubtypeOf(requested)) {
                return odata.getObject();
            } else if (!requested.isSubtypeOf(current)) {
                return null;
            }
        }

        Cursor c = getDataSet(keys).getCursor();
        if (c.next()) {
            Object result = c.get();
            if (c.next()) {
                throw new IllegalStateException("query returned more than one row");
            }
            return result;
        } else {
            return null;
        }
    }

    public Object get(final Object obj, Property prop) {
        if (LOG.isDebugEnabled()) {
            trace("get", new Object[] { obj, prop.getName() });
        }

        final Object[] result = { null };

        if (false && !getObjectType(obj).hasKey()) {
            result[0] = getProperties(obj).get(prop);
        } else {
            prop.dispatch(new Property.Switch() {
                public void onRole(Role role) {
                    PropertyData pd = fetchPropertyData(obj, role);
                    result[0] = pd.get();
                }

                public void onAlias(Alias alias) {
                    result[0] = get(obj, alias.getTarget());
                }

                public void onLink(Link link) {
                    DataSet ds = getDataSet(obj, link);
                    if (!link.isCollection()) {
                        Cursor c = ds.getCursor();
                        if (c.next()) {
                            result[0] = c.get(LINK_ASSOCIATION);

                            if (c.next()) {
                                throw new IllegalStateException("Link query returned too many rows");
                            }
                        }
                    } else {
                        result[0] = POS.getPersistentCollection(Session.this, ds);
                    }
                }
            });
        }

        if (LOG.isDebugEnabled()) {
            untrace("get", result[0]);
        }

        return result[0];
    }

    public DataSet getDataSet(ObjectType type) {
        Expression objs = new All(type.getQualifiedName());
        Signature sig = new Signature(type);
        return new DataSet(this, sig, objs);
    }

    public DataSet getDataSet(PropertyMap keys) {
        ObjectType type = keys.getObjectType();
        Expression expr = new All(type.getQualifiedName());
        Signature sig = new Signature(type);

        ObjectMap map = type.getRoot().getObjectMap(type);
        Collection keyProps = map.getKeyProperties();
        if (keyProps.isEmpty()) {
            throw new IllegalStateException("empty key: " + map);
        }

        Expression filter = null;

        for (Iterator it = keyProps.iterator(); it.hasNext();) {
            Property keyProp = (Property) it.next();
            Object key = keys.get(keyProp);
            Expression propFilt = new Equals(new Variable(keyProp.getName()), new Literal(key));

            if (filter == null) {
                filter = propFilt;
            } else {
                filter = new And(filter, propFilt);
            }
        }

        expr = new Filter(expr, filter);
        return new DataSet(this, sig, expr);
    }

    // These should probably be changed to take type signatures.
    public DataSet getDataSet(Object obj) {
        ObjectType type = getObjectType(obj);
        Signature sig = new Signature(type);
        Expression expr = new Literal(obj);
        return new DataSet(this, sig, expr);
    }

    public DataSet getDataSet(final Object obj, Property prop) {
        final DataSet[] result = new DataSet[1];
        prop.dispatch(new Property.Switch() {
            public void onRole(Role role) {
                result[0] = getDataSet(obj, role);
            }

            public void onAlias(Alias alias) {
                throw new Error("not implemented yet");
            }

            public void onLink(Link link) {
                result[0] = getDataSet(obj, link);
            }
        });

        return result[0];
    }

    private DataSet getDataSet(Object obj, Role role) {
        ObjectType propType = role.getType();
        DataSet ds = getDataSet(obj);
        if (propType.isCompound()) {
            Signature sig = new Signature(propType);
            Expression expr = ds.getExpression();
            if (!role.isCollection()) {
                expr = new Filter(expr, new Exists(new Variable(role.getName())));
            }
            expr = new Get(expr, role.getName());
            return new DataSet(this, sig, expr);
        } else {
            ds.getSignature().addPath(role.getName());
            return ds;
        }
    }

    private DataSet getDataSet(Object obj, Link link) {
        return getDataSet(obj, link, null);
    }

    private DataSet getDataSet(Object obj, Link link, Object value) {
        // join(link = all(linkType), all(targetType),
        //      target == link.target)
        ObjectType targetType = link.getTo().getType();
        Expression targets = new Define(new All(targetType.getQualifiedName()), LINK_ASSOCIATION);
        Expression links = new Define(new All(link.getLinkType().getQualifiedName()), "link");

        Variable linkVar = new Variable("link");

        Expression cond = null;
        for (Iterator it = targetType.getKeyProperties().iterator(); it.hasNext();) {
            Property key = (Property) it.next();
            String name = key.getName();
            Expression e = new Equals(new Get(new Variable(LINK_ASSOCIATION), name),
                    new Get(new Get(linkVar, link.getTo().getName()), name));
            if (cond == null) {
                cond = e;
            } else {
                cond = new And(e, cond);
            }
        }
        Expression expr = new Join(targets, links, cond);
        Expression from = new Literal(obj);
        expr = new Filter(expr, new Equals(new Get(linkVar, link.getFrom().getName()), from));

        Signature sig = new Signature();
        sig.addSource(targetType, Path.get(LINK_ASSOCIATION));
        sig.addSource(link.getLinkType(), Path.get("link"));

        if (value != null) {
            Expression to = new Literal(value);
            expr = new Filter(expr, new Equals(new Variable(LINK_ASSOCIATION), to));
        }

        return new DataSet(this, sig, expr);
    }

    Object get(Object start, Path path) {
        Object value;

        if (path.getParent() == null) {
            value = start;
        } else {
            value = get(start, path.getParent());
        }

        ObjectType type = getObjectType(start);
        Property prop = type.getProperty(path.getName());
        if (prop == null) {
            throw new IllegalArgumentException(value + ": no such property: " + path.getName());
        }

        return get(start, prop);
    }

    public void create(Object obj) {
        try {
            if (LOG.isDebugEnabled()) {
                trace("create", new Object[] { obj });
            }
            Expander e = new Expander(this);
            e.expand(new CreateEvent(this, obj));
            activate(e);
        } finally {
            if (LOG.isDebugEnabled()) {
                untrace("create");
            }
        }
    }

    public void store(Object obj, ObjectMap map) {
        try {
            if (LOG.isDebugEnabled()) {
                trace("store", new Object[] { obj });
            }
            ObjectData od = getObjectData(obj);
            ObjectMap om = od.getObjectMap();
            if (om != null) {
                throw new IllegalArgumentException("object already assigned to a store: " + obj);
            }

            od.setObjectMap(map);

            od.propogateMap(null);
            od.propogateKey(null);

            computeFlushability();
        } finally {
            if (LOG.isDebugEnabled()) {
                untrace("store");
            }
        }
    }

    public boolean delete(Object obj) {
        try {
            if (LOG.isDebugEnabled()) {
                trace("delete", new Object[] { obj });
            }

            if (isDeleted(obj)) {
                return false;
            }

            Expander e = new Expander(this);
            e.expand(new DeleteEvent(this, obj));
            activate(e);
            return true;
        } finally {
            if (LOG.isDebugEnabled()) {
                untrace("delete");
            }
        }
    }

    public void set(final Object obj, Property prop, final Object value) {
        try {
            if (LOG.isDebugEnabled()) {
                trace("set", new Object[] { obj, prop, value });
            }

            // FIXME will be unnecessary once in memory querying works
            if (value == null && isNew(obj) && !getObjectData(obj).hasPropertyData(prop)) {
                return;
            }

            final Expander e = new Expander(this);

            prop.dispatch(new Property.Switch() {
                public void onRole(Role role) {
                    e.expand(new SetEvent(Session.this, obj, role, value));
                }

                public void onAlias(Alias alias) {
                    e.expand(new SetEvent(Session.this, obj, alias.getTarget(), value));
                }

                public void onLink(Link link) {
                    Object oldValue = get(obj, link);
                    PropertyMap pmap = new PropertyMap(link.getLinkType());
                    pmap.put(link.getFrom(), obj);

                    if (oldValue != null) {
                        pmap.put(link.getTo(), oldValue);
                        e.expand(new DeleteEvent(Session.this, getObject(pmap)));
                    }

                    pmap.put(link.getTo(), value);
                    Object obj = newObject(pmap);
                    e.expand(new CreateEvent(Session.this, obj));
                    set(e, obj, pmap);
                }
            });

            activate(e);
        } finally {
            if (LOG.isDebugEnabled()) {
                untrace("set");
            }
        }
    }

    public Object add(final Object obj, Property prop, final Object value) {
        final Object[] result = { null };

        try {
            if (LOG.isDebugEnabled()) {
                trace("add", new Object[] { obj, prop, value });
            }

            final Expander e = new Expander(this);

            prop.dispatch(new Property.Switch() {
                public void onRole(Role role) {
                    e.expand(new AddEvent(Session.this, obj, role, value));
                }

                public void onAlias(Alias alias) {
                    e.expand(new AddEvent(Session.this, obj, alias.getTarget(), value));
                }

                public void onLink(Link link) {
                    PropertyMap pmap = new PropertyMap(link.getLinkType());
                    pmap.put(link.getFrom(), obj);
                    pmap.put(link.getTo(), value);
                    result[0] = newObject(pmap);
                    e.expand(new CreateEvent(Session.this, result[0]));
                    set(e, result[0], pmap);
                }
            });

            activate(e);
        } finally {
            if (LOG.isDebugEnabled()) {
                untrace("add", result[0]);
            }
        }

        return result[0];
    }

    private void set(Expander e, Object obj, PropertyMap pmap) {
        store(obj, getRoot().getObjectMap(pmap.getObjectType()));
        for (Iterator it = pmap.entrySet().iterator(); it.hasNext();) {
            Map.Entry me = (Map.Entry) it.next();
            Property prop = (Property) me.getKey();
            Object value = me.getValue();
            e.expand(new SetEvent(Session.this, obj, prop, value));
        }
    }

    public void remove(final Object obj, Property prop, final Object value) {
        try {
            if (LOG.isDebugEnabled()) {
                trace("remove", new Object[] { obj, prop, value });
            }
            Expander e = new Expander(this);
            remove(obj, prop, value, e);
            activate(e);
        } finally {
            if (LOG.isDebugEnabled()) {
                untrace("remove");
            }
        }
    }

    private void remove(final Object obj, Property prop, final Object value, final Expander e) {
        prop.dispatch(new Property.Switch() {
            public void onRole(Role role) {
                e.expand(new RemoveEvent(Session.this, obj, role, value));
            }

            public void onAlias(Alias alias) {
                e.expand(new RemoveEvent(Session.this, obj, alias.getTarget(), value));
            }

            public void onLink(Link link) {
                Cursor c = getDataSet(obj, link, value).getCursor();
                while (c.next()) {
                    e.expand(new DeleteEvent(Session.this, c.get("link")));
                }
            }
        });
    }

    public void clear(Object obj, Property prop) {
        try {
            if (LOG.isDebugEnabled()) {
                trace("clear", new Object[] { obj, prop });
            }
            // FIXME will be unnecessary once in memory querying works
            if (isNew(obj) && !getObjectData(obj).hasPropertyData(prop)) {
                return;
            }
            final Expander e = new Expander(this);
            PersistentCollection pc = (PersistentCollection) get(obj, prop);
            Cursor c = pc.getDataSet().getCursor();
            while (c.next()) {
                remove(obj, prop, c.get(), e);
            }
            activate(e);
        } finally {
            if (LOG.isDebugEnabled()) {
                untrace("clear");
            }
        }
    }

    private Object getObject(PropertyMap pmap) {
        Adapter ad = m_root.getAdapter(pmap.getObjectType());
        return getObject(ad.getSessionKey(pmap));
    }

    private Object newObject(PropertyMap pmap) {
        Adapter ad = m_root.getAdapter(pmap.getObjectType());
        return ad.getObject(pmap.getObjectType(), pmap, this);
    }

    public ObjectType getObjectType(Object obj) {
        return getAdapter(obj).getObjectType(obj);
    }

    Adapter getAdapter(Object obj) {
        return m_root.getAdapter(obj.getClass());
    }

    public boolean hasObjectMap(Object obj) {
        ObjectData odata = getObjectData(obj);
        if (odata == null) {
            return false;
        }
        return odata.getObjectMap() != null;
    }

    public ObjectMap getObjectMap(Object obj) {
        ObjectData odata = getObjectData(obj);
        if (odata == null) {
            throw new IllegalArgumentException("not a session managed object: " + str(obj));
        }
        ObjectMap result = odata.getObjectMap();
        if (result == null) {
            throw new IllegalStateException("no map for object: " + str(obj));
        }
        return result;
    }

    public Object getContainer(Object obj) {
        ObjectData odata = getObjectData(obj);
        if (odata == null) {
            throw new IllegalArgumentException("not a session managed object: " + str(obj));
        }
        return odata.getContainer();
    }

    public PropertyMap getProperties(Object obj) {
        if (hasObjectMap(obj)) {
            return getObjectData(obj).getProperties();
        } else {
            return getAdapter(obj).getProperties(obj);
        }
    }

    public boolean isManaged(Object obj) {
        return hasObjectData(obj);
    }

    /**
     * @return true iff this session has outstanding events
     **/
    public boolean isFlushed() {
        return m_events.size() == 0;
    }

    public boolean isNew(Object obj) {
        return hasObjectData(obj) && getObjectData(obj).isNew();
    }

    public boolean isDeleted(Object obj) {
        return hasObjectData(obj) && getObjectData(obj).isDeleted();
    }

    public boolean isModified(Object obj) {
        return hasObjectData(obj) && getObjectData(obj).isModified();
    }

    public boolean isFlushed(Object obj) {
        if (!hasObjectData(obj)) {
            return true;
        }

        ObjectData od = getObjectData(obj);
        return od.isFlushed();
    }

    public void assertFlushed(Object obj) {
        if (!isFlushed(obj)) {
            // could derive which violations are relevant for this object
            throw new FlushException(obj, m_violations);
        }
    }

    public boolean isFlushed(final Object obj, Property prop) {
        if (!hasObjectData(obj)) {
            return true;
        }

        final boolean[] result = { true };

        prop.dispatch(new Property.Switch() {
            public void onRole(Role r) {
                PropertyData pd = getObjectData(obj).getPropertyData(r);
                if (pd == null) {
                    result[0] = true;
                } else {
                    result[0] = pd.isFlushed();
                }
            }

            public void onAlias(Alias a) {
                result[0] = isFlushed(obj, a.getTarget());
            }

            public void onLink(Link l) {
                result[0] = isFlushed(obj, l.getFrom().getReverse());
            }
        });

        return result[0];
    }

    public boolean isPersisted(Object obj) {
        if (hasObjectData(obj)) {
            ObjectData od = getObjectData(obj);
            return !od.isInfantile() && !od.isNot();
        } else {
            return false;
        }
    }

    private static final Integer s_zero = new Integer(0);

    private void process(Collection processors, Collection events) {
        for (Iterator it = processors.iterator(); it.hasNext();) {
            EventProcessor ep = (EventProcessor) it.next();

            for (Iterator it2 = events.iterator(); it2.hasNext();) {
                Event ev = (Event) it2.next();
                ep.write(ev);
            }

            ep.flush();
        }
    }

    private void computeFlushability() {
        for (Iterator it = m_events.iterator(); it.hasNext();) {
            ((Event) it.next()).m_flushable = true;
        }

        // find unflushable events
        // the source of unflushability is not null properties that
        // have not been set
        List queue = new LinkedList();
        for (Iterator it = m_violations.iterator(); it.hasNext();) {
            Violation v = (Violation) it.next();
            queue.addAll(v.getDependentEvents());
        }

        // recursively mark reachable events as unflushable
        while (queue.size() != 0) {
            Event ev = (Event) queue.remove(0);
            if (ev.m_flushable) {
                ev.m_flushable = false;
                for (Iterator evs = ev.getDependentEvents(); evs.hasNext();) {
                    queue.add(evs.next());
                }
            }
        }
    }

    /**
     * Performs all operations queued up by the session. This is automatically
     * called when necessary in order to insure that queries performed by the
     * datastore are consistent with the contents of the in memory data cache.
     **/
    public void flush() {
        try {
            if (LOG.isDebugEnabled()) {
                trace("flush", new Object[] {});
            }

            flushInternal();
        } finally {
            if (LOG.isDebugEnabled()) {
                untrace("flush");
            }
        }
    }

    public void flushAll() {
        flush();
        if (!isFlushed()) {
            throw new FlushException(m_violations);
        }
    }

    private void flushInternal() {
        boolean flushed = m_beforeFlushMarker != null;
        List events = m_events.getEvents();
        for (int i = 0; i < events.size(); i++) {
            Event ev = (Event) events.get(i);
            if (flushed) {
                if (m_beforeFlushMarker.equals(ev)) {
                    flushed = false;
                }
                continue;
            }

            m_beforeFlushMarker = ev;

            for (Iterator eps = m_beforeFlush.iterator(); eps.hasNext();) {
                EventProcessor ep = (EventProcessor) eps.next();
                ep.write(ev);
            }
        }

        for (Iterator it = m_beforeFlush.iterator(); it.hasNext();) {
            EventProcessor ep = (EventProcessor) it.next();
            ep.flush();
        }

        List written = new LinkedList();

        Event prev = null;
        for (Iterator it = m_events.iterator(); it.hasNext();) {
            Event ev = (Event) it.next();
            if (ev.m_flushable) {
                m_engine.write(ev);
                written.add(ev);
                it.remove();
                if (ev.equals(m_beforeFlushMarker)) {
                    m_beforeFlushMarker = prev;
                }
            } else {
                prev = ev;
            }
        }

        m_engine.flush();

        for (Iterator it = written.iterator(); it.hasNext();) {
            Event ev = (Event) it.next();
            ev.sync();
        }

        computeFlushability();

        process(m_afterFlush, written);

        if (LOG.isInfoEnabled()) {
            if (m_events.size() > 0) {
                LOG.info("unflushed: " + m_events.size());
            }
        }

        if (LOG.isDebugEnabled()) {
            if (m_events.size() > 0) {
                LOG.debug("events: " + m_events.getEvents());
                LOG.debug("violations: " + m_violations);
            }
        }
    }

    private void clear(boolean isCommit) {
        for (Iterator it = m_objodata.values().iterator(); it.hasNext();) {
            ((ObjectData) it.next()).clear();
        }
        m_events.clear();
        m_violations.clear();
        m_modified.clear();
        m_beforeFlushMarker = null;
        if (LOG.isDebugEnabled()) {
            setLevel(0);
        }
        cleanUpEventProcessors(isCommit);
    }

    /**
     * Renders all changes made within the transaction permanent and ends the
     * transaction.
     **/

    public void commit() {
        boolean success = false;
        try {
            flushAll();
            clear(true);
            m_engine.commit();
            if (LOG.isInfoEnabled()) {
                LOG.info("commit");
            }
            success = true;
        } finally {
            if (!success) {
                clear(false);
            }
        }
    }

    /**
     * This does not actually commit the transaction, but does the rest of the
     * work associated with commit.
     */
    void testCommit() {
        boolean success = false;
        try {
            flushAll();
            clear(true);
            if (LOG.isInfoEnabled()) {
                LOG.info("testCommit");
            }
            success = true;
        } finally {
            if (!success) {
                clear(false);
            }
        }
    }

    private void cleanUpEventProcessors(boolean isCommit) {
        for (Iterator eps = m_afterActivate.iterator(); eps.hasNext();) {
            EventProcessor ep = (EventProcessor) eps.next();
            ep.cleanUp(isCommit);
        }
        for (Iterator eps = m_beforeFlush.iterator(); eps.hasNext();) {
            EventProcessor ep = (EventProcessor) eps.next();
            ep.cleanUp(isCommit);
        }
        for (Iterator eps = m_afterFlush.iterator(); eps.hasNext();) {
            EventProcessor ep = (EventProcessor) eps.next();
            ep.cleanUp(isCommit);
        }
    }

    /**
     * Reverts all changes made within the transaction and ends the
     * transaction.
     **/

    public void rollback() {
        try {
            m_engine.rollback();
        } finally {
            clear(false);
            if (LOG.isInfoEnabled()) {
                LOG.info("rollback");
            }
        }
    }

    void load(Object obj, Property prop, Object value) {
        if (LOG.isDebugEnabled()) {
            trace("load", new Object[] { obj, prop.getName(), value });
        }

        ObjectData od = getObjectData(obj);
        if (od == null) {
            throw new IllegalArgumentException("not a session managed object: " + obj);
        }

        PropertyData pd = od.getPropertyData(prop);
        if (pd == null) {
            pd = new PropertyData(od, prop, value);
        } else {
            pd.setValue(value);
        }

        if (LOG.isDebugEnabled()) {
            untrace("load");
        }
    }

    private void activate(Expander e) {
        List pending = e.finish();
        if (e.didDelete()) {
            process(m_beforeDelete, pending);
        }

        List activated = new ArrayList();
        for (Iterator it = pending.iterator(); it.hasNext();) {
            Event ev = (Event) it.next();
            if (e.didDelete()) {
                ObjectData od = getObjectData(ev.getObject());
                if (od != null && od.isDeleted()) {
                    continue;
                }

                if (ev instanceof PropertyEvent) {
                    PropertyEvent pev = (PropertyEvent) ev;
                    Object arg = pev.getArgument();
                    if (arg != null) {
                        ObjectData pod = getObjectData(arg);
                        if (pod != null && pod.isDeleted()) {
                            continue;
                        }
                    }
                }
            }

            ev.activate();
            activated.add(ev);
        }

        computeFlushability();

        process(m_afterActivate, activated);
    }

    public Object getSessionKey(Object obj) {
        ObjectData odata = getObjectData(obj);
        if (odata == null || odata.getKey() == null) {
            throw new IllegalArgumentException("no key for object: " + str(obj));
        }
        return odata.getKey();
    }

    /**
     * Forces this session to release references to the specified object from
     * its internal caches. Events that point to this object continue to do
     * so.
     *
     * @param obj the object to release. It should not be nul
     **/
    public void releaseObject(Object obj) {
        ObjectData od = getObjectData(obj);
        if (od != null && od.getObject() == obj) {
            Adapter ad = getAdapter(obj);
            ObjectType type = ad.getObjectType(obj);
            Object newObj = ad.getObject(type, ad.getProperties(obj), this);
            od.setObject(newObj);
        }
    }

    Object getObject(Object key) {
        if (m_keyodata.containsKey(key)) {
            return ((ObjectData) m_keyodata.get(key)).getObject();
        } else {
            return null;
        }
    }

    ObjectData getObjectDataByKey(Object key) {
        return (ObjectData) m_keyodata.get(key);
    }

    boolean hasObjectData(Object obj) {
        return m_objodata.containsKey(obj);
    }

    ObjectData getObjectData(Object obj) {
        return (ObjectData) m_objodata.get(obj);
    }

    void addObjectData(ObjectData odata) {
        m_objodata.put(odata.getObject(), odata);
    }

    void addModified(Object obj) {
        m_modified.add(obj);
    }

    boolean hasSessionKey(Object obj) {
        ObjectData odata = getObjectData(obj);
        return odata != null && odata.getKey() != null;
    }

    void setSessionKey(Object obj, Object key) {
        ObjectData old = getObjectDataByKey(key);
        ObjectData odata = getObjectData(obj);
        if (old == null || old.isDeleted() || old.isNot()) {
            m_keyodata.put(key, odata);
            odata.setKey(key);
        } else if (old == odata) {
            throw new IllegalStateException("duplicate call to setSessionKey");
        } else {
            // We used to throw duplicate object exception in this
            // case, but since this now gets called during event
            // activation that would leave the set of expanded events
            // half activated. It just so happens if we do nothing in
            // this case we will leave in place the violations
            // associated with the object which is the behavior we
            // want since at some later point the user may be able to
            // assign the object to a better key.

            //ProtoException pe = new DuplicateObjectException(obj);
            //pe.setInternal(false);
            //throw pe;
        }
    }

    PropertyData getPropertyData(Object obj, Property prop) {
        ObjectData od = getObjectData(obj);
        if (od != null) {
            return od.getPropertyData(prop);
        } else {
            return null;
        }
    }

    ObjectData fetchObjectData(Object obj) {
        ObjectData od = getObjectData(obj);
        if (od == null || !od.isLoaded()) {
            RecordSet rs = getDataSet(obj).getCursor().execute();
            if (rs.next()) {
                do {
                    rs.load(this);
                } while (rs.next());
                if (od == null) {
                    od = getObjectData(obj);
                }
            } else if (od != null) {
                od.setState(ObjectData.NONE);
            }
        }

        if (od == null || od.isDeleted() || od.isNot()) {
            return null;
        } else {
            return od;
        }
    }

    PropertyData fetchPropertyData(Object obj, Property prop) {
        ObjectData od = fetchObjectData(obj);
        if (od == null) {
            throw new IllegalArgumentException("No such object: " + getSessionKey(obj));
        }

        PropertyData pd;
        if (od.hasPropertyData(prop)) {
            pd = od.getPropertyData(prop);
        } else if (prop.isCollection()) {
            pd = new PropertyData(od, prop, POS.getPersistentCollection(this, getDataSet(obj, prop)));
        } else if (od.isNew()) {
            pd = new PropertyData(od, prop, null);
        } else {
            RecordSet rs = getDataSet(obj, prop).getCursor().execute();
            Map values = null;
            if (rs.next()) {
                values = rs.load(this);

                if (rs.next()) {
                    throw new IllegalStateException("Query returned too many rows");
                }
            }

            ObjectMap map = od.getObjectMap();
            Mapping mapping = map.getMapping(prop);

            if (mapping.isCompound()) {
                if (values == null) {
                    load(obj, prop, null);
                } else {
                    load(obj, prop, values.get(null));
                }
            } else if (values == null) {
                throw new MetadataException(prop.getRoot(), prop,
                        "Query for: " + prop.getContainer() + "." + prop + " failed to return rows");
            }

            pd = od.getPropertyData(prop);
            if (pd == null) {
                throw new IllegalStateException("Query failed to retrieve property");
            }
        }

        return pd;
    }

    void addViolation(Violation v) {
        m_violations.add(v);
    }

    void removeViolation(Violation v) {
        m_violations.remove(v);
    }

    private void check(EventProcessor ep) {
        if (ep == null) {
            throw new IllegalArgumentException("null event processor");
        }
    }

    /**
     * Before delete event processors are sent events associated with deletes
     * and removes that cause deletes. They have some constraints. It is
     * illegal to add objects to roles that are being deleted by the set of
     * events being activated. In other words, don't put an object in a
     * position in the object graph that would imply that it should be deleted
     * as a consequence of any delete event being activated.
     */
    public void addBeforeDelete(EventProcessor ep) {
        check(ep);
        m_beforeDelete.add(ep);
    }

    public void addAfterActivate(EventProcessor ep) {
        check(ep);
        m_afterActivate.add(ep);
    }

    public void addBeforeFlush(EventProcessor ep) {
        check(ep);
        m_beforeFlush.add(ep);
    }

    public void addAfterFlush(EventProcessor ep) {
        check(ep);
        m_afterFlush.add(ep);
    }

    /**
     * Sets an attribute inside of this <code>Session</code>.  The attribute
     * exists as long as the session is open.  It is not affected by commit or
     * rollback.
     *
     * @see #getAttribute(String)
     * @see #removeAttribute(String)
     *
     * @param name the name of the attribute
     * @param value the value of the attribute
     * @post getAttribute(name) == value
     */
    public void setAttribute(String name, Object value) {
        m_attrs.put(name, value);
    }

    /**
     * Returns the named attribute.
     *
     * @see #setAttribute(String, Object)
     *
     * @param name the name of the attribute
     * @return the value of the attribute, or null if no attribute with
     *   this value has been stored
     */
    public Object getAttribute(String name) {
        return m_attrs.get(name);
    }

    /**
     * Removes the named attribute from this <code>Session</code>.
     *
     * @see #setAttribute(String, Object)
     *
     * @param name the name of the attribute to remove
     * @post getAttribute(name) == null
     */
    public void removeAttribute(String name) {
        m_attrs.remove(name);
    }

    void dump() {
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        dump(pw);
        pw.flush();
        LOG.debug(sw.toString());
    }

    void dump(PrintWriter out) {
        out.println("keyodata");
        for (Iterator it = m_keyodata.entrySet().iterator(); it.hasNext();) {
            Map.Entry me = (Map.Entry) it.next();
            if (me.getValue() != null) {
                out.println(me.getKey());
            }
        }

        out.println("objodata");
        for (Iterator it = m_objodata.entrySet().iterator(); it.hasNext();) {
            Map.Entry me = (Map.Entry) it.next();
            out.println(str(me.getKey()));
        }
    }

    private static final ThreadLocal LEVEL = new ThreadLocal() {
        protected Object initialValue() {
            return new Integer(0);
        }
    };

    static final int getLevel() {
        Integer level = (Integer) LEVEL.get();
        return level.intValue();
    }

    static final void setLevel(int level) {
        if (level < 0) {
            level = 0;
        }
        LEVEL.set(new Integer(level));
    }

    static final void trace(String method, Object[] args) {
        if (LOG.isDebugEnabled()) {
            StringBuffer msg = new StringBuffer();
            int level = getLevel();
            for (int i = 0; i < level; i++) {
                msg.append("  ");
            }
            msg.append(method);
            msg.append("(");
            for (int i = 0; i < args.length; i++) {
                if (i > 0) {
                    msg.append(", ");
                }
                msg.append(str(args[i]));
            }
            msg.append(") {");
            LOG.debug(msg.toString());

            setLevel(level + 1);
        }
    }

    static final void untrace(String method) {
        untrace(method, null);
    }

    static final void untrace(String method, boolean result) {
        untrace(method, result ? Boolean.TRUE : Boolean.FALSE);
    }

    static final void untrace(String method, Object result) {
        if (LOG.isDebugEnabled()) {
            untrace(method, " -> " + result);
        }
    }

    static final void untrace(String method, String msg) {
        if (LOG.isDebugEnabled()) {
            int level = getLevel();
            setLevel(level - 1);

            StringBuffer buf = new StringBuffer();
            for (int i = 0; i < level - 1; i++) {
                buf.append("  ");
            }
            buf.append("} (");
            buf.append(method);
            buf.append(")");
            if (msg != null) {
                buf.append(msg);
            }
            LOG.debug(buf.toString());
        }
    }

    public static String str(Object obj) {
        if (obj == null) {
            return "null";
        } else {
            Class klass = obj.getClass();
            if (String.class.isAssignableFrom(klass) || Number.class.isAssignableFrom(klass)
                    || Event.class.isAssignableFrom(klass) || Property.class.isAssignableFrom(klass)
                    || ObjectType.class.isAssignableFrom(klass) || ObjectMap.class.isAssignableFrom(klass)) {
                return obj.toString();
            } else {
                return klass + "@" + Integer.toHexString(System.identityHashCode(obj));
            }
        }
    }

}