package io.github.lucaseasedup.logit.storage;
import io.github.lucaseasedup.logit.LogItCore;
import io.github.lucaseasedup.logit.logging.CustomLevel;
import io.github.lucaseasedup.logit.util.CollectionUtils;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Level;
public final class WrapperStorage implements Storage
{
private WrapperStorage(Storage leading, CacheType cacheType)
{
if (leading == null || cacheType == null)
throw new IllegalArgumentException();
this.executorService = Executors.newSingleThreadExecutor();
this.leading = leading;
this.cacheType = cacheType;
if (cacheType == CacheType.PRELOADED)
{
preloadedCache = new HashMap<>();
}
}
@Override
public synchronized void connect() throws IOException
{
log(CustomLevel.INTERNAL, "WrapperStorage#connect()");
leading.connect();
for (Storage mirror : mirrors.keySet())
{
mirror.connect();
}
}
@Override
public synchronized boolean isConnected() throws IOException
{
log(CustomLevel.INTERNAL, "WrapperStorage#isConnected()");
return leading.isConnected();
}
public synchronized void preload(String... units) throws IOException
{
if (units == null)
throw new IllegalArgumentException();
if (cacheType == CacheType.PRELOADED)
{
preloadedCache.clear();
for (String unit : units)
{
PreloadedUnitCache unitCache = new PreloadedUnitCache(
leading.getKeys(unit),
leading.getPrimaryKey(unit),
leading.selectEntries(unit)
);
preloadedCache.put(unit, unitCache);
}
}
}
@Override
public synchronized void ping() throws IOException
{
log(CustomLevel.INTERNAL, "WrapperStorage#ping()");
executorService.submit(new Runnable()
{
@Override
public void run()
{
try
{
leading.ping();
}
catch (IOException ex)
{
log(Level.WARNING, ex);
}
for (Storage mirror : mirrors.keySet())
{
try
{
mirror.ping();
}
catch (IOException ex)
{
log(Level.WARNING, ex);
}
}
}
});
}
@Override
public synchronized void close() throws IOException
{
for (StorageObserver observer : observers)
{
observer.beforeClose();
}
log(CustomLevel.INTERNAL, "WrapperStorage#close()");
leading.close();
for (Storage mirror : mirrors.keySet())
{
mirror.close();
}
}
@Override
public synchronized List<String> getUnitNames() throws IOException
{
log(CustomLevel.INTERNAL, "WrapperStorage#getUnitNames()");
if (cacheType == CacheType.DISABLED)
{
return leading.getUnitNames();
}
else if (cacheType == CacheType.PRELOADED)
{
return new ArrayList<>(preloadedCache.keySet());
}
else
{
throw new RuntimeException("Unsupported cache type: " + cacheType);
}
}
@Override
public synchronized UnitKeys getKeys(String unit)
throws IOException
{
log(CustomLevel.INTERNAL, "WrapperStorage#getKeys(\"" + unit + "\")");
if (cacheType == CacheType.DISABLED)
{
return leading.getKeys(unit);
}
else if (cacheType == CacheType.PRELOADED)
{
return preloadedCache.get(unit).getKeys();
}
else
{
throw new RuntimeException("Unsupported cache type: " + cacheType);
}
}
@Override
public synchronized String getPrimaryKey(String unit) throws IOException
{
log(CustomLevel.INTERNAL, "WrapperStorage#getPrimaryKey(\"" + unit + "\")");
if (cacheType == CacheType.DISABLED)
{
return leading.getPrimaryKey(unit);
}
else if (cacheType == CacheType.PRELOADED)
{
return preloadedCache.get(unit).getPrimaryKey();
}
else
{
throw new RuntimeException("Unsupported cache type: " + cacheType);
}
}
@Override
public synchronized List<StorageEntry> selectEntries(String unit)
throws IOException
{
log(CustomLevel.INTERNAL, "WrapperStorage#selectEntries(\"" + unit + "\")");
if (cacheType == CacheType.DISABLED)
{
return leading.selectEntries(unit);
}
else if (cacheType == CacheType.PRELOADED)
{
List<StorageEntry> entries = preloadedCache.get(unit).getEntryList();
if (entries == null)
return null;
return StorageEntry.copyList(entries);
}
else
{
throw new RuntimeException("Unsupported cache type: " + cacheType);
}
}
@Override
public synchronized List<StorageEntry> selectEntries(
String unit, Selector selector
) throws IOException
{
log(CustomLevel.INTERNAL, "WrapperStorage#selectEntries("
+ "\"" + unit + "\", "
+ SqlUtils.translateSelector(selector, "`", "'") + ")");
if (cacheType == CacheType.DISABLED)
{
return leading.selectEntries(unit, selector);
}
else if (cacheType == CacheType.PRELOADED)
{
List<StorageEntry> entries = preloadedCache.get(unit).getEntryList();
if (entries == null)
return null;
return StorageEntry.copyList(
entries, new SelectorConstant(true)
);
}
else
{
throw new RuntimeException("Unsupported cache type: " + cacheType);
}
}
@Override
public synchronized List<StorageEntry> selectEntries(
String unit, List<String> keys
) throws IOException
{
log(CustomLevel.INTERNAL, "WrapperStorage#selectEntries("
+ "\"" + unit + "\", "
+ Arrays.toString(keys.toArray()) + ")");
if (cacheType == CacheType.DISABLED)
{
return leading.selectEntries(unit, keys);
}
else if (cacheType == CacheType.PRELOADED)
{
List<StorageEntry> entries = preloadedCache.get(unit).getEntryList();
if (entries == null)
return null;
return StorageEntry.copyList(
entries, keys, new SelectorConstant(true)
);
}
else
{
throw new RuntimeException("Unsupported cache type: " + cacheType);
}
}
@Override
public synchronized List<StorageEntry> selectEntries(
String unit, List<String> keys, Selector selector
) throws IOException
{
log(CustomLevel.INTERNAL, "WrapperStorage#selectEntries("
+ "\"" + unit + "\", "
+ Arrays.toString(keys.toArray()) + ", "
+ SqlUtils.translateSelector(selector, "`", "'") + ")");
if (cacheType == CacheType.DISABLED)
{
return leading.selectEntries(unit, keys, selector);
}
else if (cacheType == CacheType.PRELOADED)
{
List<StorageEntry> entries = preloadedCache.get(unit).getEntryList();
if (entries == null)
return null;
return StorageEntry.copyList(
entries, keys, selector
);
}
else
{
throw new RuntimeException("Unsupported cache type: " + cacheType);
}
}
@Override
public synchronized void createUnit(
String unit, final UnitKeys keys, final String primaryKey
) throws IOException
{
log(CustomLevel.INTERNAL, "WrapperStorage#createUnit("
+ "\"" + unit + "\", "
+ "UnitKeys {keys: ["
+ CollectionUtils.toString(keys.keySet())
+ "]})");
leading.createUnit(unit, keys, primaryKey);
walkMirrors(new UnitWalker()
{
@Override
public void walk(Storage storage, String unit) throws IOException
{
storage.createUnit(unit, keys, primaryKey);
}
}, unit);
if (cacheType == CacheType.PRELOADED)
{
if (!preloadedCache.containsKey(unit))
{
PreloadedUnitCache unitCache = new PreloadedUnitCache(
keys, primaryKey, new LinkedList<StorageEntry>()
);
preloadedCache.put(unit, unitCache);
}
}
for (StorageObserver observer : observers)
{
observer.afterCreateUnit(unit, keys);
}
}
@Override
public synchronized void renameUnit(String unit, String newName)
throws IOException
{
log(CustomLevel.INTERNAL, "WrapperStorage#renameUnit("
+ "\"" + unit + "\", "
+ "\"" + newName + "\")");
if (unit.equals(newName))
throw new IllegalArgumentException();
leading.renameUnit(unit, newName);
for (Map.Entry<Storage, Map<String, String>> e : mirrors.entrySet())
{
String unitMapping = e.getValue().get(unit);
if (unitMapping == null)
{
unitMapping = unit;
}
e.getValue().remove(unit);
e.getValue().put(newName, unitMapping);
}
if (cacheType == CacheType.PRELOADED)
{
if (preloadedCache.containsKey(unit))
{
preloadedCache.put(newName, preloadedCache.remove(unit));
}
}
for (StorageObserver observer : observers)
{
observer.afterRenameUnit(unit, newName);
}
}
@Override
public synchronized void eraseUnit(String unit) throws IOException
{
log(CustomLevel.INTERNAL, "WrapperStorage#eraseUnit("
+ "\"" + unit + "\")");
leading.eraseUnit(unit);
walkMirrors(new UnitWalker()
{
@Override
public void walk(Storage storage, String unit) throws IOException
{
storage.eraseUnit(unit);
}
}, unit);
if (cacheType == CacheType.PRELOADED)
{
if (preloadedCache.containsKey(unit))
{
preloadedCache.get(unit).getEntryList().clear();
}
}
for (StorageObserver observer : observers)
{
observer.afterEraseUnit(unit);
}
}
@Override
public synchronized void removeUnit(String unit) throws IOException
{
log(CustomLevel.INTERNAL, "WrapperStorage#removeUnit("
+ "\"" + unit + "\")");
leading.removeUnit(unit);
walkMirrors(new UnitWalker()
{
@Override
public void walk(Storage storage, String unit) throws IOException
{
storage.removeUnit(unit);
}
}, unit);
if (cacheType == CacheType.PRELOADED)
{
if (preloadedCache.containsKey(unit))
{
preloadedCache.remove(unit);
}
}
for (StorageObserver observer : observers)
{
observer.afterRemoveUnit(unit);
}
}
@Override
public synchronized void addKey(
String unit, final String key, final DataType type
) throws IOException
{
log(CustomLevel.INTERNAL, "WrapperStorage#addKey("
+ "\"" + unit + "\", "
+ "\"" + key + "\", "
+ type + ")");
leading.addKey(unit, key, type);
walkMirrors(new UnitWalker()
{
@Override
public void walk(Storage storage, String unit) throws IOException
{
UnitKeys keys = storage.getKeys(unit);
if (!keys.containsKey(key))
{
storage.addKey(unit, key, type);
}
}
}, unit);
if (cacheType == CacheType.PRELOADED)
{
if (preloadedCache.containsKey(unit))
{
for (StorageEntry entry : preloadedCache.get(unit).getEntryList())
{
entry.put(key, "");
}
preloadedCache.get(unit).getKeys().put(key, type);
}
}
for (StorageObserver observer : observers)
{
observer.afterAddKey(unit, key, type);
}
}
@Override
public synchronized void addEntry(
String unit, final StorageEntry entry
) throws IOException
{
log(CustomLevel.INTERNAL, "WrapperStorage#addEntry("
+ "\"" + unit + "\", "
+ entry + ")");
leading.addEntry(unit, entry);
walkMirrors(new UnitWalker()
{
@Override
public void walk(Storage storage, String unit) throws IOException
{
storage.addEntry(unit, entry);
}
}, unit);
if (cacheType == CacheType.PRELOADED)
{
if (preloadedCache.containsKey(unit))
{
preloadedCache.get(unit).getEntryList().add(entry.copy());
}
}
for (StorageObserver observer : observers)
{
observer.afterAddEntry(unit, entry);
}
}
@Override
public synchronized void updateEntries(
String unit, final StorageEntry entrySubset, final Selector selector
) throws IOException
{
log(CustomLevel.INTERNAL, "WrapperStorage#updateEntries("
+ "\"" + unit + "\", "
+ entrySubset + ", "
+ SqlUtils.translateSelector(selector, "`", "'") + ")");
leading.updateEntries(unit, entrySubset, selector);
walkMirrors(new UnitWalker()
{
@Override
public void walk(Storage storage, String unit) throws IOException
{
storage.updateEntries(unit, entrySubset, selector);
}
}, unit);
if (cacheType == CacheType.PRELOADED)
{
if (preloadedCache.containsKey(unit))
{
for (StorageEntry entry : preloadedCache.get(unit).getEntryList())
{
if (SqlUtils.resolveSelector(selector, entry))
{
for (StorageDatum datum : entrySubset)
{
entry.put(datum.getKey(), datum.getValue());
}
}
}
}
}
for (StorageObserver observer : observers)
{
observer.afterUpdateEntries(unit, entrySubset, selector);
}
}
@Override
public synchronized void removeEntries(
String unit, final Selector selector
) throws IOException
{
log(CustomLevel.INTERNAL, "WrapperStorage#removeEntries("
+ "\"" + unit + "\", "
+ SqlUtils.translateSelector(selector, "`", "'") + ")");
leading.removeEntries(unit, selector);
walkMirrors(new UnitWalker()
{
@Override
public void walk(Storage storage, String unit) throws IOException
{
storage.removeEntries(unit, selector);
}
}, unit);
if (cacheType == CacheType.PRELOADED)
{
if (preloadedCache.containsKey(unit))
{
Iterator<StorageEntry> entryIt =
preloadedCache.get(unit).getEntryList().iterator();
while (entryIt.hasNext())
{
StorageEntry entry = entryIt.next();
if (SqlUtils.resolveSelector(selector, entry))
{
entryIt.remove();
}
}
}
}
for (StorageObserver observer : observers)
{
observer.afterRemoveEntries(unit, selector);
}
}
@Override
public boolean isAutobatchEnabled()
{
return leading.isAutobatchEnabled();
}
@Override
public synchronized void setAutobatchEnabled(boolean status)
{
leading.setAutobatchEnabled(status);
for (Storage mirror : mirrors.keySet())
{
mirror.setAutobatchEnabled(status);
}
}
@Override
public synchronized void executeBatch() throws IOException
{
leading.executeBatch();
executorService.submit(new Runnable()
{
@Override
public void run()
{
for (Storage mirror : mirrors.keySet())
{
try
{
mirror.executeBatch();
}
catch (IOException ex)
{
log(Level.WARNING, ex);
}
}
}
});
}
@Override
public synchronized void clearBatch() throws IOException
{
leading.clearBatch();
for (Storage mirror : mirrors.keySet())
{
mirror.clearBatch();
}
}
public synchronized void mirrorStorage(
Storage storage, Map<String, String> unitMappings
)
{
if (storage == null || unitMappings == null)
throw new IllegalArgumentException();
if (!mirrors.containsKey(storage))
{
mirrors.put(storage, unitMappings);
}
}
public synchronized void mirrorStorage(Storage storage)
{
mirrorStorage(storage, new HashMap<String, String>());
}
public synchronized void unmirrorStorage(Storage storage)
{
mirrors.remove(storage);
}
public synchronized void unmirrorAll()
{
mirrors.clear();
}
public synchronized void addObserver(StorageObserver observer)
{
if (observer == null)
throw new IllegalArgumentException();
if (!observers.contains(observer))
{
observers.add(observer);
}
}
public synchronized void deleteObserver(StorageObserver observer)
{
observers.remove(observer);
}
public synchronized void deleteObservers()
{
observers.clear();
}
public synchronized int countObservers()
{
return observers.size();
}
public Storage getLeadingStorage()
{
return leading;
}
private void walkMirrors(final UnitWalker walker, final String unit)
{
executorService.submit(new Runnable()
{
@Override
public void run()
{
for (Map.Entry<Storage, Map<String, String>> e : mirrors.entrySet())
{
String unitMapping = e.getValue().get(unit);
if (unitMapping == null)
{
unitMapping = unit;
}
try
{
walker.walk(e.getKey(), unitMapping);
}
catch (IOException ex)
{
log(Level.WARNING, ex);
}
}
}
});
}
private void log(Level level, String message)
{
LogItCore.getInstance().log(level, message);
}
private void log(Level level, Throwable throwable)
{
LogItCore.getInstance().log(level, throwable);
}
public static final class Builder
{
public WrapperStorage build()
{
return new WrapperStorage(leading, cacheType);
}
public Builder leading(Storage leading)
{
if (leading == null)
throw new IllegalArgumentException();
this.leading = leading;
return this;
}
public Builder cacheType(CacheType cacheType)
{
if (cacheType == null)
throw new IllegalArgumentException();
this.cacheType = cacheType;
return this;
}
private Storage leading;
private CacheType cacheType;
}
private static interface UnitWalker
{
public void walk(Storage storage, String unit) throws IOException;
}
/**
* Used to update mirrors in the background.
*/
private final ExecutorService executorService;
private final Storage leading;
private final CacheType cacheType;
private final Map<Storage, Map<String, String>> mirrors = new HashMap<>();
private final List<StorageObserver> observers = new ArrayList<>();
private Map<String, PreloadedUnitCache> preloadedCache;
}