001package co.codewizards.cloudstore.local;
002
003import static co.codewizards.cloudstore.core.util.Util.*;
004
005import java.util.concurrent.locks.Lock;
006
007import javax.jdo.PersistenceManager;
008import javax.jdo.PersistenceManagerFactory;
009import javax.jdo.Transaction;
010
011import co.codewizards.cloudstore.core.repo.local.LocalRepoManager;
012import co.codewizards.cloudstore.local.persistence.DAO;
013import co.codewizards.cloudstore.local.persistence.LocalRepository;
014import co.codewizards.cloudstore.local.persistence.LocalRepositoryDAO;
015
016public class LocalRepoTransactionImpl implements co.codewizards.cloudstore.core.repo.local.LocalRepoTransaction {
017
018        private final LocalRepoManager localRepoManager;
019        private final PersistenceManagerFactory persistenceManagerFactory;
020        private final boolean write;
021        private PersistenceManager persistenceManager;
022        private Transaction jdoTransaction;
023        private Lock lock;
024        private long localRevision = -1;
025
026        private final AutoTrackLifecycleListener autoTrackLifecycleListener = new AutoTrackLifecycleListener(this);
027
028        public LocalRepoTransactionImpl(LocalRepoManagerImpl localRepoManager, boolean write) {
029                this.localRepoManager = assertNotNull("localRepoManager", localRepoManager);
030                this.persistenceManagerFactory = assertNotNull("localRepoManager.persistenceManagerFactory", localRepoManager.getPersistenceManagerFactory());
031                this.write = write;
032                begin();
033        }
034
035        private synchronized void begin() {
036                if (isActive())
037                        throw new IllegalStateException("Transaction is already active!");
038
039                if (write)
040                        lock();
041
042                persistenceManager = persistenceManagerFactory.getPersistenceManager();
043                hookLifecycleListeners();
044                jdoTransaction = persistenceManager.currentTransaction();
045                jdoTransaction.begin();
046                autoTrackLifecycleListener.onBegin();
047        }
048
049        private void hookLifecycleListeners() {
050                persistenceManager.addInstanceLifecycleListener(autoTrackLifecycleListener, (Class[]) null);
051        }
052
053        @Override
054        public synchronized void commit() {
055                if (!isActive())
056                        throw new IllegalStateException("Transaction is not active!");
057
058                autoTrackLifecycleListener.onCommit();
059                persistenceManager.flush();
060                jdoTransaction.commit();
061                persistenceManager.close();
062                jdoTransaction = null;
063                persistenceManager = null;
064                localRevision = -1;
065                unlock();
066        }
067
068        @Override
069        public synchronized boolean isActive() {
070                return jdoTransaction != null && jdoTransaction.isActive();
071        }
072
073        @Override
074        public synchronized void rollback() {
075                if (!isActive())
076                        throw new IllegalStateException("Transaction is not active!");
077
078                autoTrackLifecycleListener.onRollback();
079                jdoTransaction.rollback();
080                persistenceManager.close();
081                jdoTransaction = null;
082                persistenceManager = null;
083                localRevision = -1;
084                unlock();
085        }
086
087        @Override
088        public synchronized void rollbackIfActive() {
089                if (isActive())
090                        rollback();
091        }
092
093        public PersistenceManager getPersistenceManager() {
094                if (!isActive()) {
095                        throw new IllegalStateException("Transaction is not active!");
096                }
097                return persistenceManager;
098        }
099
100        @Override
101        public long getLocalRevision() {
102                if (localRevision < 0) {
103                        if (!write)
104                                throw new IllegalStateException("This is a read-only transaction!");
105
106                        jdoTransaction.setSerializeRead(true);
107                        LocalRepository lr = getDAO(LocalRepositoryDAO.class).getLocalRepositoryOrFail();
108                        jdoTransaction.setSerializeRead(null);
109                        localRevision = lr.getRevision() + 1;
110                        lr.setRevision(localRevision);
111                        persistenceManager.flush();
112                }
113                return localRevision;
114        }
115
116        private synchronized void lock() {
117                if (lock == null) {
118                        lock = localRepoManager.getLock();
119                        lock.lock();
120                }
121        }
122
123        private synchronized void unlock() {
124                if (lock != null) {
125                        lock.unlock();
126                        lock = null;
127                }
128        }
129
130        @Override
131        public LocalRepoManager getLocalRepoManager() {
132                return localRepoManager;
133        }
134
135        @Override
136        public <D> D getDAO(Class<D> daoClass) {
137                final PersistenceManager pm = getPersistenceManager();
138                D dao;
139                try {
140                        dao = daoClass.newInstance();
141                } catch (InstantiationException e) {
142                        throw new RuntimeException(e);
143                } catch (IllegalAccessException e) {
144                        throw new RuntimeException(e);
145                }
146
147                if (!(dao instanceof DAO))
148                        throw new IllegalStateException(String.format("dao class %s does not extend DAO!", daoClass.getName()));
149
150                ((DAO<?, ?>)dao).setPersistenceManager(pm);
151                return dao;
152        }
153
154        @Override
155        public void flush() {
156                final PersistenceManager pm = getPersistenceManager();
157                pm.flush();
158        }
159}