# ConcurrentModificationCheckConcern.java

    /*
     * Copyright (c) 2008, Rickard Öberg. All Rights Reserved.
     *
     * Licensed 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.qi4j.spi.entitystore;

    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.locks.ReentrantReadWriteLock;
    import org.qi4j.api.Qi4j;
    import org.qi4j.api.concern.ConcernOf;
    import org.qi4j.api.entity.EntityDescriptor;
    import org.qi4j.api.entity.EntityReference;
    import org.qi4j.api.injection.scope.Structure;
    import org.qi4j.api.injection.scope.This;
    import org.qi4j.api.usecase.Usecase;
    import org.qi4j.spi.entity.EntityState;
    import org.qi4j.spi.module.ModuleSpi;

    /**
     * Concern that helps EntityStores do concurrent modification checks.
     * <p>
     * It caches the versions of state that it loads, and forgets them when
     * the state is committed. For normal operation this means that it does
     * not have to go down to the underlying store to get the current version.
     * Whenever there is a concurrent modification the store will most likely
     * have to check with the underlying store what the current version is.
     * </p>
     */
    public abstract class ConcurrentModificationCheckConcern
        extends ConcernOf<EntityStore>
        implements EntityStore
    {
        @This
        private EntityStateVersions versions;

        @Structure
        private Qi4j api;

        @Override
        public EntityStoreUnitOfWork newUnitOfWork( Usecase usecase, ModuleSpi module, long currentTime )
        {
            final EntityStoreUnitOfWork uow = next.newUnitOfWork( usecase, module, currentTime );
            return new ConcurrentCheckingEntityStoreUnitOfWork( uow, api.dereference( versions ), module, currentTime );
        }

        private static class ConcurrentCheckingEntityStoreUnitOfWork
            implements EntityStoreUnitOfWork
        {
            private final EntityStoreUnitOfWork uow;
            private EntityStateVersions versions;
            private ModuleSpi module;
            private long currentTime;

            private List<EntityState> loaded = new ArrayList<>();

            private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

            public ConcurrentCheckingEntityStoreUnitOfWork( EntityStoreUnitOfWork uow,
                                                            EntityStateVersions versions,
                                                            ModuleSpi module,
                                                            long currentTime
            )
            {
                this.uow = uow;
                this.versions = versions;
                this.module = module;
                this.currentTime = currentTime;
            }

            @Override
            public String identity()
            {
                return uow.identity();
            }

            @Override
            public long currentTime()
            {
                return uow.currentTime();
            }

            @Override
            public EntityState newEntityState( ModuleSpi module,
                                               EntityReference anIdentity,
                                               EntityDescriptor entityDescriptor
            )
                throws EntityStoreException
            {
                return uow.newEntityState( module, anIdentity, entityDescriptor );
            }

            @Override
            public StateCommitter applyChanges()
                throws EntityStoreException
            {
                lock.writeLock().lock();

                try
                {
                    versions.checkForConcurrentModification( loaded, module, currentTime );

                    final StateCommitter committer = uow.applyChanges();

                    return new StateCommitter()
                    {
                        @Override
                        public void commit()
                        {
                            committer.commit();
                            versions.forgetVersions( loaded );

                            lock.writeLock().unlock();
                        }

                        @Override
                        public void cancel()
                        {
                            committer.cancel();
                            versions.forgetVersions( loaded );

                            lock.writeLock().unlock();
                        }
                    };
                }
                catch( EntityStoreException e )
                {
                    lock.writeLock().unlock();
                    throw e;
                }
            }

            @Override
            public void discard()
            {
                try
                {
                    uow.discard();
                }
                finally
                {
                    lock.writeLock().lock();

                    try
                    {
                        versions.forgetVersions( loaded );
                    }
                    finally
                    {
                        lock.writeLock().unlock();
                    }
                }
            }

            @Override
            public Usecase usecase()
            {
                return uow.usecase();
            }

            @SuppressWarnings( "DuplicateThrows" )
            @Override
            public EntityState entityStateOf( ModuleSpi module, EntityReference anIdentity )
                throws EntityStoreException, EntityNotFoundException
            {
                lock.readLock().lock();

                try
                {
                    EntityState entityState = uow.entityStateOf( module, anIdentity );
                    versions.rememberVersion( entityState.identity(), entityState.version() );
                    loaded.add( entityState );
                    return entityState;
                }
                finally
                {
                    lock.readLock().unlock();
                }
            }
        }
    }