/** * 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.sponge; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Maps; import ninja.leaping.permissionsex.data.Change; import ninja.leaping.permissionsex.data.ImmutableSubjectData; import ninja.leaping.permissionsex.data.SubjectDataReference; import ninja.leaping.permissionsex.util.GuavaCollectors; import org.spongepowered.api.service.permission.Subject; import org.spongepowered.api.service.context.Context; import org.spongepowered.api.service.permission.SubjectData; import org.spongepowered.api.util.Tristate; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutionException; /** * Wrapper around ImmutableSubjectData that writes to backend each change */ class PEXSubjectData implements SubjectData { private final PermissionsExPlugin plugin; private SubjectDataReference data; private final ConcurrentMap<Set<Map.Entry<String, String>>, List<Subject>> parentsCache = new ConcurrentHashMap<>(); public PEXSubjectData(SubjectDataReference data, PermissionsExPlugin plugin) throws ExecutionException { this.plugin = plugin; this.data = data; this.data.onUpdate(this::clearCache); } /** * This is valid because all Contexts are Map.Entries * * @param input The input set * @return A properly casted set */ @SuppressWarnings("unchecked") static Set<Map.Entry<String, String>> parSet(Set<Context> input) { return (Set) input; } private static <T> Map<Set<Context>, T> tKeys(Map<Set<Map.Entry<String, String>>, T> input) { final ImmutableMap.Builder<Set<Context>, T> ret = ImmutableMap.builder(); for (Map.Entry<Set<Map.Entry<String, String>>, T> ent : input.entrySet()) { ret.put(ent.getKey().stream() .map(ctx -> ctx instanceof Context ? (Context) ctx : new Context(ctx.getKey(), ctx.getValue())) .collect(GuavaCollectors.toImmutableSet()), ent.getValue()); } return ret.build(); } private boolean wasSuccess(CompletableFuture<Change<ImmutableSubjectData>> future) { if (future.isDone()) { try { future.get(); return true; } catch (InterruptedException | ExecutionException e) { return false; } } else { return true; } } private void clearCache(ImmutableSubjectData newData) { synchronized (parentsCache) { parentsCache.clear(); } } @Override public Map<Set<Context>, Map<String, String>> getAllOptions() { return tKeys(this.data.get().getAllOptions()); } @Override public Map<String, String> getOptions(Set<Context> contexts) { return this.data.get().getOptions(parSet(contexts)); } @Override public boolean setOption(final Set<Context> contexts, final String key, final String value) { return wasSuccess(data.update(input -> input.setOption(parSet(contexts), key, value))); } @Override public boolean clearOptions(final Set<Context> contexts) { return wasSuccess(data.update(input -> input.clearOptions(parSet(contexts)))); } @Override public boolean clearOptions() { return wasSuccess(data.update(ImmutableSubjectData::clearOptions)); } @Override public Map<Set<Context>, Map<String, Boolean>> getAllPermissions() { return Maps.transformValues(tKeys(data.get().getAllPermissions()), map -> Maps.transformValues(map, i -> i > 0)); } @Override public Map<String, Boolean> getPermissions(Set<Context> set) { return Maps.transformValues(data.get().getPermissions(parSet(set)), value -> value > 0); } @Override public boolean setPermission(final Set<Context> set, final String s, Tristate tristate) { final int val; switch (tristate) { case TRUE: val = 1; break; case FALSE: val = -1; break; case UNDEFINED: val = 0; break; default: throw new IllegalStateException("Unknown tristate provided " + tristate); } return wasSuccess(data.update(input -> input.setPermission(parSet(set), s, val))); } @Override public boolean clearPermissions() { return wasSuccess(data.update(ImmutableSubjectData::clearPermissions)); } @Override public boolean clearPermissions(final Set<Context> set) { return wasSuccess(data.update(input -> input.clearPermissions(parSet(set)))); } @Override public Map<Set<Context>, List<Subject>> getAllParents() { synchronized (parentsCache) { data.get().getActiveContexts().forEach(this::getParentsInternal); return tKeys(parentsCache); } } @Override public List<Subject> getParents(Set<Context> set) { return getParentsInternal(parSet(set)); } public List<Subject> getParentsInternal(Set<Map.Entry<String, String>> set) { List<Subject> parents = parentsCache.get(set); if (parents == null) { synchronized (parentsCache) { List<Map.Entry<String, String>> rawParents = data.get().getParents(set); if (rawParents == null) { parents = ImmutableList.of(); } else { parents = new ArrayList<>(rawParents.size()); for (Map.Entry<String, String> ent : rawParents) { parents.add(plugin.getSubjects(ent.getKey()).get(ent.getValue())); // TODO: Parallelize } } List<Subject> existingParents = parentsCache.putIfAbsent(set, parents); if (existingParents != null) { parents = existingParents; } } } return parents; } @Override public boolean addParent(final Set<Context> set, final Subject subject) { return wasSuccess(data.update(input -> input.addParent(parSet(set), subject.getContainingCollection().getIdentifier(), subject.getIdentifier()))); } @Override public boolean removeParent(final Set<Context> set, final Subject subject) { return wasSuccess(data.update(input -> input.removeParent(parSet(set), subject.getContainingCollection().getIdentifier(), subject.getIdentifier()))); } @Override public boolean clearParents() { return wasSuccess(data.update(ImmutableSubjectData::clearParents)); } @Override public boolean clearParents(final Set<Context> set) { return wasSuccess(data.update(input -> input.clearParents(parSet(set)))); } }