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