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}