Package org.apache.isis.objectstore.jdo.datanucleus.persistence

Source Code of org.apache.isis.objectstore.jdo.datanucleus.persistence.FrameworkSynchronizer

/*
*  Licensed to the Apache Software Foundation (ASF) under one
*  or more contributor license agreements.  See the NOTICE file
*  distributed with this work for additional information
*  regarding copyright ownership.  The ASF licenses this file
*  to you under the Apache License, Version 2.0 (the
*  "License"); you may not use this file except in compliance
*  with the License.  You may obtain a copy of the License at
*
*        http://www.apache.org/licenses/LICENSE-2.0
*
*  Unless required by applicable law or agreed to in writing,
*  software distributed under the License is distributed on an
*  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
*  KIND, either express or implied.  See the License for the
*  specific language governing permissions and limitations
*  under the License.
*/
package org.apache.isis.objectstore.jdo.datanucleus.persistence;

import java.text.MessageFormat;
import java.util.concurrent.Callable;
import javax.jdo.JDOHelper;
import javax.jdo.PersistenceManager;
import javax.jdo.spi.PersistenceCapable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.isis.core.commons.authentication.AuthenticationSession;
import org.apache.isis.core.commons.exceptions.IsisException;
import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
import org.apache.isis.core.metamodel.adapter.ResolveState;
import org.apache.isis.core.metamodel.adapter.mgr.AdapterManager;
import org.apache.isis.core.metamodel.adapter.mgr.AdapterManager.ConcurrencyChecking;
import org.apache.isis.core.metamodel.adapter.oid.Oid;
import org.apache.isis.core.metamodel.adapter.oid.RootOid;
import org.apache.isis.core.metamodel.adapter.version.ConcurrencyException;
import org.apache.isis.core.metamodel.adapter.version.Version;
import org.apache.isis.core.metamodel.facets.object.callbacks.*;
import org.apache.isis.core.runtime.persistence.PersistorUtil;
import org.apache.isis.core.runtime.system.context.IsisContext;
import org.apache.isis.core.runtime.system.persistence.OidGenerator;
import org.apache.isis.core.runtime.system.persistence.PersistenceSession;
import org.apache.isis.core.runtime.system.transaction.IsisTransaction;
import org.apache.isis.objectstore.jdo.datanucleus.DataNucleusObjectStore;

public class FrameworkSynchronizer {

    private static final Logger LOG = LoggerFactory.getLogger(FrameworkSynchronizer.class);

    /**
     * Categorises where called from.
     *
     * <p>
     * Just used for logging.
     */
    public enum CalledFrom {
        EVENT_LOAD,
        EVENT_PRESTORE,
        EVENT_POSTSTORE,
        EVENT_PREDIRTY,
        EVENT_POSTDIRTY,
        OS_QUERY,
        OS_RESOLVE,
        OS_LAZILYLOADED,
        EVENT_PREDELETE,
        EVENT_POSTDELETE
    }


    public void postLoadProcessingFor(final PersistenceCapable pojo, CalledFrom calledFrom) {

        withLogging(pojo, new Runnable() {
            @Override
            public void run() {
                final PersistenceCapable pc = pojo;
               
                // need to do eagerly, because (if a viewModel then) a
                // viewModel's #viewModelMemento might need to use services
                getPersistenceSession().getServicesInjector().injectServicesInto(pojo);
               
                final Version datastoreVersion = getVersionIfAny(pc);
               
                final RootOid originalOid ;
                ObjectAdapter adapter = getAdapterManager().getAdapterFor(pojo);
                if(adapter != null) {
                    ensureRootObject(pojo);
                    originalOid = (RootOid) adapter.getOid();

                    final Version originalVersion = adapter.getVersion();

                    // sync the pojo held by the adapter with that just loaded
                    getPersistenceSession().remapRecreatedPojo(adapter, pojo);
                   
                    // since there was already an adapter, do concurrency check
                    // (but don't set abort cause if checking is suppressed through thread-local)
                    final RootOid thisOid = originalOid;
                    final Version thisVersion = originalVersion;
                    final Version otherVersion = datastoreVersion;
                   
                    if(thisVersion != null &&
                       otherVersion != null &&
                       thisVersion.different(otherVersion)) {

                        if(ConcurrencyChecking.isCurrentlyEnabled()) {
                            LOG.info("concurrency conflict detected on " + thisOid + " (" + otherVersion + ")");
                            final String currentUser = getAuthenticationSession().getUserName();
                            final ConcurrencyException abortCause = new ConcurrencyException(currentUser, thisOid, thisVersion, otherVersion);
                            getCurrentTransaction().setAbortCause(abortCause);

                        } else {
                            LOG.warn("concurrency conflict detected but suppressed, on " + thisOid + " (" + otherVersion + ")");
                        }
                    }
                } else {
                    final OidGenerator oidGenerator = getOidGenerator();
                    originalOid = oidGenerator.createPersistentOrViewModelOid(pojo, null);
                   
                    // it appears to be possible that there is already an adapter for this Oid,
                    // ie from ObjectStore#resolveImmediately()
                    adapter = getAdapterManager().getAdapterFor(originalOid);
                    if(adapter != null) {
                        getPersistenceSession().remapRecreatedPojo(adapter, pojo);
                    } else {
                        adapter = getPersistenceSession().mapRecreatedPojo(originalOid, pojo);
                        CallbackFacet.Util.callCallback(adapter, LoadedCallbackFacet.class);
                    }
                }
                if(!adapter.isResolved()) {
                    PersistorUtil.startResolving(adapter);
                    PersistorUtil.toEndState(adapter);
                }
                adapter.setVersion(datastoreVersion);
                if(pojo.jdoIsDeleted()) {
                    adapter.changeState(ResolveState.DESTROYED);
                }

                ensureFrameworksInAgreement(pojo);
            }
        }, calledFrom);
    }


    /**
     * Called either when an entity is initially persisted, or when an entity is updated; fires the appropriate
     * lifecycle callback.
     *
     * <p>
     * The implementation therefore uses Isis' {@link org.apache.isis.core.metamodel.adapter.oid.Oid#isTransient() oid}
     * to determine which callback to fire.
     */
    public void preStoreProcessingFor(final PersistenceCapable pojo, final CalledFrom calledFrom) {
        withLogging(pojo, new Runnable() {
            @Override
            public void run() {
                final ObjectAdapter adapter = getAdapterManager().getAdapterFor(pojo);
                if(adapter == null) {
                    // not expected.
                    return;
                }

                final RootOid isisOid = (RootOid) adapter.getOid();
                if (isisOid.isTransient()) {
                    // persisting
                    // previously this was performed in the DataNucleusSimplePersistAlgorithm.
                    CallbackFacet.Util.callCallback(adapter, PersistingCallbackFacet.class);
                } else {
                    // updating

                    // don't call here, already called in preDirty.

                    // CallbackFacet.Util.callCallback(adapter, UpdatingCallbackFacet.class);
                }

            }
        }, calledFrom);
    }

    /**
     * Called either when an entity is initially persisted, or when an entity is updated; fires the appropriate lifecycle callback
     *
     * <p>
     * The implementation therefore uses Isis' {@link org.apache.isis.core.metamodel.adapter.oid.Oid#isTransient() oid}
     * to determine which callback to fire.
     */
    public void postStoreProcessingFor(final PersistenceCapable pojo, CalledFrom calledFrom) {
        withLogging(pojo, new Runnable() {
            @Override
            public void run() {
                ensureRootObject(pojo);

                // assert is persistent
                if(!pojo.jdoIsPersistent()) {
                    throw new IllegalStateException("Pojo JDO state is not persistent! pojo dnOid: " + JDOHelper.getObjectId(pojo));
                }

                final ObjectAdapter adapter = getAdapterManager().getAdapterFor(pojo);
                final RootOid isisOid = (RootOid) adapter.getOid();


                if (isisOid.isTransient()) {
                    // persisting
                    final RootOid persistentOid = getOidGenerator().createPersistentOrViewModelOid(pojo, isisOid);

                    getPersistenceSession().remapAsPersistent(adapter, persistentOid);

                    CallbackFacet.Util.callCallback(adapter, PersistedCallbackFacet.class);

                    final IsisTransaction transaction = getCurrentTransaction();
                    transaction.enlistCreated(adapter);
                } else {
                    // updating;
                    // the callback and transaction.enlist are done in the preDirty callback
                    // (can't be done here, as the enlist requires to capture the 'before' values)
                    CallbackFacet.Util.callCallback(adapter, UpdatedCallbackFacet.class);
                }

                Utils.clearDirtyFor(adapter);

                Version versionIfAny = getVersionIfAny(pojo);
                adapter.setVersion(versionIfAny);

                ensureFrameworksInAgreement(pojo);
            }
        }, calledFrom);
    }

    public void preDirtyProcessingFor(final PersistenceCapable pojo, CalledFrom calledFrom) {
        withLogging(pojo, new Runnable() {
            @Override
            public void run() {
                ObjectAdapter adapter = getAdapterManager().getAdapterFor(pojo);
                if (adapter == null) {
                    // seen this happen in the case when a parent entity (LeaseItem) has a collection of children
                    // objects (LeaseTerm) for which we haven't had a loaded callback fired and so are not yet
                    // mapped.
                   
                    // it seems reasonable in this case to simply map into Isis here ("just-in-time"); presumably
                    // DN would not be calling this callback if the pojo was not persistent.
                   
                    adapter = lazilyLoaded(pojo, CalledFrom.EVENT_PREDIRTY);
                    if(adapter == null) {
                        throw new RuntimeException("DN could not find objectId for pojo (unexpected) and so could not map into Isis; pojo=[" +  pojo + "]");
                    }
                }
                if(adapter.isTransient()) {
                    // seen this happen in the case when there's a 1<->m bidirectional collection, and we're
                    // attaching the child object, which is being persisted by DN as a result of persistence-by-reachability,
                    // and it "helpfully" sets up the parent attribute on the child, causing this callback to fire.
                    //
                    // however, at the same time, Isis has only queued up a CreateObjectCommand for the transient object, but it
                    // hasn't yet executed, so thinks that the adapter is still transient.
                    return;
                }

                CallbackFacet.Util.callCallback(adapter, UpdatingCallbackFacet.class);

                final IsisTransaction transaction = getCurrentTransaction();
                transaction.enlistUpdating(adapter);

                ensureRootObject(pojo);
                ensureFrameworksInAgreement(pojo);
            }
        }, calledFrom);
    }


    public ObjectAdapter lazilyLoaded(final PersistenceCapable pojo, CalledFrom calledFrom) {
        return withLogging(pojo, new Callable<ObjectAdapter>() {
            @Override
            public ObjectAdapter call() {
                if(getJdoPersistenceManager().getObjectId(pojo) == null) {
                    return null;
                }
                final RootOid oid = getPersistenceSession().getOidGenerator().createPersistentOrViewModelOid(pojo, null);
                final ObjectAdapter adapter = getPersistenceSession().mapRecreatedPojo(oid, pojo);
                return adapter;
            }
        }, calledFrom);
    }

   
    public void preDeleteProcessingFor(final PersistenceCapable pojo, final CalledFrom calledFrom) {
        withLogging(pojo, new Runnable() {
            @Override
            public void run() {
                ObjectAdapter adapter = getAdapterManager().adapterFor(pojo);
               
                final IsisTransaction transaction = getCurrentTransaction();
                transaction.enlistDeleting(adapter);

                CallbackFacet.Util.callCallback(adapter, RemovingCallbackFacet.class);
                ensureFrameworksInAgreement(pojo);
            }
        }, calledFrom);
       
    }

    public void postDeleteProcessingFor(final PersistenceCapable pojo, final CalledFrom calledFrom) {
        withLogging(pojo, new Runnable() {
            @Override
            public void run() {
                ObjectAdapter adapter = getAdapterManager().getAdapterFor(pojo);
                if(adapter == null) {
                    return;
                }
                if(!adapter.isDestroyed()) {
                    adapter.changeState(ResolveState.DESTROYED);
                }


                // previously we called the removed callback (if any).
                // however, this is almost certainly incorrect, because DN will not allow us
                // to "touch" the pojo once deleted.
                //
                // CallbackFacet.Util.callCallback(adapter, RemovedCallbackFacet.class);


                // this is probably still ok to do, however.
                ensureFrameworksInAgreement(pojo);
            }
        }, calledFrom);
       
    }
   
    // /////////////////////////////////////////////////////////
    // Helpers
    // /////////////////////////////////////////////////////////
   
    private <T> T withLogging(PersistenceCapable pojo, Callable<T> runnable, CalledFrom calledFrom) {
        if (LOG.isDebugEnabled()) {
            LOG.debug(logString(calledFrom, LoggingLocation.ENTRY, pojo));
        }
        try {
            return runnable.call();
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            if (LOG.isDebugEnabled()) {
                LOG.debug(logString(calledFrom, LoggingLocation.EXIT, pojo));
            }
        }
    }
   
    private void withLogging(PersistenceCapable pojo, final Runnable runnable, CalledFrom calledFrom) {
        withLogging(pojo, new Callable<Void>() {

            @Override
            public Void call() throws Exception {
                runnable.run();
                return null;
            }
           
        }, calledFrom);
    }
   
    private String logString(CalledFrom calledFrom, LoggingLocation location, PersistenceCapable pojo) {
        final AdapterManager adapterManager = getAdapterManager();
        final ObjectAdapter adapter = adapterManager.getAdapterFor(pojo);
        // initial spaces just to look better in log when wrapped by IsisLifecycleListener...
        return calledFrom.name() + " " + location.prefix + " oid=" + (adapter !=null? adapter.getOid(): "(null)") + " ,pojo " + pojo;
    }


    // /////////////////////////////////////////////////////////
    // More Helpers...
    // /////////////////////////////////////////////////////////

    void ensureFrameworksInAgreement(final PersistenceCapable pojo) {
        final ObjectAdapter adapter = getAdapterManager().getAdapterFor(pojo);
        final Oid oid = adapter.getOid();

        if(!pojo.jdoIsPersistent()) {
            // make sure the adapter is transient
            if (!adapter.getResolveState().isTransient()) {
                throw new IsisException(MessageFormat.format("adapter oid={0} has resolve state in invalid state; should be transient but is {1}; pojo: {2}", oid, adapter.getResolveState(), pojo));
            }

            // make sure the oid is transient
            if (!oid.isTransient()) {
                throw new IsisException(MessageFormat.format("adapter oid={0} has oid in invalid state; should be transient; pojo: {1}", oid, pojo));
            }

        } else if(pojo.jdoIsDeleted()) {
           
            // make sure the adapter is destroyed
            if (!adapter.getResolveState().isDestroyed()) {
                throw new IsisException(MessageFormat.format("adapter oid={0} has resolve state in invalid state; should be destroyed but is {1}; pojo: {2}", oid, adapter.getResolveState(), pojo));
            }
           
        } else {
           
           
           
            // make sure the adapter is persistent
            if (!adapter.getResolveState().representsPersistent()) {
                throw new IsisException(MessageFormat.format("adapter oid={0} has resolve state in invalid state; should be in a persistent but is {1}; pojo: {2}", oid, adapter.getResolveState(), pojo));
            }

            // make sure the oid is persistent
            if (oid.isTransient()) {
                throw new IsisException(MessageFormat.format("adapter oid={0} has oid in invalid state; should be persistent; pojo: {1}", oid, pojo));
            }
        }
    }

    // make sure the entity is known to Isis and is a root
    // TODO: will probably need to handle aggregated entities at some point...
    void ensureRootObject(final PersistenceCapable pojo) {
        final Oid oid = getAdapterManager().adapterFor(pojo).getOid();
        if (!(oid instanceof RootOid)) {
            throw new IsisException(MessageFormat.format("Not a RootOid: oid={0}, for {1}", oid, pojo));
        }
    }

    private Version getVersionIfAny(final PersistenceCapable pojo) {
        return Utils.getVersionIfAny(pojo, getAuthenticationSession());
    }

    @SuppressWarnings("unused")
    private void ensureObjectNotLoaded(final PersistenceCapable pojo) {
        final ObjectAdapter adapter = getAdapterManager().getAdapterFor(pojo);
        if(adapter != null) {
            final Oid oid = adapter.getOid();
            throw new IsisException(MessageFormat.format("Object is already mapped in Isis: oid={0}, for {1}", oid, pojo));
        }
    }


   
    // /////////////////////////////////////////////////////////
    // Dependencies (from context)
    // /////////////////////////////////////////////////////////

    protected AdapterManager getAdapterManager() {
        return getPersistenceSession().getAdapterManager();
    }

    protected OidGenerator getOidGenerator() {
        return getPersistenceSession().getOidGenerator();
    }

    protected PersistenceSession getPersistenceSession() {
        return IsisContext.getPersistenceSession();
    }

    protected AuthenticationSession getAuthenticationSession() {
        return IsisContext.getAuthenticationSession();
    }

    protected IsisTransaction getCurrentTransaction() {
        return IsisContext.getCurrentTransaction();
    }

    protected PersistenceManager getJdoPersistenceManager() {
        final DataNucleusObjectStore objectStore = getObjectStore();
        return objectStore.getPersistenceManager();
    }

    protected DataNucleusObjectStore getObjectStore() {
        return (DataNucleusObjectStore) IsisContext.getPersistenceSession().getObjectStore();
    }




}
TOP

Related Classes of org.apache.isis.objectstore.jdo.datanucleus.persistence.FrameworkSynchronizer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.