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}