001/*
002 * PermissionsEx
003 * Copyright (C) zml and PermissionsEx contributors
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *    http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package ca.stellardrift.permissionsex.impl.backend.memory;
018
019import ca.stellardrift.permissionsex.datastore.DataStoreContext;
020import ca.stellardrift.permissionsex.impl.backend.AbstractDataStore;
021import ca.stellardrift.permissionsex.impl.config.FilePermissionsExConfiguration;
022import ca.stellardrift.permissionsex.datastore.DataStore;
023import ca.stellardrift.permissionsex.datastore.DataStoreFactory;
024import ca.stellardrift.permissionsex.datastore.ProtoDataStore;
025import ca.stellardrift.permissionsex.context.ContextValue;
026import ca.stellardrift.permissionsex.context.ContextInheritance;
027import ca.stellardrift.permissionsex.exception.PermissionsLoadingException;
028import ca.stellardrift.permissionsex.impl.util.PCollections;
029import ca.stellardrift.permissionsex.subject.ImmutableSubjectData;
030import ca.stellardrift.permissionsex.impl.rank.FixedRankLadder;
031import ca.stellardrift.permissionsex.rank.RankLadder;
032import ca.stellardrift.permissionsex.subject.SubjectRef;
033import com.google.auto.service.AutoService;
034import org.checkerframework.checker.nullness.qual.Nullable;
035import org.spongepowered.configurate.BasicConfigurationNode;
036import org.spongepowered.configurate.objectmapping.ConfigSerializable;
037import org.spongepowered.configurate.objectmapping.meta.Comment;
038import org.spongepowered.configurate.objectmapping.meta.Setting;
039
040import java.util.Collection;
041import java.util.Map;
042import java.util.Set;
043import java.util.concurrent.CompletableFuture;
044import java.util.concurrent.ConcurrentHashMap;
045import java.util.concurrent.ConcurrentMap;
046import java.util.function.Function;
047import java.util.stream.Collectors;
048import java.util.stream.Stream;
049
050import static org.spongepowered.configurate.util.UnmodifiableCollections.immutableMapEntry;
051
052/**
053 * A data store backed entirely in memory
054 */
055public class MemoryDataStore extends AbstractDataStore<MemoryDataStore, MemoryDataStore.Config> {
056    @ConfigSerializable
057    static class Config {
058        @Setting
059        @Comment("Whether or not this data store will store subjects being set")
060        boolean track = true;
061    }
062
063    @AutoService(DataStoreFactory.class)
064    public static final class Factory extends AbstractDataStore.Factory<MemoryDataStore, Config> {
065        static final String TYPE = "memory";
066
067        public Factory() {
068            super(TYPE, Config.class, MemoryDataStore::new);
069        }
070    }
071
072
073    private final ConcurrentMap<Map.Entry<String, String>, ImmutableSubjectData> data = new ConcurrentHashMap<>();
074    private final ConcurrentMap<String, RankLadder> rankLadders = new ConcurrentHashMap<>();
075    private volatile ContextInheritance inheritance = new MemoryContextInheritance();
076
077    public static ProtoDataStore<?> create(final String identifier) {
078        try {
079            return DataStoreFactory.forType(Factory.TYPE)
080                .create(identifier, BasicConfigurationNode.root(FilePermissionsExConfiguration.PEX_OPTIONS));
081        } catch (final PermissionsLoadingException ex) {
082            // Not possible to have loading errors when we're not loading anything
083            throw new RuntimeException(ex);
084        }
085    }
086
087    public MemoryDataStore(final DataStoreContext context, final ProtoDataStore<Config> properties) {
088        super(context, properties);
089    }
090
091    @Override
092    protected void load() {
093        this.markFirstRun();
094    }
095
096    @Override
097    public void close() {
098    }
099
100    @Override
101    public CompletableFuture<ImmutableSubjectData> getDataInternal(final String type, final String identifier) {
102        final Map.Entry<String, String> key = immutableMapEntry(type, identifier);
103        ImmutableSubjectData ret = data.get(key);
104        if (ret == null) {
105            ret = new MemorySubjectData();
106            if (config().track) {
107                final @Nullable ImmutableSubjectData existingData = data.putIfAbsent(key, ret);
108                if (existingData != null) {
109                    ret = existingData;
110                }
111            }
112        }
113        return completedFuture(ret);
114    }
115
116    @Override
117    public CompletableFuture<ImmutableSubjectData> setDataInternal(final String type, final String identifier, final ImmutableSubjectData data) {
118        if (config().track) {
119            this.data.put(immutableMapEntry(type, identifier), data);
120        }
121        return completedFuture(data);
122    }
123
124    @Override
125    protected CompletableFuture<RankLadder> getRankLadderInternal(String name) {
126        RankLadder ladder = rankLadders.get(name.toLowerCase());
127        if (ladder == null) {
128            ladder = new FixedRankLadder(name, PCollections.vector());
129        }
130        return completedFuture(ladder);
131    }
132
133    @Override
134    protected CompletableFuture<RankLadder> setRankLadderInternal(final String ladder, final @Nullable RankLadder newLadder) {
135        if (newLadder == null) {
136            this.rankLadders.remove(ladder);
137        } else {
138            this.rankLadders.put(ladder, newLadder);
139        }
140        return completedFuture(newLadder);
141    }
142
143    private <T> CompletableFuture<T> completedFuture(T i) {
144        return CompletableFuture.supplyAsync(() -> i, this.context().asyncExecutor());
145    }
146
147    @Override
148    public CompletableFuture<Boolean> isRegistered(String type, String identifier) {
149        return completedFuture(data.containsKey(immutableMapEntry(type, identifier)));
150    }
151
152    @Override
153    public Stream<String> getAllIdentifiers(final String type) {
154        return data.keySet().stream()
155                .filter(inp -> inp.getKey().equals(type))
156                .map(Map.Entry::getValue);
157    }
158
159    @Override
160    public Set<String> getRegisteredTypes() {
161        return this.data.keySet().stream()
162                .map(Map.Entry::getKey)
163                .collect(PCollections.toPSet());
164    }
165
166    @Override
167    public CompletableFuture<Set<String>> getDefinedContextKeys() {
168        return CompletableFuture.completedFuture(data.values().stream()
169                .flatMap(data -> data.activeContexts().stream())
170                .flatMap(Collection::stream)
171                .map(ContextValue::key)
172                .collect(Collectors.toSet()));
173    }
174
175    @Override
176    public Stream<Map.Entry<SubjectRef<?>, ImmutableSubjectData>> getAll() {
177        return this.data.entrySet().stream()
178                .map(entry -> immutableMapEntry(this.context().deserializeSubjectRef(entry.getKey()), entry.getValue()));
179    }
180
181    @Override
182    public Stream<String> getAllRankLadders() {
183        return this.rankLadders.keySet().stream();
184    }
185
186    @Override
187    public CompletableFuture<Boolean> hasRankLadder(String ladder) {
188        return completedFuture(rankLadders.containsKey(ladder.toLowerCase()));
189    }
190
191    @Override
192    public CompletableFuture<ContextInheritance> getContextInheritanceInternal() {
193        return completedFuture(this.inheritance);
194    }
195
196    @Override
197    public CompletableFuture<ContextInheritance> setContextInheritanceInternal(final ContextInheritance inheritance) {
198        this.inheritance = inheritance;
199        return completedFuture(this.inheritance);
200    }
201
202    @Override
203    protected <T> T performBulkOperationSync(Function<DataStore, T> function) {
204        return function.apply(this);
205    }
206}