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}