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;
018
019import ca.stellardrift.permissionsex.PermissionsEngine;
020import ca.stellardrift.permissionsex.context.ContextDefinition;
021import ca.stellardrift.permissionsex.context.ContextDefinitionProvider;
022import ca.stellardrift.permissionsex.context.ContextInheritance;
023import ca.stellardrift.permissionsex.context.SimpleContextDefinition;
024import ca.stellardrift.permissionsex.datastore.ConversionResult;
025import ca.stellardrift.permissionsex.datastore.DataStore;
026import ca.stellardrift.permissionsex.datastore.DataStoreContext;
027import ca.stellardrift.permissionsex.datastore.DataStoreFactory;
028import ca.stellardrift.permissionsex.datastore.ProtoDataStore;
029import ca.stellardrift.permissionsex.impl.backend.memory.MemoryDataStore;
030import ca.stellardrift.permissionsex.impl.config.PermissionsExConfiguration;
031import ca.stellardrift.permissionsex.exception.PEBKACException;
032import ca.stellardrift.permissionsex.exception.PermissionsLoadingException;
033import ca.stellardrift.permissionsex.impl.context.PEXContextDefinition;
034import ca.stellardrift.permissionsex.impl.context.ServerTagContextDefinition;
035import ca.stellardrift.permissionsex.impl.context.TimeContextDefinition;
036import ca.stellardrift.permissionsex.impl.util.CacheListenerHolder;
037import ca.stellardrift.permissionsex.impl.rank.RankLadderCache;
038import ca.stellardrift.permissionsex.impl.subject.SubjectDataCacheImpl;
039import ca.stellardrift.permissionsex.impl.subject.ToDataSubjectRefImpl;
040import ca.stellardrift.permissionsex.impl.logging.DebugPermissionCheckNotifier;
041import ca.stellardrift.permissionsex.impl.subject.LazySubjectRef;
042import ca.stellardrift.permissionsex.impl.util.PCollections;
043import ca.stellardrift.permissionsex.logging.PermissionCheckNotifier;
044import ca.stellardrift.permissionsex.impl.logging.RecordingPermissionCheckNotifier;
045import ca.stellardrift.permissionsex.logging.FormattedLogger;
046import ca.stellardrift.permissionsex.impl.logging.WrappingFormattedLogger;
047import ca.stellardrift.permissionsex.impl.subject.CalculatedSubjectImpl;
048import ca.stellardrift.permissionsex.rank.RankLadderCollection;
049import ca.stellardrift.permissionsex.subject.SubjectRef;
050import ca.stellardrift.permissionsex.subject.SubjectType;
051import ca.stellardrift.permissionsex.impl.subject.SubjectTypeCollectionImpl;
052import ca.stellardrift.permissionsex.impl.util.Util;
053import ca.stellardrift.permissionsex.subject.SubjectTypeCollection;
054import io.leangen.geantyref.TypeToken;
055import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
056import org.checkerframework.checker.nullness.qual.Nullable;
057import org.pcollections.PVector;
058import org.pcollections.TreePVector;
059import org.slf4j.Logger;
060import org.spongepowered.configurate.util.CheckedFunction;
061
062import javax.sql.DataSource;
063import java.io.IOException;
064import java.nio.file.Path;
065import java.sql.SQLException;
066import java.util.Collection;
067import java.util.Collections;
068import java.util.HashSet;
069import java.util.List;
070import java.util.Set;
071import java.util.concurrent.CompletableFuture;
072import java.util.concurrent.ConcurrentHashMap;
073import java.util.concurrent.ConcurrentMap;
074import java.util.concurrent.Executor;
075import java.util.concurrent.atomic.AtomicReference;
076import java.util.function.Consumer;
077import java.util.function.Function;
078import java.util.function.Supplier;
079import java.util.regex.Pattern;
080import java.util.stream.Collectors;
081
082import static ca.stellardrift.permissionsex.impl.Messages.*;
083import static java.util.Objects.requireNonNull;
084
085
086/**
087 * The entry point to the PermissionsEx engine.
088 *
089 * <p>The fastest way to get going with working with subjects is to access a subject type collection
090 * with {@link #subjects(SubjectType)} and request a {@link CalculatedSubjectImpl} to query data
091 * from. Directly working with {@link ToDataSubjectRefImpl}s is another option, preferable if most
092 * of the operations being performed are writes, or querying data directly defined on a subject.</p>
093 *
094 * <p>Keep in mind most of PEX's core data objects are immutable and must be resubmitted to their
095 * holders to apply updates. Most write operations are done asynchronously, and futures are returned
096 * that complete when the backend is finished writing out data. For larger operations, it can be
097 * useful to perform changes within {@link #performBulkOperation(Supplier)}, which will reduce
098 * unnecessary writes to the backing data store in some cases.</p>
099 */
100public class PermissionsEx<P> implements Consumer<ContextInheritance>,
101        ContextDefinitionProvider,
102        PermissionsEngine,
103        DataStoreContext {
104
105    // Mechanics
106    private final FormattedLogger logger;
107    private final Path baseDirectory;
108    private final Executor asyncExecutor;
109    private final CheckedFunction<String, DataSource, SQLException> dataSourceProvider;
110    private final MemoryDataStore transientData;
111    private final SubjectType<SubjectType<?>> defaultsType;
112    private final SubjectType<SubjectType<?>> fallbacksType;
113
114    // Caches
115    private final ConcurrentMap<String, SubjectTypeCollectionImpl<?>> subjectTypeCache = new ConcurrentHashMap<>();
116    private @MonotonicNonNull RankLadderCache rankLadderCache;
117    private volatile @Nullable CompletableFuture<ContextInheritance> cachedInheritance;
118    private final CacheListenerHolder<Boolean, ContextInheritance> cachedInheritanceListeners = new CacheListenerHolder<>();
119
120    // Mutable state
121    private final RecordingPermissionCheckNotifier baseNotifier = new RecordingPermissionCheckNotifier();
122    private volatile PermissionCheckNotifier notifier = baseNotifier;
123    private final ConcurrentMap<String, ContextDefinition<?>> contextTypes = new ConcurrentHashMap<>();
124    private final AtomicReference<@Nullable State<P>> state = new AtomicReference<>();
125
126    private static class State<P> {
127        private final PermissionsExConfiguration<P> config;
128        private final DataStore activeDataStore;
129        private PVector<ConversionResult> availableConversions = TreePVector.empty();
130
131        private State(PermissionsExConfiguration<P> config, DataStore activeDataStore) {
132            this.config = config;
133            this.activeDataStore = activeDataStore;
134        }
135    }
136
137    public PermissionsEx(
138        final Logger logger,
139        final Path baseDirectory,
140        final Executor asyncExecutor,
141        final CheckedFunction<String, @Nullable DataSource, SQLException> databaseProvider
142    ) {
143        this.logger = WrappingFormattedLogger.of(logger, false);
144        this.baseDirectory = baseDirectory;
145        this.asyncExecutor = asyncExecutor;
146        this.dataSourceProvider = databaseProvider;
147        this.registerContextDefinitions(
148                ServerTagContextDefinition.INSTANCE,
149                TimeContextDefinition.BEFORE_TIME,
150                TimeContextDefinition.AFTER_TIME);
151
152        this.defaultsType = this.subjectTypeBuilder("default")
153            .transientHasPriority(false)
154            .build();
155        this.fallbacksType = this.subjectTypeBuilder("fallback").build();
156
157        try {
158            this.transientData = (MemoryDataStore) MemoryDataStore.create("transient").defrost(this);
159        } catch (final PermissionsLoadingException ex) {
160            throw new RuntimeException("Failed to create memory data store!", ex);
161        }
162    }
163
164    private SubjectType.Builder<SubjectType<?>> subjectTypeBuilder(final String id) {
165        return  SubjectType.builder(id, new TypeToken<SubjectType<?>>() {})
166            .serializedBy(SubjectType::name)
167            .deserializedBy(name -> this.subjectTypeCache.get(name).type());
168    }
169
170    private State<P> state() throws IllegalStateException {
171        final @Nullable State<P> ret = this.state.get();
172        if (ret == null) {
173            throw new IllegalStateException("Manager has already been closed!");
174        }
175        return ret;
176    }
177
178    @Override
179    public SubjectTypeCollection<SubjectType<?>> defaults() {
180        return this.subjects(this.defaultsType);
181    }
182
183    @Override
184    public SubjectType<SubjectType<?>> defaultsType() {
185        return this.defaultsType;
186    }
187
188    @Override
189    public SubjectTypeCollection<SubjectType<?>> fallbacks() {
190        return this.subjects(this.fallbacksType);
191    }
192
193    @Override
194    public SubjectType<SubjectType<?>> fallbacksType() {
195        return this.fallbacksType;
196    }
197
198    @Override
199    public void registerSubjectTypes(final SubjectType<?>... types) {
200        for (final SubjectType<?> type : types) {
201            this.subjects(type);
202        }
203    }
204
205    /**
206     * Get the collection of subjects of a given type. No data is loaded in this operation.
207     * Any string is supported as a subject type, but some common types have been provided as constants
208     * in this class for convenience.
209     *
210     * @see PermissionsEngine#defaults()
211     * @param type The type identifier requested. Can be any string
212     * @return The subject type collection
213     */
214    @Override
215    public <I> SubjectTypeCollectionImpl<I> subjects(final SubjectType<I> type) {
216        @SuppressWarnings("unchecked")
217        final SubjectTypeCollectionImpl<I> collection = (SubjectTypeCollectionImpl<I>) this.subjectTypeCache.computeIfAbsent(type.name(),
218            key -> {
219                final SubjectRef<SubjectType<?>> defaultIdentifier = SubjectRef.subject(this.defaultsType, type);
220                return new SubjectTypeCollectionImpl<>(
221                    this,
222                    type,
223                    new SubjectDataCacheImpl<>(type, defaultIdentifier, state().activeDataStore),
224                    new SubjectDataCacheImpl<>(type, defaultIdentifier, transientData));
225            });
226
227        if (!type.equals(collection.type())) {
228            throw new IllegalArgumentException("Provided subject type " + type + " is different from registered type " + collection.type());
229        }
230        return collection;
231    }
232
233    public SubjectType<?> subjectType(final String id) {
234        return this.subjectTypeCache.get(id).type();
235    }
236
237    /**
238     * Get a view of the currently cached subject types
239     *
240     * @return Unmodifiable view of the currently cached subject types
241     */
242    @Override
243    public Collection<SubjectTypeCollectionImpl<?>> loadedSubjectTypes() {
244        return Collections.unmodifiableCollection(this.subjectTypeCache.values());
245    }
246
247    /**
248     * Get all registered subject types in the active data store.
249     * The set is an immutable copy of the backend data.
250     *
251     * @return A set of registered subject types
252     */
253    @Override
254    public Set<SubjectType<?>> knownSubjectTypes() {
255        return this.subjectTypeCache.values().stream().map(SubjectTypeCollectionImpl::type).collect(Collectors.toSet());
256    }
257
258    // -- DataStoreContext -- //
259
260    @Override
261    public PermissionsEngine engine() {
262        return this;
263    }
264
265    @Override
266    public SubjectRef<?> deserializeSubjectRef(final String type, final String name) {
267        final @Nullable SubjectTypeCollectionImpl<?> existingCollection = this.subjectTypeCache.get(type);
268        if (existingCollection == null) {
269            throw new IllegalArgumentException("Unknown subject type " + type);
270        }
271        return deserialize(existingCollection.type(), name);
272    }
273
274    @Override
275    public SubjectRef<?> lazySubjectRef(String type, String identifier) {
276        return new LazySubjectRef(
277                this,
278                requireNonNull(type, "type"),
279                requireNonNull(identifier, "identifier")
280        );
281    }
282
283    private <I> SubjectRef<I> deserialize(final SubjectType<I> type, final String serializedIdent) {
284        return SubjectRef.subject(type, type.parseIdentifier(serializedIdent));
285    }
286
287    @Override
288    public <V> CompletableFuture<V> doBulkOperation(Function<DataStore, CompletableFuture<V>> actor) {
289        return this.state().activeDataStore.performBulkOperation(actor).thenCompose(it -> it);
290    }
291
292    /**
293     * Suppress writes to the data store for the duration of a specific operation. Only really useful for extremely large operations
294     *
295     * @param func The operation to perform
296     * @param <T> The type of data that will be returned
297     * @return A future that completes once all data has been written to the store
298     */
299    public <T> CompletableFuture<T> performBulkOperation(Supplier<CompletableFuture<T>> func) {
300        return state().activeDataStore.performBulkOperation(store -> func.get().join());
301    }
302
303    /**
304     * Access rank ladders through a cached interface
305     *
306     * @return Access to rank ladders
307     */
308    @Override
309    public RankLadderCollection ladders() {
310        return this.rankLadderCache;
311    }
312
313    /**
314     * Imports data into the currently active backend from another configured backend.
315     *
316     * @param dataStoreIdentifier The identifier of the backend to import from
317     * @return A future that completes once the import operation is complete
318     */
319    public CompletableFuture<?> importDataFrom(String dataStoreIdentifier) {
320        final State<P> state = state();
321        final @Nullable ProtoDataStore<?> expected = state.config.getDataStore(dataStoreIdentifier);
322        if (expected == null) {
323            return Util.failedFuture(new IllegalArgumentException("Data store " + dataStoreIdentifier + " is not present"));
324        }
325        return importDataFrom(expected);
326    }
327
328    public CompletableFuture<?> importDataFrom(ConversionResult conversion) {
329        return importDataFrom(conversion.store());
330    }
331
332    private CompletableFuture<?> importDataFrom(final ProtoDataStore<?> request) {
333        final State<P> state = state();
334        final DataStore expected;
335        try {
336            expected = request.defrost(this);
337        } catch (PermissionsLoadingException e) {
338            return Util.failedFuture(e);
339        }
340
341        return state.activeDataStore.performBulkOperation(store -> {
342            CompletableFuture<?> result = CompletableFuture.allOf(expected.getAll().map(subject -> store.setData(subject.getKey(), subject.getValue())).toArray(CompletableFuture[]::new)); // subjects
343            result = result.thenCombine(expected.getContextInheritance(null).thenCompose(store::setContextInheritance), (a, b) -> a); // context inheritance
344            result = expected.getAllRankLadders()
345                    .map(ladder -> expected.getRankLadder(ladder, null).thenCompose(ladderData -> store.setRankLadder(ladder, ladderData))) // combine all rank ladder futures
346                    .reduce(result, (existing, next) -> existing.thenCombine(next, (v, a) -> null), (one, two) -> one.thenCombine(two, (v, a) -> null));
347            return result;
348        }).thenCompose(x -> x);
349    }
350
351    /**
352     * Get the currently active notifier. This object has callbacks triggered on every permission check
353     *
354     * @return The active notifier
355     */
356    public PermissionCheckNotifier getNotifier() {
357        return this.notifier;
358    }
359
360    /**
361     * Get the base notifier that logs any permission checks that gave taken place.
362     * @return the notifier, even if not active
363     */
364    public RecordingPermissionCheckNotifier getRecordingNotifier() {
365        return this.baseNotifier;
366    }
367
368    // TODO: Proper thread-safety
369
370    /**
371     * Know whether or not debug mode is enabled
372     *
373     * @return true if debug mode is enabled
374     */
375    @Override
376    public boolean debugMode() {
377        return this.getNotifier() instanceof DebugPermissionCheckNotifier;
378    }
379
380    /**
381     * Set whether or not debug mode is enabled. Debug mode logs all permission, option, and inheritance
382     * checks made to the console.
383     *
384     * @param debug Whether to enable debug mode
385     * @param filterPattern A pattern to filter which permissions are logged. Null for no filter.
386     */
387    @Override
388    public synchronized void debugMode(boolean debug, final @Nullable Pattern filterPattern) {
389        if (debug) {
390            if (this.notifier instanceof DebugPermissionCheckNotifier) {
391                this.notifier = new DebugPermissionCheckNotifier(this.logger(), ((DebugPermissionCheckNotifier) this.notifier).getDelegate(), filterPattern == null ? null : perm -> filterPattern.matcher(perm).find());
392            } else {
393                this.notifier = new DebugPermissionCheckNotifier(this.logger(), this.notifier, filterPattern == null ? null : perm -> filterPattern.matcher(perm).find());
394            }
395        } else {
396            if (this.notifier instanceof DebugPermissionCheckNotifier) {
397                this.notifier = ((DebugPermissionCheckNotifier) this.notifier).getDelegate();
398            }
399        }
400    }
401
402    /**
403     * Synchronous helper to perform reloads
404     *
405     * @throws PEBKACException If the configuration couldn't be parsed
406     * @throws PermissionsLoadingException When there's an error loading the data store
407     */
408    private void reloadSync() throws PEBKACException, PermissionsLoadingException {
409        try {
410            PermissionsExConfiguration<P> config = state().config.reload();
411            config.validate();
412            prepare(config);
413            // TODO: Throw reload event to cache any relevant subject types
414        } catch (IOException e) {
415            throw new PEBKACException(CONFIG_ERROR_LOAD.tr(e.getLocalizedMessage()));
416        }
417    }
418
419    /**
420     * Initialize the engine.
421     *
422     * May be called even if the engine has been initialized already, with results essentially equivalent to performing a reload
423     *
424     * @param config The configuration to use in this engine
425     * @throws PermissionsLoadingException If an error occurs loading the backend
426     */
427    private void prepare(final PermissionsExConfiguration<P> config) throws PermissionsLoadingException {
428        this.debugMode(config.isDebugEnabled());
429        final DataStore newStore = config.getDefaultDataStore().defrost(this);
430        State<P> newState = new State<>(config, newStore);
431        boolean shouldAnnounceImports = newState.activeDataStore.firstRun();
432        try {
433            newState.config.save();
434        } catch (IOException e) {
435            throw new PermissionsLoadingException(CONFIG_ERROR_SAVE.tr(), e);
436        }
437
438        if (shouldAnnounceImports) {
439            this.logger().warn(CONVERSION_BANNER.tr());
440        }
441
442        PVector<ConversionResult> allResults = TreePVector.empty();
443        for (final DataStoreFactory<?> convertable : DataStoreFactory.all().values()) {
444            if (!(convertable instanceof DataStoreFactory.Convertable))  {
445                continue;
446            }
447            final DataStoreFactory.Convertable<?> prov = ((DataStoreFactory.Convertable<?>) convertable);
448
449            List<ConversionResult> res = prov.listConversionOptions(this);
450            if (!res.isEmpty()) {
451                if (shouldAnnounceImports) {
452                    this.logger().info(CONVERSION_PLUGINHEADER.tr(prov.friendlyName()));
453                    for (ConversionResult result : res) {
454                        this.logger().info(CONVERSION_INSTANCE.tr(result.description(), result.store().identifier()));
455                    }
456                }
457                allResults = allResults.plusAll(res);
458            }
459        }
460        newState.availableConversions = allResults;
461
462        final @Nullable State<P> oldState = this.state.getAndSet(newState);
463        if (oldState != null) {
464            try {
465                oldState.activeDataStore.close();
466            } catch (final Exception ignore) {} // TODO maybe warn?
467        }
468
469        this.rankLadderCache = new RankLadderCache(this.rankLadderCache, newState.activeDataStore);
470        this.subjectTypeCache.forEach((key, val) -> val.update(newState.activeDataStore));
471        this.contextTypes.values().forEach(ctxDef -> {
472            if (ctxDef instanceof PEXContextDefinition<?>) {
473                ((PEXContextDefinition<?>) ctxDef).update(newState.config);
474            }
475        });
476        if (this.cachedInheritance != null) {
477            this.cachedInheritance = null;
478            contextInheritance((Consumer<ContextInheritance>) null).thenAccept(inheritance -> this.cachedInheritanceListeners.call(true, inheritance));
479        }
480
481        // Migrate over legacy subject data
482        newState.activeDataStore.moveData("system", this.defaultsType.name(), this.defaultsType.name(), this.defaultsType.name()).thenRun(() -> {
483            this.logger().info(CONVERSION_RESULT_SUCCESS.tr());
484        });
485    }
486
487    public void initialize(final PermissionsExConfiguration<P> config) throws PermissionsLoadingException {
488        this.prepare(config);
489
490        this.registerSubjectTypes(
491            this.defaultsType,
492            this.fallbacksType
493        );
494    }
495
496    /**
497     * Reload the configuration file in use and refresh backend data
498     *
499     * @return A future that completes once a reload has finished
500     */
501    public CompletableFuture<Void> reload() {
502        return Util.asyncFailableFuture(() -> {
503            reloadSync();
504            return null;
505        }, this.asyncExecutor());
506    }
507
508    /**
509     * Shut down the PEX engine. Once this has been done, no further action can be taken
510     * until the engine is reinitialized with a fresh configuration.
511     */
512    public void close() {
513        final @Nullable State<P> state = this.state.getAndSet(null);
514        if (state != null) {
515            state.activeDataStore.close();
516        }
517    }
518
519    public List<ConversionResult> getAvailableConversions() {
520        return state().availableConversions;
521    }
522
523    @Override
524    public FormattedLogger logger() {
525        return this.logger;
526    }
527
528    // -- Implementation interface proxy methods --
529
530    @Override
531    public Path baseDirectory() {
532        return this.baseDirectory;
533    }
534
535    @Override
536    @Deprecated
537    public @Nullable DataSource dataSourceForUrl(String url) throws SQLException {
538        return this.dataSourceProvider.apply(url);
539    }
540
541    /**
542     * Get an executor to run tasks asynchronously on.
543     *
544     * @return The async executor
545     */
546    @Override
547    public Executor asyncExecutor() {
548        return this.asyncExecutor;
549    }
550
551    public String version() {
552        return PermissionsEx.class.getPackage().getImplementationVersion();
553    }
554
555    /**
556     * Get the current configuration PEX is operating with. This object is immutable.
557     *
558     * @return The current configuration object
559     */
560    public PermissionsExConfiguration<P> config() {
561        return state().config;
562    }
563
564    public DataStore activeDataStore() {
565        return state().activeDataStore;
566    }
567
568    /**
569     * Get context inheritance data.
570     *
571     * <p>The result of the future is immutable -- to take effect, the object returned by any
572     * update methods in {@link ContextInheritance} must be passed to {@link #contextInheritance(ContextInheritance)}.
573     *  It follows that anybody else's changes will not appear in the returned inheritance object -- so if updates are
574     *  desired providing a callback function is important.</p>
575     *
576     * @param listener A callback function that will be triggered whenever there is a change to the context inheritance
577     * @return A future providing the current context inheritance data
578     */
579    @Override
580    public CompletableFuture<ContextInheritance> contextInheritance(final @Nullable Consumer<ContextInheritance> listener) {
581        if (this.cachedInheritance == null) {
582            this.cachedInheritance = state().activeDataStore.getContextInheritance(this);
583        }
584        if (listener != null) {
585            this.cachedInheritanceListeners.addListener(true, listener);
586        }
587        return this.cachedInheritance;
588
589    }
590
591    /**
592     * Update the context inheritance when values have been changed
593     *
594     * @param newInheritance The modified inheritance object
595     * @return A future containing the latest context inheritance object
596     */
597    @Override
598    public CompletableFuture<ContextInheritance> contextInheritance(ContextInheritance newInheritance) {
599        return state().activeDataStore.setContextInheritance(newInheritance);
600    }
601
602    /**
603     * Listener method that handles changes to context inheritance. Should not be called by outside users
604     *
605     * @param newData The new data to replace cached information
606     */
607    @Override
608    public void accept(ContextInheritance newData) {
609        this.cachedInheritance = CompletableFuture.completedFuture(newData);
610        this.cachedInheritanceListeners.call(true, newData);
611    }
612
613    @Override
614    public CompletableFuture<Set<ContextDefinition<?>>> usedContextTypes() {
615        return state().activeDataStore.getDefinedContextKeys().thenCombine(transientData.getDefinedContextKeys(), (persist, trans) -> {
616            final Set<ContextDefinition<?>> build = new HashSet<>();
617            for (final ContextDefinition<?> def : this.contextTypes.values()) {
618                if (persist.contains(def.name()) || trans.contains(def.name())) {
619                    build.add(def);
620                }
621            }
622           return Collections.unmodifiableSet(build);
623        });
624    }
625
626    @Override
627    public <T> boolean registerContextDefinition(ContextDefinition<T> contextDefinition) {
628        if (contextDefinition instanceof PEXContextDefinition<?> && this.state.get() != null) {
629            ((PEXContextDefinition<T>) contextDefinition).update(config());
630        }
631       final @Nullable ContextDefinition<?> possibleOut =  this.contextTypes.putIfAbsent(contextDefinition.name(), contextDefinition);
632        if (possibleOut instanceof SimpleContextDefinition.Fallback) {
633            return this.contextTypes.replace(contextDefinition.name(), possibleOut, contextDefinition);
634        } else {
635            return possibleOut == null;
636        }
637    }
638
639    @Override
640    public int registerContextDefinitions(ContextDefinition<?>... definitions) {
641        int numRegistered = 0;
642        for (ContextDefinition<?> def : definitions) {
643            if (registerContextDefinition(def)) {
644                numRegistered++;
645            }
646        }
647        return numRegistered;
648    }
649
650    @Override
651    public List<ContextDefinition<?>> registeredContextTypes() {
652        return PCollections.asVector(this.contextTypes.values());
653    }
654
655    @Override
656    public @Nullable ContextDefinition<?> contextDefinition(final String definitionKey, final boolean allowFallbacks) {
657        @Nullable ContextDefinition<?> ret = this.contextTypes.get(definitionKey);
658        if (ret == null && allowFallbacks) {
659            ContextDefinition<?> fallback = new SimpleContextDefinition.Fallback(definitionKey);
660            ret = this.contextTypes.putIfAbsent(definitionKey, fallback);
661            if (ret == null) {
662                ret = fallback;
663            }
664        }
665        return ret;
666    }
667}