/* * 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 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.stream.Stream; /** * */ public class WrapperMutableMetaSnapshot implements MutableMetaSnapshot { private static final Logger LOGGER = LogManager.getLogger(WrapperMutableMetaSnapshot.class); private final ImmutableMetaSnapshot wrapped; private final HashMap<String, Tuple2<MutableMetaDatabase, MetaElementState>> dbsByName; private final Map<String, Tuple2<MutableMetaDatabase, MetaElementState>> aliveMap; public WrapperMutableMetaSnapshot(ImmutableMetaSnapshot wrapped) { this.wrapped = wrapped; this.dbsByName = new HashMap<>(); wrapped.streamMetaDatabases().forEach((db) -> { WrapperMutableMetaDatabase mutable = createMetaDatabase(db); dbsByName.put(db.getName(), new Tuple2<>(mutable, MetaElementState.NOT_CHANGED)); }); aliveMap = Maps.filterValues(dbsByName, tuple -> tuple.v2().isAlive()); } protected WrapperMutableMetaDatabase createMetaDatabase(ImmutableMetaDatabase immutable) { return new WrapperMutableMetaDatabase(immutable, this::onMetaDatabaseChange); } @Override public MutableMetaDatabase addMetaDatabase(String dbName, String dbId) throws IllegalArgumentException { if (getMetaDatabaseByName(dbName) != null) { throw new IllegalArgumentException("There is another database whose name is " + dbName); } assert getMetaDatabaseByIdentifier(dbId) == null : "There is another database whose id is " + dbId; WrapperMutableMetaDatabase result = createMetaDatabase( new ImmutableMetaDatabase(dbName, dbId, Collections.emptyList()) ); dbsByName.put(dbName, new Tuple2<>(result, MetaElementState.ADDED)); return result; } @Override public Iterable<Tuple2<MutableMetaDatabase, MetaElementState>> getModifiedDatabases() { return Maps.filterValues(dbsByName, tuple -> tuple.v2().hasChanged()) .values(); } @Override public boolean hasChanged() { return dbsByName.values().stream().anyMatch(tuple -> tuple.v2.hasChanged()); } @Override public ImmutableMetaSnapshot immutableCopy() { if (dbsByName.values().stream().noneMatch(tuple -> tuple.v2().hasChanged())) { return wrapped; } else { ImmutableMetaSnapshot.Builder builder = new ImmutableMetaSnapshot.Builder(wrapped); dbsByName.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 Stream<? extends WrapperMutableMetaDatabase> streamMetaDatabases() { return aliveMap.values().stream().map(tuple -> (WrapperMutableMetaDatabase) tuple.v1()); } @Override public WrapperMutableMetaDatabase getMetaDatabaseByName(String dbName) { Tuple2<MutableMetaDatabase, MetaElementState> tuple = aliveMap.get(dbName); if (tuple == null) { return null; } return (WrapperMutableMetaDatabase) tuple.v1(); } @Override public WrapperMutableMetaDatabase getMetaDatabaseByIdentifier(String dbIdentifier) { LOGGER.debug("Looking for a meta collection by the unindexed attribute 'id'. " + "Performance lost is expected"); return aliveMap.values().stream() .map(tuple -> (WrapperMutableMetaDatabase) tuple.v1()) .filter((collection) -> collection.getIdentifier().equals(dbIdentifier)) .findAny() .orElse(null); } @Override public boolean removeMetaDatabaseByName(String dbName) { WrapperMutableMetaDatabase metaDb = getMetaDatabaseByName(dbName); if (metaDb == null) { return false; } removeMetaDatabase(metaDb); return true; } @Override public boolean removeMetaDatabaseByIdentifier(String dbId) { WrapperMutableMetaDatabase metaDb = getMetaDatabaseByIdentifier(dbId); if (metaDb == null) { return false; } removeMetaDatabase(metaDb); return true; } private void removeMetaDatabase(WrapperMutableMetaDatabase metaCol) { dbsByName.put(metaCol.getName(), new Tuple2<>(metaCol, MetaElementState.REMOVED)); } private boolean isTransitionAllowed(MetaDatabase metaDb, MetaElementState newState) { MetaElementState oldState; Tuple2<MutableMetaDatabase, MetaElementState> tuple = dbsByName.get(metaDb.getName()); if (tuple == null) { oldState = MetaElementState.NOT_EXISTENT; } else { oldState = tuple.v2(); } oldState.assertLegalTransition(newState); return true; } private void onMetaDatabaseChange(WrapperMutableMetaDatabase changed) { assert isTransitionAllowed(changed, MetaElementState.MODIFIED); dbsByName.put(changed.getName(), new Tuple2<>(changed, MetaElementState.MODIFIED)); } }