001package co.codewizards.cloudstore.local;
002
003import static co.codewizards.cloudstore.core.util.Util.*;
004import static java.util.Objects.*;
005
006import java.io.IOException;
007import java.lang.reflect.Proxy;
008import java.util.ArrayList;
009import java.util.Collections;
010import java.util.HashMap;
011import java.util.HashSet;
012import java.util.List;
013import java.util.Map;
014import java.util.Set;
015import java.util.concurrent.CopyOnWriteArrayList;
016
017import org.slf4j.Logger;
018import org.slf4j.LoggerFactory;
019
020import co.codewizards.cloudstore.core.oio.File;
021import co.codewizards.cloudstore.core.repo.local.FileAlreadyRepositoryException;
022import co.codewizards.cloudstore.core.repo.local.LocalRepoManager;
023import co.codewizards.cloudstore.core.repo.local.LocalRepoManagerCloseEvent;
024import co.codewizards.cloudstore.core.repo.local.LocalRepoManagerCloseListener;
025import co.codewizards.cloudstore.core.repo.local.LocalRepoManagerException;
026import co.codewizards.cloudstore.core.repo.local.LocalRepoManagerFactory;
027
028/**
029 * Registry of {@link LocalRepoManager}s.
030 * <p>
031 * There is one single instance of this registry. It serves as the central point to obtain
032 * {@code LocalRepoManager}s.
033 * @author Marco หงุ่ยตระกูล-Schulze - marco at codewizards dot co
034 */
035public class LocalRepoManagerFactoryImpl implements LocalRepoManagerFactory {
036        private static final Logger logger = LoggerFactory.getLogger(LocalRepoManagerFactoryImpl.class);
037
038        private final Map<File, LocalRepoManagerImpl> localRoot2LocalRepoManagerImpl = new HashMap<File, LocalRepoManagerImpl>();
039        private final Set<LocalRepoManagerImpl> nonReOpenableLocalRepoManagerImpls = new HashSet<LocalRepoManagerImpl>();
040
041        private final List<LocalRepoManagerCloseListener> localRepoManagerCloseListeners = new CopyOnWriteArrayList<LocalRepoManagerCloseListener>();
042
043        private final LocalRepoManagerCloseListener localRepoManagerCloseListener = new LocalRepoManagerCloseListener() {
044                @Override
045                public void preClose(final LocalRepoManagerCloseEvent event) {
046                        if (!event.isBackend())
047                                throw new IllegalStateException("Why are we notified by the proxy?!?");
048
049                        preLocalRepoManagerBackendClose(event.getLocalRepoManager());
050                }
051                @Override
052                public void postClose(final LocalRepoManagerCloseEvent event) {
053                        if (!event.isBackend())
054                                throw new IllegalStateException("Why are we notified by the proxy?!?");
055
056                        postLocalRepoManagerBackendClose((LocalRepoManagerImpl) event.getLocalRepoManager());
057                }
058        };
059
060        @Override
061        public synchronized Set<File> getLocalRoots() {
062                return Collections.unmodifiableSet(new HashSet<File>(localRoot2LocalRepoManagerImpl.keySet()));
063        }
064
065        @SuppressWarnings("resource")
066        @Override
067        public synchronized LocalRepoManager createLocalRepoManagerForExistingRepository(File localRoot) throws LocalRepoManagerException {
068                localRoot = canonicalize(localRoot);
069
070                LocalRepoManagerImpl localRepoManagerImpl = localRoot2LocalRepoManagerImpl.get(localRoot);
071                if (localRepoManagerImpl != null && !localRepoManagerImpl.open()) {
072                        localRoot2LocalRepoManagerImpl.remove(localRoot);
073                        nonReOpenableLocalRepoManagerImpls.add(localRepoManagerImpl);
074                        while (localRepoManagerImpl.isOpen()) {
075                                logger.info("createLocalRepoManagerForExistingRepository: Existing LocalRepoManagerImpl is currently closing and could not be re-opened. Waiting for it to be completely closed.");
076                                try { Thread.sleep(100); } catch (final InterruptedException x) { doNothing(); }
077                        }
078                        localRepoManagerImpl = null;
079                }
080
081                if (localRepoManagerImpl == null) {
082                        localRepoManagerImpl = new LocalRepoManagerImpl(localRoot, false);
083                        if (!localRepoManagerImpl.open())
084                                throw new IllegalStateException("localRepoManagerImpl.open() of *new* instance returned false!");
085
086                        enlist(localRepoManagerImpl);
087                }
088                return createProxy(localRepoManagerImpl);
089        }
090
091        @SuppressWarnings("resource")
092        @Override
093        public synchronized LocalRepoManager createLocalRepoManagerForNewRepository(File localRoot) throws LocalRepoManagerException {
094                localRoot = canonicalize(localRoot);
095
096                LocalRepoManagerImpl localRepoManagerImpl = localRoot2LocalRepoManagerImpl.get(localRoot);
097                if (localRepoManagerImpl != null) {
098                        throw new FileAlreadyRepositoryException(localRoot);
099                }
100
101                localRepoManagerImpl = new LocalRepoManagerImpl(localRoot, true);
102                if (!localRepoManagerImpl.open())
103                        throw new IllegalStateException("localRepoManagerImpl.open() of *new* instance returned false!");
104
105                enlist(localRepoManagerImpl);
106                return createProxy(localRepoManagerImpl);
107        }
108
109        private LocalRepoManager createProxy(final LocalRepoManagerImpl localRepoManagerImpl) {
110                return (LocalRepoManager) Proxy.newProxyInstance(
111                                this.getClass().getClassLoader(),
112                                new Class<?>[] { LocalRepoManager.class },
113                                new LocalRepoManagerInvocationHandler(localRepoManagerImpl));
114        }
115
116        @Override
117        public synchronized void close() {
118                for (final LocalRepoManagerImpl localRepoManagerImpl : new ArrayList<LocalRepoManagerImpl>(localRoot2LocalRepoManagerImpl.values())) {
119                        localRepoManagerImpl.close();
120                }
121        }
122
123        @Override
124        public void addLocalRepoManagerCloseListener(final LocalRepoManagerCloseListener listener) {
125                localRepoManagerCloseListeners.add(listener);
126        }
127
128        @Override
129        public void removeLocalRepoManagerCloseListener(final LocalRepoManagerCloseListener listener) {
130                localRepoManagerCloseListeners.remove(listener);
131        }
132
133        private void enlist(final LocalRepoManagerImpl localRepoManager) {
134                localRoot2LocalRepoManagerImpl.put(localRepoManager.getLocalRoot(), localRepoManager);
135                localRepoManager.addLocalRepoManagerCloseListener(localRepoManagerCloseListener);
136        }
137
138        private File canonicalize(File localRoot) {
139                requireNonNull(localRoot, "localRoot");
140                try {
141                        localRoot = localRoot.getCanonicalFile();
142                } catch (final IOException e) {
143                        throw new RuntimeException(e);
144                }
145                return localRoot;
146        }
147
148        private void preLocalRepoManagerBackendClose(final LocalRepoManager localRepoManager) {
149                final LocalRepoManagerCloseEvent event = new LocalRepoManagerCloseEvent(this, localRepoManager, true);
150                for (final LocalRepoManagerCloseListener listener : localRepoManagerCloseListeners) {
151                        listener.preClose(event);
152                }
153        }
154
155        private void postLocalRepoManagerBackendClose(final LocalRepoManagerImpl localRepoManager) {
156                requireNonNull(localRepoManager, "localRepoManager");
157                synchronized (this) {
158                        final LocalRepoManagerImpl localRepoManager2 = localRoot2LocalRepoManagerImpl.remove(localRepoManager.getLocalRoot());
159                        if (localRepoManager != localRepoManager2) {
160                                if (nonReOpenableLocalRepoManagerImpls.remove(localRepoManager))
161                                        logger.info("localRepoManager[{}] could not be re-opened and was unlisted before.", localRepoManager.id);
162                                else
163                                        throw new IllegalStateException(String.format("localRepoManager[%s] is unknown!", localRepoManager.id));
164
165                                localRoot2LocalRepoManagerImpl.put(localRepoManager2.getLocalRoot(), localRepoManager2); // re-add!
166                        }
167                }
168                final LocalRepoManagerCloseEvent event = new LocalRepoManagerCloseEvent(this, localRepoManager, true);
169                for (final LocalRepoManagerCloseListener listener : localRepoManagerCloseListeners) {
170                        listener.postClose(event);
171                }
172        }
173}