com.db4o.drs.hibernate.impl.HibernateReplicationProvider.java Source code

Java tutorial

Introduction

Here is the source code for com.db4o.drs.hibernate.impl.HibernateReplicationProvider.java

Source

/* Copyright (C) 2004 - 2008  Versant Inc.  http://www.db4o.com
    
This file is part of the db4o open source object database.
    
db4o is free software; you can redistribute it and/or modify it under
the terms of version 2 of the GNU General Public License as published
by the Free Software Foundation and as clarified by db4objects' GPL 
interpretation policy, available at
http://www.db4o.com/about/company/legalpolicies/gplinterpretation/
Alternatively you can write to db4objects, Inc., 1900 S Norfolk Street,
Suite 350, San Mateo, CA 94403, USA.
    
db4o 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 General Public License
for more details.
    
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
59 Temple Place - Suite 330, Boston, MA  02111-1307, USA. */
package com.db4o.drs.hibernate.impl;

import java.io.*;
import java.sql.*;
import java.util.*;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Set;

import org.hibernate.*;
import org.hibernate.cfg.*;
import org.hibernate.criterion.*;
import org.hibernate.event.*;
import org.hibernate.mapping.*;

import com.db4o.*;
import com.db4o.drs.foundation.*;
import com.db4o.drs.hibernate.metadata.*;
import com.db4o.drs.inside.*;
import com.db4o.ext.*;
import com.db4o.foundation.*;

public final class HibernateReplicationProvider implements TestableReplicationProviderInside {

    private static final boolean SHOW_SQL = false;

    private boolean _simpleObjectContainerCommitCalled = true;

    private Configuration _cfg;

    private final String _name;

    private Session _session;

    private SessionFactory _sessionFactory;

    private Transaction _transaction;

    private ObjectReferenceMap _replicationReferences = new ObjectReferenceMap();

    private boolean _alive = false;

    private ObjectLifeCycleEventsListener _listener = new MyObjectLifeCycleEventsListener();

    /**
     * The Signature of the peer in the current Transaction.
     */
    private PeerSignature _peerSignature;

    private MyFlushEventListener _flushEventListener = new MyFlushEventListener();

    /**
     * The Record of {@link #_peerSignature}.
     */
    private Record _replicationRecord;

    private CollectionHandler _collectionHandler;

    /**
     * Objects which meta data not yet updated.
     */
    private Set<ReplicationReference> _dirtyRefs = new HashSet();

    private boolean _inReplication = false;

    private final TimeStampIdGenerator _generator;

    private long _commitTimestamp;

    public HibernateReplicationProvider(Configuration cfg) {
        this(cfg, null);
    }

    public HibernateReplicationProvider(Configuration cfg, String name) {
        _name = name;
        _cfg = ReplicationConfiguration.decorate(cfg);

        if (SHOW_SQL) {
            _cfg.setProperty("hibernate.show_sql", "true");
        }

        new TablesCreatorImpl(_cfg).validateOrCreate();

        _cfg.setInterceptor(EmptyInterceptor.INSTANCE);

        EventListeners el = _cfg.getEventListeners();
        el.setFlushEventListeners(
                (FlushEventListener[]) Util.add(el.getFlushEventListeners(), _flushEventListener));

        _listener.configure(cfg);

        _sessionFactory = getConfiguration().buildSessionFactory();
        _session = _sessionFactory.openSession();
        _session.setFlushMode(FlushMode.COMMIT);
        _transaction = _session.beginTransaction();

        _listener.install(getSession(), cfg);

        _generator = GeneratorMap.get(_session);

        _alive = true;
    }

    public final void activate(Object object) {
        Hibernate.initialize(object);
    }

    public final void clearAllReferences() {
        _replicationReferences.clear();
    }

    public final void commit() {
        final Session session = getSession();
        session.flush();
        _transaction.commit();
        _transaction = session.beginTransaction();
        setCommitted(true);
    }

    public final synchronized void commitReplicationTransaction() {
        ensureReplicationActive();
        storeReplicationRecord();
        getSession().flush();
        commit();
        _dirtyRefs.clear();
        _flushEventListener.useDefaultTransactionTimestamp();
        _inReplication = false;
    }

    public final void delete(Object obj) {
        getSession().delete(obj);
        setCommitted(false);
    }

    public final void deleteAllInstances(Class clazz) {
        ensureReplicationInActive();
        List col = getSession().createCriteria(clazz).list();
        for (Object o : col)
            delete(o);
    }

    public final synchronized void destroy() {
        if (!_alive)
            throw new RuntimeException("Provider has already been destroyed.");

        _alive = false;

        try {
            _session.connection().createStatement().execute("SHUTDOWN IMMEDIATELY");
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        _session.close();
        _sessionFactory.close();

        _transaction = null;
        _session = null;
        _sessionFactory = null;

        _dirtyRefs = null;

        _replicationReferences = null;

        EventListeners eventListeners = getConfiguration().getEventListeners();
        FlushEventListener[] o1 = eventListeners.getFlushEventListeners();
        FlushEventListener[] r1 = (FlushEventListener[]) Util.removeElement(o1, _flushEventListener);
        if ((o1.length - r1.length) != 1)
            throw new RuntimeException("can't remove");

        eventListeners.setFlushEventListeners(r1);
        _flushEventListener = null;

        _listener.destroy();
        _listener = null;

        _cfg = null;
    }

    public final Configuration getConfiguration() {
        return _cfg;
    }

    public long getLastReplicationVersion() {
        ensureReplicationActive();

        return _replicationRecord.getTime();
    }

    public final String getName() {
        return _name;
    }

    public final Session getSession() {
        return _session;
    }

    public final ReadonlyReplicationProviderSignature getSignature() {
        return Util.genMySignature(getSession());
    }

    public final ObjectSet getStoredObjects(Class aClass) {
        if (shouldIgnore(aClass))
            throw new IllegalArgumentException("Hibernate does not query by Collection or Enums");

        getSession().flush();

        return new ObjectSetCollectionFacade(getSession().createCriteria(aClass).list());
    }

    protected boolean shouldIgnore(Class aClass) {
        return _collectionHandler.canHandleClass(aClass) || aClass.isEnum();
    }

    protected boolean shouldIgnore(Object obj) {
        return _collectionHandler.canHandle(obj) || obj.getClass().isEnum();
    }

    public final ObjectSet objectsChangedSinceLastReplication() {
        ensureReplicationActive();

        getSession().flush();

        Set<PersistentClass> mappedClasses = new HashSet<PersistentClass>();

        Iterator classMappings = getConfiguration().getClassMappings();
        while (classMappings.hasNext()) {
            PersistentClass persistentClass = (PersistentClass) classMappings.next();
            Class claxx = persistentClass.getMappedClass();

            if (Util.isAssignableFromInternalObject(claxx))
                continue;

            mappedClasses.add(persistentClass);
        }

        Set out = new HashSet();
        for (PersistentClass persistentClass : mappedClasses)
            out.addAll(getChangedObjectsSinceLastReplication(persistentClass));

        return new ObjectSetCollectionFacade(out);
    }

    public final ObjectSet objectsChangedSinceLastReplication(Class clazz) {
        ensureReplicationActive();
        getSession().flush();

        PersistentClass persistentClass = getConfiguration().getClassMapping(clazz.getName());
        return new ObjectSetCollectionFacade(getChangedObjectsSinceLastReplication(persistentClass));
    }

    public final ReplicationReference produceReference(Object obj, Object referencingObj, String fieldName) {
        ensureReplicationActive();

        ReplicationReference existing = _replicationReferences.get(obj);
        if (existing != null)
            return existing;

        if (shouldIgnore(obj))
            return null;
        else
            return produceObjectReference(obj);
    }

    public final ReplicationReference produceReferenceByUUID(final DrsUUID uuid, Class hint) {
        ensureReplicationActive();

        if (uuid == null)
            throw new IllegalArgumentException("uuid cannot be null");
        if (hint == null)
            throw new IllegalArgumentException("hint cannot be null");

        getSession().flush();

        ReplicationReference exist = _replicationReferences.getByUUID(uuid);
        if (exist != null)
            return exist;

        if (shouldIgnore(hint)) {
            return null;
        } else {
            return produceObjectReferenceByUUID(uuid);
        }
    }

    public final ReplicationReference referenceNewObject(Object obj, ReplicationReference counterpartReference,
            ReplicationReference referencingObjCounterPartRef, String fieldName) {
        ensureReplicationActive();

        if (obj == null)
            throw new NullPointerException("obj is null");
        if (counterpartReference == null)
            throw new NullPointerException("counterpartReference is null");

        if (shouldIgnore(obj))
            return null;
        else {
            DrsUUID uuid = counterpartReference.uuid();
            long version = counterpartReference.version();
            ReplicationReferenceImpl replicationReference = new ReplicationReferenceImpl(obj, uuid, version);
            _replicationReferences.put(replicationReference);
            return replicationReference;
        }
    }

    public void replicateDeletion(DrsUUID uuid) {
        ObjectReference ref = Util.getByUUID(getSession(), translate(uuid));

        if (ref == null)
            return;

        Object loaded = getSession().get(ref.getClassName(), ref.getTypedId());
        if (loaded == null)
            return;

        getSession().delete(loaded);
    }

    public final synchronized void rollbackReplication() {
        ensureReplicationActive();

        _transaction.rollback();
        clearSession();

        _transaction = getSession().beginTransaction();
        clearAllReferences();
        _dirtyRefs.clear();
        _inReplication = false;
    }

    public final void startReplicationTransaction(ReadonlyReplicationProviderSignature aPeerSignature) {
        ensureReplicationInActive();
        ensureCommitted();

        _transaction.commit();
        _transaction = getSession().beginTransaction();
        clearSession();
        clearAllReferences();

        byte[] peerSigBytes = aPeerSignature.getSignature();

        if (Arrays.equals(peerSigBytes, getSignature().getSignature()))
            throw new RuntimeException("peerSigBytes must not equal to my own sig");

        final List exisitingSigs = getSession().createCriteria(PeerSignature.class)
                .add(Restrictions.eq(ProviderSignature.Fields.SIG, peerSigBytes)).list();

        if (exisitingSigs.size() == 1) {
            _peerSignature = (PeerSignature) exisitingSigs.get(0);
            List existingRecords = getSession().createCriteria(Record.class)
                    .createCriteria(Record.Fields.PEER_SIGNATURE)
                    .add(Restrictions.eq(ProviderSignature.Fields.ID, _peerSignature.getId())).list();

            if (existingRecords.size() == 1) //This peer X replicated with this provider before 
                _replicationRecord = (Record) existingRecords.get(0);
            else if (existingRecords.size() == 0) //
                /*
                 * This peer X never replicated with this provider, 
                 * but objects from this peer X was replicated thru another provider, 
                 * .e.g. X->another provider->this provider
                 */
                createNewRecord();
            else
                throw new RuntimeException("This state is never reachable.");
        } else if (exisitingSigs.size() == 0) {
            _peerSignature = new PeerSignature(peerSigBytes);
            getSession().save(_peerSignature);
            getSession().flush();

            createNewRecord();
        } else
            throw new RuntimeException("result size = " + exisitingSigs.size() + ". It should be either 1 or 0");

        _generator.setMinimumNext(Util.getMaxReplicationRecordVersion(_session));

        _commitTimestamp = _flushEventListener.generateTransactionTimestamp();

        _inReplication = true;
    }

    private void createNewRecord() {
        _replicationRecord = new Record();
        _replicationRecord.setPeerSignature(_peerSignature);
    }

    public final void storeNew(Object object) {
        ensureReplicationInActive();
        Session s = getSession();
        s.save(object);
        s.flush();
        setCommitted(false);
    }

    public final void storeReplica(Object entity) {
        ensureReplicationActive();

        getSession().flush();

        //Hibernate does not treat Collection as 1st class object, so storing a Collection is no-op
        if (shouldIgnore(entity))
            return;

        ReplicationReference ref = _replicationReferences.get(entity);
        if (ref == null)
            throw new RuntimeException("Reference should always be available before storeReplica");

        final Session s = getSession();
        if (s.contains(entity)) {
            s.update(entity);
        } else {
            s.save(entity);
        }

        _dirtyRefs.add(ref);

        getSession().flush();
    }

    public boolean supportsHybridCollection() {
        return false;
    }

    public boolean supportsMultiDimensionalArrays() {
        return false;
    }

    public boolean supportsRollback() {
        return true;
    }

    private void storeReplicationRecord() {
        _replicationRecord.setTime(_commitTimestamp);
        getSession().saveOrUpdate(_replicationRecord);
        getSession().flush();
    }

    public final String toString() {
        return _name;
    }

    public final void update(Object obj) {
        ensureReplicationInActive();
        if (!shouldIgnore(obj)) {
            getSession().flush();
            getSession().update(obj);
            getSession().flush();
        }
        setCommitted(false);
    }

    public void updateCounterpart(Object entity) {
        ensureReplicationActive();

        getSession().flush();

        //Hibernate does not treat Collection as 1st class object, so storing a Collection is no-op
        if (shouldIgnore(entity))
            return;

        ReplicationReference ref = _replicationReferences.get(entity);
        if (ref == null)
            throw new RuntimeException("Reference should always be available before storeReplica");

        getSession().update(entity);

        _dirtyRefs.add(ref);

        getSession().flush();
    }

    public final void visitCachedReferences(Visitor4 visitor) {
        ensureReplicationActive();
        _replicationReferences.visitEntries(visitor);
    }

    public final boolean wasModifiedSinceLastReplication(ReplicationReference reference) {
        ensureReplicationActive();
        return reference.version() > getLastReplicationVersion();
    }

    Uuid translate(DrsUUID du) {
        Uuid uuid = new Uuid();
        uuid.setCreated(du.getLongPart());
        uuid.setProvider(produceProviderSignature(du.getSignaturePart()));
        return uuid;
    }

    private void clearSession() {
        getSession().clear();
    }

    private void ensureAlive() {
        if (!_alive)
            throw new UnsupportedOperationException("This provider is dead because #destroy() is called");
    }

    private void ensureCommitted() {
        if (!_simpleObjectContainerCommitCalled)
            throw new RuntimeException("Please call commit() first");
    }

    private void ensureReplicationActive() {
        ensureAlive();
        if (!isReplicationActive())
            throw new UnsupportedOperationException("Replication transaction IS NOT active");
    }

    private void ensureReplicationInActive() {
        ensureAlive();
        if (isReplicationActive())
            throw new UnsupportedOperationException("Replication transaction IS active");
    }

    private Collection getChangedObjectsSinceLastReplication(PersistentClass persistentClass) {
        Criteria criteria = getSession().createCriteria(ObjectReference.class);
        long lastReplicationVersion = getLastReplicationVersion();
        criteria.add(Restrictions.gt(ObjectReference.Fields.VERSION, lastReplicationVersion));
        criteria.add(Restrictions.lt(ObjectReference.Fields.VERSION, _commitTimestamp));
        Disjunction disjunction = Restrictions.disjunction();

        List<String> names = new ArrayList<String>();
        names.add(persistentClass.getClassName());
        if (persistentClass.hasSubclasses()) {
            final Iterator it = persistentClass.getSubclassClosureIterator();
            while (it.hasNext()) {
                PersistentClass subC = (PersistentClass) it.next();
                names.add(subC.getClassName());
            }
        }

        for (String s : names)
            disjunction.add(Restrictions.eq(ObjectReference.Fields.CLASS_NAME, s));

        criteria.add(disjunction);

        Set out = new HashSet();
        for (Object o : criteria.list()) {
            ObjectReference ref = (ObjectReference) o;
            out.add(getSession().load(persistentClass.getRootClass().getClassName(), ref.getTypedId()));
        }
        return out;
    }

    /**
     * Get the ProviderSignature of the given signaturePart.
     * If not found, generate a ProviderSignature for the given signaturePart on the fly.
     * @param signaturePart
     * @return
     */
    private ProviderSignature produceProviderSignature(byte[] signaturePart) {
        final List exisitingSigs = getSession().createCriteria(ProviderSignature.class)
                .add(Restrictions.eq(ProviderSignature.Fields.SIG, signaturePart)).list();
        if (exisitingSigs.size() == 1) {
            return (ProviderSignature) exisitingSigs.get(0);
        } else if (exisitingSigs.size() == 0) {
            ProviderSignature alienProvider = new PeerSignature(signaturePart);
            getSession().save(alienProvider);
            getSession().flush();
            return alienProvider;
        } else {
            throw new RuntimeException(
                    "This condition should never be reachable.It is impossible to have more than two records for one sig.");
        }
    }

    private boolean isReplicationActive() {
        return _inReplication;
    }

    private ReplicationReference produceObjectReference(Object obj) {
        if (!getSession().contains(obj))
            return null;
        final ObjectReference ref = objectReferenceFor(obj);

        if (ref == null)
            throw new RuntimeException("ObjectReference must exist for " + obj);

        Uuid uuid = ref.getUuid();

        ReplicationReferenceImpl replicationReference = new ReplicationReferenceImpl(obj,
                new DrsUUIDImpl(new Db4oUUID(uuid.getCreated(), uuid.getProvider().getSignature())),
                ref.getModified());
        _replicationReferences.put(replicationReference);
        return replicationReference;
    }

    private ObjectReference objectReferenceFor(Object obj) {
        long id = Util.castAsLong(getSession().getIdentifier(obj));
        final ObjectReference ref = Util.getObjectReferenceById(getSession(), obj.getClass().getName(), id);
        return ref;
    }

    private ReplicationReference produceObjectReferenceByUUID(DrsUUID uuid) {
        ObjectReference of = Util.getByUUID(getSession(), translate(uuid));
        if (of == null)
            return null;
        else {
            Object obj = getSession().load(of.getClassName(), of.getTypedId());

            ReplicationReferenceImpl replicationReference = new ReplicationReferenceImpl(obj, uuid,
                    of.getModified());
            _replicationReferences.put(replicationReference);
            return replicationReference;
        }
    }

    private void setCommitted(boolean b) {
        _simpleObjectContainerCommitCalled = b;
    }

    private final class MyFlushEventListener implements FlushEventListener {

        private long _transactionTimestamp;

        public long generateTransactionTimestamp() {
            TimeStampIdGenerator generator = GeneratorMap.get(getSession());
            _transactionTimestamp = generator.generate();
            return _transactionTimestamp;
        }

        public void transactionTimestamp(long newTimestamp) {
            _transactionTimestamp = newTimestamp;
        }

        public void useDefaultTransactionTimestamp() {
            _transactionTimestamp = 0;
        }

        public final void onFlush(FlushEvent event) throws HibernateException {
            if (!isReplicationActive())
                return;

            for (ReplicationReference ref : _dirtyRefs) {
                _dirtyRefs.remove(ref);

                final Object obj = ref.object();
                long id = Util.castAsLong(getSession().getIdentifier(obj));

                Uuid uuid = translate(ref.uuid());

                final ObjectReference exist = Util.getByUUID(getSession(), uuid);
                if (exist == null) {
                    ObjectReference tmp = new ObjectReference();
                    tmp.setClassName(obj.getClass().getName());
                    tmp.setTypedId(Util.castAsLong(getSession().getIdentifier(obj)));
                    tmp.setUuid(uuid);
                    tmp.setModified(_transactionTimestamp);
                    try {
                        getSession().save(tmp);
                    } catch (HibernateException e) {
                        throw new RuntimeException(e);
                    }
                } else {
                    if (!exist.getClassName().equals(obj.getClass().getName()))
                        throw new RuntimeException("Same classname expected");

                    if (exist.getTypedId() != id) //deletion rollback case, id may change
                        exist.setTypedId(id);

                    exist.setModified(_transactionTimestamp);
                    getSession().update(exist);
                }
            }
        }
    }

    private final class MyObjectLifeCycleEventsListener extends ObjectLifeCycleEventsListenerImpl {
        public final void onPostInsert(PostInsertEvent event) {
            if (!isReplicationActive())
                super.onPostInsert(event);
        }

        protected final void ObjectUpdated(Object obj, Serializable id) {
            if (!isReplicationActive())
                super.ObjectUpdated(obj, Util.castAsLong(id));
        }
    }

    public boolean isProviderSpecific(Object original) {
        return original.getClass().getName().startsWith("org.hibernate.collection.");
    }

    public void replicationReflector(ReplicationReflector replicationReflector) {
        _collectionHandler = new CollectionHandlerImpl(replicationReflector);
    }

    public ReplicationReference produceReference(Object obj) {
        return produceReference(obj, null, null);
    }

    public Object replaceIfSpecific(Object value) {
        if (value instanceof Timestamp) {
            return new Date(((Timestamp) value).getTime());
        }
        return value;
    }

    public boolean isSecondClassObject(Object obj) {
        return obj instanceof Timestamp;
    }

    public long objectVersion(Object obj) {
        ObjectReference ref = objectReferenceFor(obj);
        if (ref == null) {
            return 0;
        }
        return ref.getModified();
    }

    public long creationTime(Object obj) {
        ObjectReference ref = objectReferenceFor(obj);
        if (ref == null) {
            return 0;
        }
        return ref.getUuid().getCreated();
    }

    public void ensureVersionsAreGenerated() {
    }

    public TimeStamps timeStamps() {
        return new TimeStamps(_replicationRecord.getTime(), _commitTimestamp);
    }

    public void waitForPreviousCommits() {
        // do nothing
    }

    public void syncCommitTimestamp(long syncedTimeStamp) {
        if (syncedTimeStamp <= _commitTimestamp) {
            return;
        }
        _commitTimestamp = syncedTimeStamp;
        _generator.setMinimumNext(syncedTimeStamp + 1);
        _flushEventListener.transactionTimestamp(_commitTimestamp);
    }

}