/*
* ToroDB
* Copyright © 2014 8Kdata Technology (www.8kdata.com)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.torodb.core.transaction.metainf;
import com.google.common.collect.Maps;
import com.torodb.core.annotations.DoNotChange;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jooq.lambda.tuple.Tuple2;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
import java.util.stream.Stream;
/**
*
*/
public class WrapperMutableMetaDatabase implements MutableMetaDatabase {
private static final Logger LOGGER = LogManager.getLogger(WrapperMutableMetaDatabase.class);
private final ImmutableMetaDatabase wrapped;
private final HashMap<String, Tuple2<MutableMetaCollection, MetaElementState>> collectionsByName;
private final Consumer<WrapperMutableMetaDatabase> changeConsumer;
private final Map<String, Tuple2<MutableMetaCollection, MetaElementState>> aliveCollectionsMap;
public WrapperMutableMetaDatabase(ImmutableMetaDatabase wrapped,
Consumer<WrapperMutableMetaDatabase> changeConsumer) {
this.wrapped = wrapped;
this.changeConsumer = changeConsumer;
this.collectionsByName = new HashMap<>();
wrapped.streamMetaCollections().forEach((collection) -> {
WrapperMutableMetaCollection mutable = createMetaColletion(collection);
collectionsByName.put(collection.getName(),
new Tuple2<>(mutable, MetaElementState.NOT_CHANGED));
});
aliveCollectionsMap = Maps.filterValues(collectionsByName, tuple -> tuple.v2().isAlive());
}
protected WrapperMutableMetaCollection createMetaColletion(ImmutableMetaCollection immutable) {
return new WrapperMutableMetaCollection(immutable, this::onMetaCollectionChange);
}
@Override
public WrapperMutableMetaCollection addMetaCollection(String colName, String colId) throws
IllegalArgumentException {
if (getMetaCollectionByName(colName) != null) {
throw new IllegalArgumentException("There is another collection whose name is " + colName);
}
assert getMetaCollectionByIdentifier(colId) == null : "There is another collection whose id is "
+ colId;
WrapperMutableMetaCollection result = createMetaColletion(new ImmutableMetaCollection(
colName,
colId,
Collections.emptyMap(),
Collections.emptyMap())
);
collectionsByName.put(colName, new Tuple2<>(result, MetaElementState.ADDED));
changeConsumer.accept(this);
return result;
}
@DoNotChange
@Override
public Iterable<Tuple2<MutableMetaCollection, MetaElementState>> getModifiedCollections() {
return Maps.filterValues(collectionsByName, tuple -> tuple.v2().hasChanged())
.values();
}
@Override
public ImmutableMetaDatabase immutableCopy() {
if (collectionsByName.values().stream().noneMatch(tuple -> tuple.v2().hasChanged())) {
return wrapped;
} else {
ImmutableMetaDatabase.Builder builder = new ImmutableMetaDatabase.Builder(wrapped);
collectionsByName.values()
.forEach(tuple -> {
switch (tuple.v2()) {
case ADDED:
case MODIFIED:
case NOT_CHANGED:
builder.put(tuple.v1().immutableCopy());
break;
case REMOVED:
builder.remove(tuple.v1());
break;
case NOT_EXISTENT:
default:
throw new AssertionError("Unexpected case" + tuple.v2());
}
});
return builder.build();
}
}
@Override
public String getName() {
return wrapped.getName();
}
@Override
public String getIdentifier() {
return wrapped.getIdentifier();
}
@Override
public Stream<? extends WrapperMutableMetaCollection> streamMetaCollections() {
return aliveCollectionsMap.values().stream().map(tuple -> (WrapperMutableMetaCollection) tuple
.v1());
}
@Override
public WrapperMutableMetaCollection getMetaCollectionByName(String collectionName) {
Tuple2<MutableMetaCollection, MetaElementState> tuple = aliveCollectionsMap.get(collectionName);
if (tuple == null) {
return null;
}
return (WrapperMutableMetaCollection) tuple.v1();
}
@Override
public WrapperMutableMetaCollection getMetaCollectionByIdentifier(String collectionIdentifier) {
LOGGER.debug("Looking for a meta collection by the unindexed attribute 'id'. "
+ "Performance lost is expected");
return aliveCollectionsMap.values().stream()
.map(tuple -> (WrapperMutableMetaCollection) tuple.v1())
.filter((collection) -> collection.getIdentifier().equals(collectionIdentifier))
.findAny()
.orElse(null);
}
@Override
public boolean removeMetaCollectionByName(String collectionName) {
WrapperMutableMetaCollection metaCol = getMetaCollectionByName(collectionName);
if (metaCol == null) {
return false;
}
removeMetaCollection(metaCol);
return true;
}
@Override
public boolean removeMetaCollectionByIdentifier(String collectionId) {
WrapperMutableMetaCollection metaCol = getMetaCollectionByIdentifier(collectionId);
if (metaCol == null) {
return false;
}
removeMetaCollection(metaCol);
return true;
}
private void removeMetaCollection(WrapperMutableMetaCollection metaCol) {
collectionsByName.put(metaCol.getName(), new Tuple2<>(metaCol, MetaElementState.REMOVED));
changeConsumer.accept(this);
}
@Override
public String toString() {
return defautToString();
}
private boolean isTransitionAllowed(MetaCollection metaCol, MetaElementState newState) {
MetaElementState oldState;
Tuple2<MutableMetaCollection, MetaElementState> tuple = collectionsByName.get(
metaCol.getName());
if (tuple == null) {
oldState = MetaElementState.NOT_EXISTENT;
} else {
oldState = tuple.v2();
}
oldState.assertLegalTransition(newState);
return true;
}
private void onMetaCollectionChange(WrapperMutableMetaCollection changed) {
assert isTransitionAllowed(changed, MetaElementState.MODIFIED);
collectionsByName.put(changed.getName(), new Tuple2<>(changed, MetaElementState.MODIFIED));
changeConsumer.accept(this);
}
}