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; 018 019import ca.stellardrift.permissionsex.PermissionsEngine; 020import ca.stellardrift.permissionsex.datastore.DataStoreContext; 021import ca.stellardrift.permissionsex.context.ContextValue; 022import ca.stellardrift.permissionsex.impl.util.CacheListenerHolder; 023import ca.stellardrift.permissionsex.exception.PermissionsException; 024import ca.stellardrift.permissionsex.context.ContextInheritance; 025import ca.stellardrift.permissionsex.subject.ImmutableSubjectData; 026import ca.stellardrift.permissionsex.datastore.DataStore; 027import ca.stellardrift.permissionsex.datastore.DataStoreFactory; 028import ca.stellardrift.permissionsex.datastore.ProtoDataStore; 029import ca.stellardrift.permissionsex.exception.PermissionsLoadingException; 030import ca.stellardrift.permissionsex.rank.RankLadder; 031import ca.stellardrift.permissionsex.impl.util.Util; 032import net.kyori.adventure.text.Component; 033import org.checkerframework.checker.nullness.qual.Nullable; 034import org.spongepowered.configurate.ConfigurationNode; 035import org.spongepowered.configurate.serialize.SerializationException; 036import org.spongepowered.configurate.util.CheckedSupplier; 037import org.spongepowered.configurate.util.UnmodifiableCollections; 038 039import java.util.Collections; 040import java.util.Map; 041import java.util.concurrent.CompletableFuture; 042import java.util.function.BiFunction; 043import java.util.function.Consumer; 044import java.util.function.Function; 045import java.util.stream.Stream; 046 047import static java.util.Objects.requireNonNull; 048 049/** 050 * Base implementation of a data store that provides common points for other data stores to hook into. 051 * 052 * @param <T> self type 053 * @param <C> config type 054 */ 055public abstract class AbstractDataStore<T extends AbstractDataStore<T, C>, C> implements DataStore { 056 private final DataStoreContext context; 057 private final ProtoDataStore<C> properties; 058 private boolean firstRun; 059 protected final CacheListenerHolder<Map.Entry<String, String>, ImmutableSubjectData> listeners = new CacheListenerHolder<>(); 060 protected final CacheListenerHolder<String, RankLadder> rankLadderListeners = new CacheListenerHolder<>(); 061 protected final CacheListenerHolder<Boolean, ContextInheritance> contextInheritanceListeners = new CacheListenerHolder<>(); 062 063 /** 064 * Create the data store. 065 * 066 * <p>No actual loading or creation of threads should be performed here.</p> 067 * 068 * @param context the data store context 069 * @param props properties defining this data store 070 */ 071 protected AbstractDataStore(final DataStoreContext context, final ProtoDataStore<C> props) { 072 this.context = context; 073 this.properties = props; 074 } 075 076 @Override 077 public String name() { 078 return this.properties.identifier(); 079 } 080 081 @Override 082 public boolean firstRun() { 083 return this.firstRun; 084 } 085 086 /** 087 * Mark that this is the data store's first run. 088 */ 089 protected void markFirstRun() { 090 this.firstRun = true; 091 } 092 093 protected final PermissionsEngine engine() { 094 return this.context.engine(); 095 } 096 097 protected final DataStoreContext context() { 098 return this.context; 099 } 100 101 protected C config() { 102 return this.properties.config(); 103 } 104 105 /** 106 * Load any data necessary to initialize this data store. 107 * 108 * @throws PermissionsLoadingException if unable to load 109 */ 110 protected abstract void load() throws PermissionsLoadingException; 111 112 @Override 113 public final CompletableFuture<ImmutableSubjectData> getData(final String type, final String identifier, final @Nullable Consumer<ImmutableSubjectData> listener) { 114 requireNonNull(type, "type"); 115 requireNonNull(identifier, "identifier"); 116 117 final CompletableFuture<ImmutableSubjectData> ret = getDataInternal(type, identifier); 118 ret.thenRun(() -> { 119 if (listener != null) { 120 listeners.addListener(UnmodifiableCollections.immutableMapEntry(type, identifier), listener); 121 } 122 }); 123 return ret; 124 } 125 126 @Override 127 public final CompletableFuture<ImmutableSubjectData> setData(final String type, final String identifier, final @Nullable ImmutableSubjectData data) { 128 requireNonNull(type, "type"); 129 requireNonNull(identifier, "identifier"); 130 131 final Map.Entry<String, String> lookupKey = UnmodifiableCollections.immutableMapEntry(type, identifier); 132 return setDataInternal(type, identifier, data) 133 .thenApply(newData -> { 134 if (newData != null) { 135 listeners.call(lookupKey, newData); 136 } 137 return newData; 138 }); 139 } 140 141 protected <V> CompletableFuture<V> runAsync(CheckedSupplier<V, ?> supplier) { 142 return Util.asyncFailableFuture(supplier, this.context.asyncExecutor()); 143 } 144 145 protected CompletableFuture<Void> runAsync(Runnable run) { 146 return CompletableFuture.runAsync(run, this.context.asyncExecutor()); 147 } 148 149 /** 150 * Apply default data when creating a new file. 151 * 152 * This consists of 153 * <ul> 154 * <li>Modifying default data to give all permissions to a user connecting locally</li> 155 * </ul> 156 */ 157 protected final void applyDefaultData() { 158 getData(PermissionsEngine.SUBJECTS_DEFAULTS.name(), PermissionsEngine.SUBJECTS_DEFAULTS.name(), null) 159 .thenApply(data -> data.withSegment(Collections.singleton(new ContextValue<>("localip", "127.0.0.1")), s -> s.withFallbackPermission(1))) 160 .thenCompose(data -> setData(PermissionsEngine.SUBJECTS_DEFAULTS.name(), PermissionsEngine.SUBJECTS_DEFAULTS.name(), data)); 161 } 162 163 protected abstract CompletableFuture<ImmutableSubjectData> getDataInternal(String type, String identifier); 164 165 protected abstract CompletableFuture<ImmutableSubjectData> setDataInternal(String type, String identifier, @Nullable ImmutableSubjectData data); 166 167 @Override 168 public final Stream<Map.Entry<String, ImmutableSubjectData>> getAll(final String type) { 169 requireNonNull(type, "type"); 170 return getAllIdentifiers(type) 171 .map(id -> UnmodifiableCollections.immutableMapEntry(id, getData(type, id, null).join())); 172 } 173 174 @Override 175 public final <V> CompletableFuture<V> performBulkOperation(final Function<DataStore, V> function) { 176 return Util.asyncFailableFuture(() -> performBulkOperationSync(function), this.context.asyncExecutor()); 177 } 178 179 @Override 180 public final CompletableFuture<RankLadder> getRankLadder(final String ladderName, final @Nullable Consumer<RankLadder> listener) { 181 requireNonNull(ladderName, "ladderName"); 182 CompletableFuture<RankLadder> ladder = getRankLadderInternal(ladderName); 183 if (listener != null) { 184 rankLadderListeners.addListener(ladderName.toLowerCase(), listener); 185 } 186 return ladder; 187 } 188 189 @Override 190 public final CompletableFuture<RankLadder> setRankLadder(final String identifier, final @Nullable RankLadder ladder) { 191 return setRankLadderInternal(identifier, ladder) 192 .thenApply(newData -> { 193 if (newData != null) { 194 rankLadderListeners.call(identifier, newData); 195 } 196 return newData; 197 }); 198 } 199 200 201 protected abstract CompletableFuture<RankLadder> getRankLadderInternal(String ladder); 202 protected abstract CompletableFuture<RankLadder> setRankLadderInternal(String ladder, final @Nullable RankLadder newLadder); 203 204 @Override 205 public final CompletableFuture<ContextInheritance> getContextInheritance(final @Nullable Consumer<ContextInheritance> listener) { 206 CompletableFuture<ContextInheritance> inheritance = getContextInheritanceInternal(); 207 if (listener != null) { 208 contextInheritanceListeners.addListener(true, listener); 209 } 210 return inheritance; 211 } 212 213 214 @Override 215 public final CompletableFuture<ContextInheritance> setContextInheritance(ContextInheritance contextInheritance) { 216 return setContextInheritanceInternal(contextInheritance) 217 .thenApply(newData -> { 218 if (newData != null) { 219 contextInheritanceListeners.call(true, newData); 220 } 221 return newData; 222 }); 223 } 224 225 226 227 protected abstract CompletableFuture<ContextInheritance> getContextInheritanceInternal(); 228 protected abstract CompletableFuture<ContextInheritance> setContextInheritanceInternal(ContextInheritance contextInheritance); 229 230 /** 231 * Internally perform a bulk operation. 232 * 233 * <p>Safe to call blocking operations from this method -- we're running it asyncly.</p> 234 * 235 * @param function The function to run 236 * @param <V> The 237 * @return result of operation, after a save 238 * @throws Exception if thrown by operation 239 */ 240 protected abstract <V> V performBulkOperationSync(Function<DataStore, V> function) throws Exception; 241 242 @Override 243 public CompletableFuture<Void> moveData(String oldType, String oldIdentifier, String newType, String newIdentifier) { 244 return isRegistered(oldType, oldIdentifier).thenCombine(isRegistered(newType, newIdentifier), (oldRegistered, newRegistered) -> { 245 if (oldRegistered && !newRegistered) { 246 return getData(oldType, oldIdentifier, null) 247 .thenCompose(oldData -> setData(newType, newIdentifier, oldData)) 248 .thenCompose(newData -> setData(oldType, oldIdentifier, null)) 249 .thenApply(inp -> (Void) null); 250 } else { 251 return Util.<Void>failedFuture(new PermissionsException(Messages.DATASTORE_MOVE_ERROR.tr())); 252 } 253 254 }).thenCompose(future -> future); 255 } 256 257 public abstract static class Factory<T extends AbstractDataStore<T, C>, C> implements DataStoreFactory<C> { 258 private final String name; 259 private final Class<C> configType; 260 /** 261 * Function taking the name of a data store instance and its configuration, and creating the appropriate new object. 262 */ 263 private final BiFunction<DataStoreContext, ProtoDataStore<C>, T> newInstanceSupplier; 264 private final Component friendlyName; 265 266 protected Factory(final String name, final Class<C> clazz, final BiFunction<DataStoreContext, ProtoDataStore<C>, T> newInstanceSupplier) { 267 requireNonNull(name, "name"); 268 requireNonNull(clazz, "clazz"); 269 this.name = name; 270 this.friendlyName = Component.text(name); 271 this.configType = clazz; 272 this.newInstanceSupplier = newInstanceSupplier; 273 } 274 275 @Override 276 public Component friendlyName() { 277 return this.friendlyName; 278 } 279 280 @Override 281 public final String name() { 282 return this.name; 283 } 284 285 @Override 286 public final ProtoDataStore<C> create(String identifier, ConfigurationNode config) throws PermissionsLoadingException { 287 try { 288 final C dataStoreConfig = config.get(this.configType); 289 return ProtoDataStore.of(identifier, dataStoreConfig, this); 290 } catch (SerializationException e) { 291 throw new PermissionsLoadingException(Messages.DATASTORE_ERROR_DESERIALIZE.tr(identifier), e); 292 } 293 } 294 295 @Override 296 public DataStore defrost(final DataStoreContext context, final ProtoDataStore<C> properties) throws PermissionsLoadingException { 297 requireNonNull(context, "context"); 298 final T store = this.newInstanceSupplier.apply(context, properties); 299 store.load(); 300 return store; 301 } 302 303 @Override 304 public void serialize(final ConfigurationNode node, final ProtoDataStore<C> protoStore) throws SerializationException { 305 node.set(protoStore.config()); 306 } 307 308 } 309}