package co.codewizards.cloudstore.local; import static co.codewizards.cloudstore.core.util.Util.*; import java.io.IOException; import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import co.codewizards.cloudstore.core.oio.File; import co.codewizards.cloudstore.core.repo.local.FileAlreadyRepositoryException; import co.codewizards.cloudstore.core.repo.local.LocalRepoManager; import co.codewizards.cloudstore.core.repo.local.LocalRepoManagerCloseEvent; import co.codewizards.cloudstore.core.repo.local.LocalRepoManagerCloseListener; import co.codewizards.cloudstore.core.repo.local.LocalRepoManagerException; import co.codewizards.cloudstore.core.repo.local.LocalRepoManagerFactory; import co.codewizards.cloudstore.core.util.AssertUtil; /** * Registry of {@link LocalRepoManager}s. * <p> * There is one single instance of this registry. It serves as the central point to obtain * {@code LocalRepoManager}s. * @author Marco หงุ่ยตระกูล-Schulze - marco at codewizards dot co */ public class LocalRepoManagerFactoryImpl implements LocalRepoManagerFactory { private static final Logger logger = LoggerFactory.getLogger(LocalRepoManagerFactoryImpl.class); private final Map<File, LocalRepoManagerImpl> localRoot2LocalRepoManagerImpl = new HashMap<File, LocalRepoManagerImpl>(); private final Set<LocalRepoManagerImpl> nonReOpenableLocalRepoManagerImpls = new HashSet<LocalRepoManagerImpl>(); private final List<LocalRepoManagerCloseListener> localRepoManagerCloseListeners = new CopyOnWriteArrayList<LocalRepoManagerCloseListener>(); private final LocalRepoManagerCloseListener localRepoManagerCloseListener = new LocalRepoManagerCloseListener() { @Override public void preClose(final LocalRepoManagerCloseEvent event) { if (!event.isBackend()) throw new IllegalStateException("Why are we notified by the proxy?!?"); preLocalRepoManagerBackendClose(event.getLocalRepoManager()); } @Override public void postClose(final LocalRepoManagerCloseEvent event) { if (!event.isBackend()) throw new IllegalStateException("Why are we notified by the proxy?!?"); postLocalRepoManagerBackendClose((LocalRepoManagerImpl) event.getLocalRepoManager()); } }; @Override public synchronized Set<File> getLocalRoots() { return Collections.unmodifiableSet(new HashSet<File>(localRoot2LocalRepoManagerImpl.keySet())); } @SuppressWarnings("resource") @Override public synchronized LocalRepoManager createLocalRepoManagerForExistingRepository(File localRoot) throws LocalRepoManagerException { localRoot = canonicalize(localRoot); LocalRepoManagerImpl localRepoManagerImpl = localRoot2LocalRepoManagerImpl.get(localRoot); if (localRepoManagerImpl != null && !localRepoManagerImpl.open()) { localRoot2LocalRepoManagerImpl.remove(localRoot); nonReOpenableLocalRepoManagerImpls.add(localRepoManagerImpl); while (localRepoManagerImpl.isOpen()) { logger.info("createLocalRepoManagerForExistingRepository: Existing LocalRepoManagerImpl is currently closing and could not be re-opened. Waiting for it to be completely closed."); try { Thread.sleep(100); } catch (final InterruptedException x) { doNothing(); } } localRepoManagerImpl = null; } if (localRepoManagerImpl == null) { localRepoManagerImpl = new LocalRepoManagerImpl(localRoot, false); if (!localRepoManagerImpl.open()) throw new IllegalStateException("localRepoManagerImpl.open() of *new* instance returned false!"); enlist(localRepoManagerImpl); } return createProxy(localRepoManagerImpl); } @SuppressWarnings("resource") @Override public synchronized LocalRepoManager createLocalRepoManagerForNewRepository(File localRoot) throws LocalRepoManagerException { localRoot = canonicalize(localRoot); LocalRepoManagerImpl localRepoManagerImpl = localRoot2LocalRepoManagerImpl.get(localRoot); if (localRepoManagerImpl != null) { throw new FileAlreadyRepositoryException(localRoot); } localRepoManagerImpl = new LocalRepoManagerImpl(localRoot, true); if (!localRepoManagerImpl.open()) throw new IllegalStateException("localRepoManagerImpl.open() of *new* instance returned false!"); enlist(localRepoManagerImpl); return createProxy(localRepoManagerImpl); } private LocalRepoManager createProxy(final LocalRepoManagerImpl localRepoManagerImpl) { return (LocalRepoManager) Proxy.newProxyInstance( this.getClass().getClassLoader(), new Class<?>[] { LocalRepoManager.class }, new LocalRepoManagerInvocationHandler(localRepoManagerImpl)); } @Override public synchronized void close() { for (final LocalRepoManagerImpl localRepoManagerImpl : new ArrayList<LocalRepoManagerImpl>(localRoot2LocalRepoManagerImpl.values())) { localRepoManagerImpl.close(); } } @Override public void addLocalRepoManagerCloseListener(final LocalRepoManagerCloseListener listener) { localRepoManagerCloseListeners.add(listener); } @Override public void removeLocalRepoManagerCloseListener(final LocalRepoManagerCloseListener listener) { localRepoManagerCloseListeners.remove(listener); } private void enlist(final LocalRepoManagerImpl localRepoManager) { localRoot2LocalRepoManagerImpl.put(localRepoManager.getLocalRoot(), localRepoManager); localRepoManager.addLocalRepoManagerCloseListener(localRepoManagerCloseListener); } private File canonicalize(File localRoot) { AssertUtil.assertNotNull(localRoot, "localRoot"); try { localRoot = localRoot.getCanonicalFile(); } catch (final IOException e) { throw new RuntimeException(e); } return localRoot; } private void preLocalRepoManagerBackendClose(final LocalRepoManager localRepoManager) { final LocalRepoManagerCloseEvent event = new LocalRepoManagerCloseEvent(this, localRepoManager, true); for (final LocalRepoManagerCloseListener listener : localRepoManagerCloseListeners) { listener.preClose(event); } } private void postLocalRepoManagerBackendClose(final LocalRepoManagerImpl localRepoManager) { AssertUtil.assertNotNull(localRepoManager, "localRepoManager"); synchronized (this) { final LocalRepoManagerImpl localRepoManager2 = localRoot2LocalRepoManagerImpl.remove(localRepoManager.getLocalRoot()); if (localRepoManager != localRepoManager2) { if (nonReOpenableLocalRepoManagerImpls.remove(localRepoManager)) logger.info("localRepoManager[{}] could not be re-opened and was unlisted before.", localRepoManager.id); else throw new IllegalStateException(String.format("localRepoManager[%s] is unknown!", localRepoManager.id)); localRoot2LocalRepoManagerImpl.put(localRepoManager2.getLocalRoot(), localRepoManager2); // re-add! } } final LocalRepoManagerCloseEvent event = new LocalRepoManagerCloseEvent(this, localRepoManager, true); for (final LocalRepoManagerCloseListener listener : localRepoManagerCloseListeners) { listener.postClose(event); } } }