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