/**
* PermissionsEx
* Copyright (C) zml and PermissionsEx contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ninja.leaping.permissionsex.backend;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.common.util.concurrent.Futures;
import ninja.leaping.configurate.ConfigurationNode;
import ninja.leaping.configurate.objectmapping.ObjectMapper;
import ninja.leaping.configurate.objectmapping.ObjectMappingException;
import ninja.leaping.permissionsex.PermissionsEx;
import ninja.leaping.permissionsex.data.CacheListenerHolder;
import ninja.leaping.permissionsex.data.Caching;
import ninja.leaping.permissionsex.data.ContextInheritance;
import ninja.leaping.permissionsex.data.ImmutableSubjectData;
import ninja.leaping.permissionsex.exception.PermissionsLoadingException;
import ninja.leaping.permissionsex.rank.RankLadder;
import ninja.leaping.permissionsex.util.ThrowingSupplier;
import ninja.leaping.permissionsex.util.Util;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
import static ninja.leaping.permissionsex.util.Translations.t;
/**
* Base implementation of a data store that provides common points for other data stores to hook into.
*/
public abstract class AbstractDataStore implements DataStore {
private PermissionsEx manager;
private final Factory factory;
private final CacheListenerHolder<Map.Entry<String, String>, ImmutableSubjectData> listeners = new CacheListenerHolder<>();
private final CacheListenerHolder<String, RankLadder> rankLadderListeners = new CacheListenerHolder<>();
private final CacheListenerHolder<Boolean, ContextInheritance> contextInheritanceListeners = new CacheListenerHolder<>();
protected AbstractDataStore(Factory factory) {
if (!factory.expectedClazz.equals(getClass())) {
throw new ExceptionInInitializerError("Data store factory for wrong class " + factory.expectedClazz + " provided to a " + getClass());
}
this.factory = factory;
}
protected PermissionsEx getManager() {
return this.manager;
}
@Override
public final void initialize(PermissionsEx core) throws PermissionsLoadingException {
this.manager = core;
initializeInternal();
}
protected abstract void initializeInternal() throws PermissionsLoadingException;
@Override
public final CompletableFuture<ImmutableSubjectData> getData(String type, String identifier, Caching<ImmutableSubjectData> listener) {
Objects.requireNonNull(type, "type");
Objects.requireNonNull(identifier, "identifier");
CompletableFuture<ImmutableSubjectData> ret = getDataInternal(type, identifier);
ret.thenRun(() -> {
if (listener != null) {
listeners.addListener(Maps.immutableEntry(type, identifier), listener);
}
});
return ret;
}
@Override
public final CompletableFuture<ImmutableSubjectData> setData(String type, String identifier, ImmutableSubjectData data) {
Objects.requireNonNull(type, "type");
Objects.requireNonNull(identifier, "identifier");
final Map.Entry<String, String> lookupKey = Maps.immutableEntry(type, identifier);
return setDataInternal(type, identifier, data)
.thenApply(newData -> {
if (newData != null) {
listeners.call(lookupKey, newData);
}
return newData;
});
}
protected <T> CompletableFuture<T> runAsync(ThrowingSupplier<T, ?> supplier) {
return Util.asyncFailableFuture(supplier, getManager().getAsyncExecutor());
}
protected CompletableFuture<Void> runAsync(Runnable run) {
return CompletableFuture.runAsync(run, getManager().getAsyncExecutor());
}
/**
* Apply default data when creating a new file.
*
* This consists of
* <ul>
* <li>Modifying default data to give all permissions to a user connecting locally</li>
* </ul>
*/
protected final void applyDefaultData() {
getData(PermissionsEx.SUBJECTS_DEFAULTS, PermissionsEx.SUBJECTS_DEFAULTS, null)
.thenApply(data -> data.setDefaultValue(ImmutableSet.of(Maps.immutableEntry("localip", "127.0.0.1")), 1))
.thenCompose(data -> setData(PermissionsEx.SUBJECTS_DEFAULTS, PermissionsEx.SUBJECTS_DEFAULTS, data));
}
protected abstract CompletableFuture<ImmutableSubjectData> getDataInternal(String type, String identifier);
protected abstract CompletableFuture<ImmutableSubjectData> setDataInternal(String type, String identifier, ImmutableSubjectData data);
@Override
public final Iterable<Map.Entry<String, ImmutableSubjectData>> getAll(final String type) {
Objects.requireNonNull(type, "type");
return Iterables.transform(getAllIdentifiers(type),
input -> Maps.immutableEntry(input, Futures.getUnchecked(getData(type, input, null))));
}
@Override
public final <T> CompletableFuture<T> performBulkOperation(final Function<DataStore, T> function) {
return Util.asyncFailableFuture(() -> performBulkOperationSync(function), getManager().getAsyncExecutor());
}
@Override
public final CompletableFuture<RankLadder> getRankLadder(String ladderName, Caching<RankLadder> listener) {
Objects.requireNonNull(ladderName, "ladderName");
CompletableFuture<RankLadder> ladder = getRankLadderInternal(ladderName);
if (listener != null) {
rankLadderListeners.addListener(ladderName.toLowerCase(), listener);
}
return ladder;
}
@Override
public final CompletableFuture<RankLadder> setRankLadder(final String identifier, RankLadder ladder) {
return setRankLadderInternal(identifier, ladder)
.thenApply(newData -> {
if (newData != null) {
rankLadderListeners.call(identifier, newData);
}
return newData;
});
}
protected abstract CompletableFuture<RankLadder> getRankLadderInternal(String ladder);
protected abstract CompletableFuture<RankLadder> setRankLadderInternal(String ladder, RankLadder newLadder);
@Override
public final CompletableFuture<ContextInheritance> getContextInheritance(Caching<ContextInheritance> listener) {
CompletableFuture<ContextInheritance> inheritance = getContextInheritanceInternal();
if (listener != null) {
contextInheritanceListeners.addListener(true, listener);
}
return inheritance;
}
@Override
public final CompletableFuture<ContextInheritance> setContextInheritance(ContextInheritance contextInheritance) {
return setContextInheritanceInternal(contextInheritance)
.thenApply(newData -> {
if (newData != null) {
contextInheritanceListeners.call(true, newData);
}
return newData;
});
}
protected abstract CompletableFuture<ContextInheritance> getContextInheritanceInternal();
protected abstract CompletableFuture<ContextInheritance> setContextInheritanceInternal(ContextInheritance contextInheritance);
/**
* Internally perform a bulk operation. Safe to call blocking operations from this method -- we're running it asyncly.
*
* @param function The function to run
* @param <T> The
* @return
* @throws Exception
*/
protected abstract <T> T performBulkOperationSync(Function<DataStore, T> function) throws Exception;
@Override
@SuppressWarnings("unchecked") // Correct types are verified in the constructor
public String serialize(ConfigurationNode node) throws PermissionsLoadingException {
Objects.requireNonNull(node, "node");
try {
((ObjectMapper) factory.mapper).bind(this).serialize(node);
} catch (ObjectMappingException e) {
throw new PermissionsLoadingException(t("Error while serializing backend %s", node.getKey()), e);
}
return factory.type;
}
protected static class Factory implements DataStoreFactory {
private final String type;
private final Class<? extends AbstractDataStore> expectedClazz;
private final ObjectMapper<? extends AbstractDataStore> mapper;
public Factory(final String type, Class<? extends AbstractDataStore> clazz) {
Objects.requireNonNull(type, "type");
Objects.requireNonNull(clazz, "clazz");
this.type = type;
this.expectedClazz = clazz;
try {
mapper = ObjectMapper.forClass(clazz);
} catch (ObjectMappingException e) {
throw new ExceptionInInitializerError(e);
}
}
@Override
public DataStore createDataStore(String identifier, ConfigurationNode config) throws PermissionsLoadingException {
try {
return mapper.bindToNew().populate(config);
} catch (ObjectMappingException e) {
throw new PermissionsLoadingException(t("Error while deserializing backend %s", identifier), e);
}
}
}
}