001package co.codewizards.cloudstore.core.repo.local;
002
003import java.util.ArrayList;
004import java.util.Collections;
005import java.util.Comparator;
006import java.util.Iterator;
007import java.util.LinkedList;
008import java.util.List;
009import java.util.ServiceLoader;
010
011import co.codewizards.cloudstore.core.util.AssertUtil;
012
013public class LocalRepoTransactionListenerRegistry {
014
015        private final LocalRepoTransaction transaction;
016
017        private final List<LocalRepoTransactionListener> listeners;
018        private static List<Class<? extends LocalRepoTransactionListener>> listenerClasses;
019
020        public LocalRepoTransactionListenerRegistry(final LocalRepoTransaction transaction) {
021                this.transaction = AssertUtil.assertNotNull(transaction, "transaction");
022                this.listeners = createListeners();
023
024                for (final LocalRepoTransactionListener listener : listeners)
025                        transaction.setContextObject(listener);
026        }
027
028        public LocalRepoTransaction getTransaction() {
029                return transaction;
030        }
031
032        /**
033         * Notifies this instance about the {@linkplain #getTransaction() transaction} being begun.
034         * @see #onCommit()
035         * @see #onRollback()
036         */
037        public void onBegin() {
038                for (final LocalRepoTransactionListener listener : listeners)
039                        listener.onBegin();
040        }
041
042        /**
043         * Notifies this instance about the {@linkplain #getTransaction() transaction} being committed.
044         * @see #onBegin()
045         * @see #onRollback()
046         */
047        public void onCommit() {
048                // We flush *before* triggering each listener! It's likely that flushing causes a few JDO-lifecycle-listeners to be
049                // triggered and thus the LocalRepoTransactionListeners might work on an incomplete state, if we flushed later.
050                // Additionally, every listener might change some data and we thus need to flush again between the listeners.
051                transaction.flush();
052                for (final LocalRepoTransactionListener listener : listeners) {
053                        listener.onCommit();
054                        transaction.flush();
055                }
056        }
057
058        /**
059         * Notifies this instance about the {@linkplain #getTransaction() transaction} being rolled back.
060         * @see #onBegin()
061         * @see #onCommit()
062         */
063        public void onRollback() {
064                for (final LocalRepoTransactionListener listener : listeners)
065                        listener.onRollback();
066        }
067
068        private List<LocalRepoTransactionListener> createListeners() {
069                if (listenerClasses == null) {
070                        final List<LocalRepoTransactionListener> listeners = new LinkedList<>();
071                        final Iterator<LocalRepoTransactionListener> iterator = ServiceLoader.load(LocalRepoTransactionListener.class).iterator();
072                        while (iterator.hasNext()) {
073                                final LocalRepoTransactionListener listener = iterator.next();
074                                listener.setTransaction(transaction);
075                                listeners.add(listener);
076                        }
077
078                        sortListeners(listeners);
079
080                        final List<Class<? extends LocalRepoTransactionListener>> lcl = new ArrayList<>(listeners.size());
081                        for (final LocalRepoTransactionListener listener : listeners)
082                                lcl.add(listener.getClass());
083
084                        listenerClasses = lcl;
085                        return listeners;
086                }
087                else {
088                        final List<LocalRepoTransactionListener> listeners = new ArrayList<>(listenerClasses.size());
089                        for (final Class<? extends LocalRepoTransactionListener> lc : listenerClasses) {
090                                final LocalRepoTransactionListener listener = createInstance(lc);
091                                listener.setTransaction(transaction);
092                                listeners.add(listener);
093                        }
094
095                        return listeners;
096                }
097        }
098
099        private void sortListeners(final List<LocalRepoTransactionListener> listeners) {
100                Collections.sort(listeners, new Comparator<LocalRepoTransactionListener>() {
101                        @Override
102                        public int compare(final LocalRepoTransactionListener o1, final LocalRepoTransactionListener o2) {
103                                final int result = -1 * Integer.compare(o1.getPriority(), o2.getPriority());
104                                if (result != 0)
105                                        return result;
106
107                                return o1.getClass().getName().compareTo(o2.getClass().getName());
108                        }
109                });
110        }
111
112        private static <T> T createInstance(final Class<T> clazz) {
113                try {
114                        return clazz.newInstance();
115                } catch (InstantiationException | IllegalAccessException e) {
116                        throw new RuntimeException(e);
117                }
118        }
119}