001/*
002 * SPDX-FileCopyrightText: none
003 * SPDX-License-Identifier: CC0-1.0
004 */
005
006package dev.metaschema.databind;
007
008import java.util.ArrayList;
009import java.util.Collection;
010import java.util.HashSet;
011import java.util.List;
012import java.util.Set;
013import java.util.concurrent.locks.Lock;
014import java.util.concurrent.locks.ReentrantLock;
015
016import dev.metaschema.core.model.IBoundObject;
017import dev.metaschema.core.model.IModule;
018import dev.metaschema.core.model.IModuleLoader;
019import dev.metaschema.core.model.MetaschemaException;
020import dev.metaschema.core.util.CollectionUtil;
021import dev.metaschema.databind.IBindingContext.IBindingMatcher;
022import dev.metaschema.databind.model.IBoundDefinitionModelComplex;
023import dev.metaschema.databind.model.IBoundModule;
024import edu.umd.cs.findbugs.annotations.NonNull;
025
026/**
027 * A module loader strategy that applies post-processors to loaded modules.
028 * <p>
029 * This strategy wraps another {@link IBindingContext.IModuleLoaderStrategy} and
030 * ensures that configured {@link IModuleLoader.IModulePostProcessor} instances
031 * are invoked on each module before it is registered. Post-processing is
032 * applied only once per module, even if the module is referenced multiple
033 * times.
034 *
035 * @since 2.0.0
036 */
037public class PostProcessingModuleLoaderStrategy
038    implements IBindingContext.IModuleLoaderStrategy {
039  @NonNull
040  private final List<IModuleLoader.IModulePostProcessor> modulePostProcessors;
041  private final IBindingContext.IModuleLoaderStrategy delegate;
042  private final Set<IModule> postProcessedModules = new HashSet<>();
043  private final Lock postProcessedModulesLock = new ReentrantLock();
044
045  /**
046   * Construct a new post-processing module loader strategy using the default
047   * delegate strategy.
048   *
049   * @param modulePostProcessors
050   *          the post-processors to apply to loaded modules
051   */
052  public PostProcessingModuleLoaderStrategy(
053      @NonNull List<IModuleLoader.IModulePostProcessor> modulePostProcessors) {
054    this(modulePostProcessors, new SimpleModuleLoaderStrategy());
055  }
056
057  /**
058   * Construct a new post-processing module loader strategy with a custom
059   * delegate.
060   *
061   * @param modulePostProcessors
062   *          the post-processors to apply to loaded modules
063   * @param delegate
064   *          the delegate strategy to use for actual module loading and
065   *          registration
066   */
067  public PostProcessingModuleLoaderStrategy(
068      @NonNull List<IModuleLoader.IModulePostProcessor> modulePostProcessors,
069      @NonNull IBindingContext.IModuleLoaderStrategy delegate) {
070    this.modulePostProcessors = CollectionUtil.unmodifiableList(new ArrayList<>(modulePostProcessors));
071    this.delegate = delegate;
072  }
073
074  /**
075   * Get the configured module post-processors.
076   *
077   * @return an unmodifiable list of post-processors
078   */
079  @NonNull
080  protected List<IModuleLoader.IModulePostProcessor> getModulePostProcessors() {
081    return modulePostProcessors;
082  }
083
084  @Override
085  public IBoundModule loadModule(Class<? extends IBoundModule> clazz, IBindingContext bindingContext) {
086    return delegate.loadModule(clazz, bindingContext);
087  }
088
089  @Override
090  public void postProcessModule(IModule module, IBindingContext bindingContext) {
091    processModule(module);
092    delegate.postProcessModule(module, bindingContext);
093  }
094
095  @Override
096  public IBoundModule registerModule(IModule module, IBindingContext bindingContext) throws MetaschemaException {
097    IBoundModule boundModule;
098    postProcessedModulesLock.lock();
099    try {
100      // process before registering
101      processModule(module);
102
103      boundModule = delegate.registerModule(module, bindingContext);
104
105      // ensure the resulting bound module is not processed again
106      postProcessedModules.add(boundModule);
107    } finally {
108      postProcessedModulesLock.unlock();
109    }
110    return boundModule;
111  }
112
113  /**
114   * Perform post-processing on the provided module.
115   *
116   * @param module
117   *          the module to post process
118   */
119  protected void processModule(@NonNull IModule module) {
120    postProcessedModulesLock.lock();
121    try {
122      if (!postProcessedModules.contains(module)) {
123        for (IModuleLoader.IModulePostProcessor postProcessor : getModulePostProcessors()) {
124          postProcessor.processModule(module);
125        }
126        postProcessedModules.add(module);
127      }
128    } finally {
129      postProcessedModulesLock.unlock();
130    }
131  }
132
133  @Override
134  public Collection<IBindingMatcher> getBindingMatchers() {
135    return delegate.getBindingMatchers();
136  }
137
138  @Override
139  public IBoundDefinitionModelComplex getBoundDefinitionForClass(
140      Class<? extends IBoundObject> clazz,
141      IBindingContext bindingContext) {
142    return delegate.getBoundDefinitionForClass(clazz, bindingContext);
143  }
144}