1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package dev.metaschema.databind;
7   
8   import java.util.ArrayList;
9   import java.util.Collection;
10  import java.util.HashSet;
11  import java.util.List;
12  import java.util.Set;
13  import java.util.concurrent.locks.Lock;
14  import java.util.concurrent.locks.ReentrantLock;
15  
16  import dev.metaschema.core.model.IBoundObject;
17  import dev.metaschema.core.model.IModule;
18  import dev.metaschema.core.model.IModuleLoader;
19  import dev.metaschema.core.model.MetaschemaException;
20  import dev.metaschema.core.util.CollectionUtil;
21  import dev.metaschema.databind.IBindingContext.IBindingMatcher;
22  import dev.metaschema.databind.model.IBoundDefinitionModelComplex;
23  import dev.metaschema.databind.model.IBoundModule;
24  import edu.umd.cs.findbugs.annotations.NonNull;
25  
26  /**
27   * A module loader strategy that applies post-processors to loaded modules.
28   * <p>
29   * This strategy wraps another {@link IBindingContext.IModuleLoaderStrategy} and
30   * ensures that configured {@link IModuleLoader.IModulePostProcessor} instances
31   * are invoked on each module before it is registered. Post-processing is
32   * applied only once per module, even if the module is referenced multiple
33   * times.
34   *
35   * @since 2.0.0
36   */
37  public class PostProcessingModuleLoaderStrategy
38      implements IBindingContext.IModuleLoaderStrategy {
39    @NonNull
40    private final List<IModuleLoader.IModulePostProcessor> modulePostProcessors;
41    private final IBindingContext.IModuleLoaderStrategy delegate;
42    private final Set<IModule> postProcessedModules = new HashSet<>();
43    private final Lock postProcessedModulesLock = new ReentrantLock();
44  
45    /**
46     * Construct a new post-processing module loader strategy using the default
47     * delegate strategy.
48     *
49     * @param modulePostProcessors
50     *          the post-processors to apply to loaded modules
51     */
52    public PostProcessingModuleLoaderStrategy(
53        @NonNull List<IModuleLoader.IModulePostProcessor> modulePostProcessors) {
54      this(modulePostProcessors, new SimpleModuleLoaderStrategy());
55    }
56  
57    /**
58     * Construct a new post-processing module loader strategy with a custom
59     * delegate.
60     *
61     * @param modulePostProcessors
62     *          the post-processors to apply to loaded modules
63     * @param delegate
64     *          the delegate strategy to use for actual module loading and
65     *          registration
66     */
67    public PostProcessingModuleLoaderStrategy(
68        @NonNull List<IModuleLoader.IModulePostProcessor> modulePostProcessors,
69        @NonNull IBindingContext.IModuleLoaderStrategy delegate) {
70      this.modulePostProcessors = CollectionUtil.unmodifiableList(new ArrayList<>(modulePostProcessors));
71      this.delegate = delegate;
72    }
73  
74    /**
75     * Get the configured module post-processors.
76     *
77     * @return an unmodifiable list of post-processors
78     */
79    @NonNull
80    protected List<IModuleLoader.IModulePostProcessor> getModulePostProcessors() {
81      return modulePostProcessors;
82    }
83  
84    @Override
85    public IBoundModule loadModule(Class<? extends IBoundModule> clazz, IBindingContext bindingContext) {
86      return delegate.loadModule(clazz, bindingContext);
87    }
88  
89    @Override
90    public void postProcessModule(IModule module, IBindingContext bindingContext) {
91      processModule(module);
92      delegate.postProcessModule(module, bindingContext);
93    }
94  
95    @Override
96    public IBoundModule registerModule(IModule module, IBindingContext bindingContext) throws MetaschemaException {
97      IBoundModule boundModule;
98      postProcessedModulesLock.lock();
99      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 }