diff --git a/source/java/org/alfresco/repo/web/scripts/ExtensibilityContainer.java b/source/java/org/alfresco/repo/web/scripts/ExtensibilityContainer.java index a7730fb6db..1b77dbf91d 100644 --- a/source/java/org/alfresco/repo/web/scripts/ExtensibilityContainer.java +++ b/source/java/org/alfresco/repo/web/scripts/ExtensibilityContainer.java @@ -23,425 +23,847 @@ * along with Alfresco. If not, see . * #L% */ -package org.alfresco.repo.web.scripts; - -import java.io.IOException; -import java.io.Writer; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.ResourceBundle; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.dom4j.Element; -import org.springframework.extensions.config.ConfigImpl; -import org.springframework.extensions.config.ConfigSection; -import org.springframework.extensions.config.ConfigService; -import org.springframework.extensions.config.evaluator.Evaluator; -import org.springframework.extensions.config.xml.XMLConfigService; -import org.springframework.extensions.config.xml.elementreader.ConfigElementReader; -import org.springframework.extensions.surf.extensibility.BasicExtensionModule; -import org.springframework.extensions.surf.extensibility.ExtensibilityModel; -import org.springframework.extensions.surf.extensibility.HandlesExtensibility; -import org.springframework.extensions.surf.extensibility.WebScriptExtensibilityModuleHandler; -import org.springframework.extensions.surf.extensibility.impl.ExtensibilityModelImpl; -import org.springframework.extensions.surf.extensibility.impl.MarkupDirective; -import org.springframework.extensions.webscripts.Authenticator; -import org.springframework.extensions.webscripts.ExtendedScriptConfigModel; -import org.springframework.extensions.webscripts.ExtendedTemplateConfigModel; -import org.springframework.extensions.webscripts.ScriptConfigModel; -import org.springframework.extensions.webscripts.TemplateConfigModel; -import org.springframework.extensions.webscripts.WebScriptPropertyResourceBundle; -import org.springframework.extensions.webscripts.WebScriptRequest; -import org.springframework.extensions.webscripts.WebScriptResponse; - -/** - *

A simple extensibility {@link org.springframework.extensions.webscripts.Container} for processing WebScripts. This extends the {@link RepositoryContainer} and - * implements the {@link HandlesExtensibility} interface to provide extensibility capabilities.

- * - * @author David Draper - */ -public class ExtensibilityContainer extends RepositoryContainer implements HandlesExtensibility -{ - private static final Log logger = LogFactory.getLog(ExtensibilityContainer.class); - - public boolean isExtensibilitySuppressed() - { - return false; - } - - /** - *

Opens a new {@link ExtensibilityModel}, defers execution to the extended {@link RepositoryContainer} and - * then closes the {@link ExtensibilityModel}.

- */ - @Override - public void executeScript(WebScriptRequest scriptReq, - WebScriptResponse scriptRes, - Authenticator auth) throws IOException - { - ExtensibilityModel extModel = this.openExtensibilityModel(); - try - { - super.executeScript(scriptReq, scriptRes, auth); - } - finally - { - // It's only necessary to close the model if it's actually been used. Not all WebScripts will make use of the - // model. An example of this would be the StreamContent WebScript. It is important not to attempt to close - // an unused model since the WebScript executed may have already flushed the response if it has overridden - // the default .execute() method. - if (this.modelUsed.get()) - { - try - { - this.closeExtensibilityModel(extModel, scriptRes.getWriter()); - } - catch (IOException e) - { - logger.error("An error occurred getting the Writer when closing an ExtensibilityModel", e); - } - } - } - } - - /** - *

This keeps track of whether or not the {@link ExtensibilityModel} for the current thread has been used. The - * thread local value will only be set to true if the getCurrentExtensibilityModel method - * is called.

- */ - private ThreadLocal modelUsed = new ThreadLocal(); - - /** - *

A {@link WebScriptExtensibilityModuleHandler} is required for retrieving information on what - * {@link BasicExtensionModule} instances have been configured and the extension files that need - * to be processed. This variable should be set thorugh the Spring application context configuration.

- */ - private WebScriptExtensibilityModuleHandler extensibilityModuleHandler = null; - - /** - *

Sets the {@link WebScriptExtensibilityModuleHandler} for this {@link org.springframework.extensions.webscripts.Container}.

- * @param extensibilityModuleHandler WebScriptExtensibilityModuleHandler - */ - public void setExtensibilityModuleHandler(WebScriptExtensibilityModuleHandler extensibilityModuleHandler) - { - this.extensibilityModuleHandler = extensibilityModuleHandler; - } - - /** - *

Maintains a list of all the {@link ExtensibilityModel} instances being used across all the - * available threads.

- */ - private ThreadLocal extensibilityModel = new ThreadLocal(); - - /** - *

Creates a new {@link ExtensibilityModel} and sets it on the current thread - */ - public ExtensibilityModel openExtensibilityModel() - { - if (logger.isDebugEnabled()) - { - logger.debug("Opening for thread: " + Thread.currentThread().getName()); - } - this.extendedBundleCache.set(new HashMap()); - this.evaluatedModules.set(null); - this.fileBeingProcessed.set(null); - this.globalConfig.set(null); - this.sections.set(null); - this.sectionsByArea.set(null); - - ExtensibilityModel model = new ExtensibilityModelImpl(null, this); - this.extensibilityModel.set(model); - this.modelUsed.set(Boolean.FALSE); - return model; - } - - /** - *

Flushes the {@link ExtensibilityModel} provided and sets its parent as the current {@link ExtensibilityModel} - * for the current thread.

- */ - public void closeExtensibilityModel(ExtensibilityModel model, Writer out) - { - if (logger.isDebugEnabled()) - { - logger.debug("Closing for thread: " + Thread.currentThread().getName()); - } - - model.flushModel(out); - this.modelUsed.set(Boolean.FALSE); - this.extensibilityModel.set(null); - } - - /** - *

Returns the {@link ExtensibilityModel} for the current thread.

- */ - public ExtensibilityModel getCurrentExtensibilityModel() - { - if (logger.isDebugEnabled()) - { - logger.debug("Getting current for thread: " + Thread.currentThread().getName()); - } - this.modelUsed.set(Boolean.TRUE); - return this.extensibilityModel.get(); - } - - /** - *

This method is implemented to perform no action as it is not necessary for a standalone WebScript - * container to add dependencies for processing.

- */ - public void updateExtendingModuleDependencies(String pathBeingProcessed, Map model) - { - // NOT REQUIRED FOR STANDALONE WEBSCRIPT CONTAINER - } - - /** - *

A thread-safe cache of extended {@link ResourceBundle} instances for the current request.

- */ - private ThreadLocal> extendedBundleCache = new ThreadLocal>(); - - /** - *

Checks the cache to see if it has cached an extended bundle (that is a basic {@link ResourceBundle} that - * has had extension modules applied to it. Extended bundles can only be safely cached once per request as the modules - * applied can vary for each request.

- * - * @param webScriptId The id of the WebScript to retrieve the extended bundle for. - * @return A cached bundle or null if the bundle has not previously been cached. - */ - public ResourceBundle getCachedExtendedBundle(String webScriptId) - { - ResourceBundle cachedExtendedBundle = null; - Map threadLocal = this.extendedBundleCache.get(); - if (threadLocal != null) - { - cachedExtendedBundle = this.extendedBundleCache.get().get(webScriptId); - } - return cachedExtendedBundle; - } - - /** - *

Adds a new extended bundle to the cache. An extended bundle is a WebScript {@link ResourceBundle} that has had - * {@link ResourceBundle} instances merged into it from extension modules that have been applied. These can only be cached - * for the lifetime of the request as different modules may be applied to the same WebScript for different requests.

- * - * @param webScriptId The id of the WebScript to cache the extended bundle against. - * @param extensionBundle The extended bundle to cache. - */ - public void addExtensionBundleToCache(String webScriptId, WebScriptPropertyResourceBundle extensionBundle) - { - Map threadLocal = this.extendedBundleCache.get(); - if (threadLocal == null) - { - // This should never be the case because when a new model is opened this value should be reset - // but we will double-check to avoid the potential of NPEs... - threadLocal = new HashMap(); - this.extendedBundleCache.set(threadLocal); - } - threadLocal.put(webScriptId, extensionBundle); - } - - /** - *

A {@link ThreadLocal} reference to the file currently being processed in the model. - */ - private ThreadLocal fileBeingProcessed = new ThreadLocal(); - - /** - *

Returns the path of the file currently being processed in the model by the current thread. - * This information is primarily provided for the purposes of generating debug information.

- * - * @return The path of the file currently being processed. - */ - public String getFileBeingProcessed() - { - return this.fileBeingProcessed.get(); - } - - /** - *

Sets the path of the file currently being processed in the model by the current thread. - * This information should be collected to assist with providing debug information.

- * @param file The path of the file currently being processed. - */ - public void setFileBeingProcessed(String file) - { - this.fileBeingProcessed.set(file); - } - - /** - *

Retrieves an files for the evaluated modules that are extending the WebScript files being processed.

- */ - public List getExtendingModuleFiles(String pathBeingProcessed) - { - List extendingModuleFiles = new ArrayList(); - for (BasicExtensionModule module: this.getEvaluatedModules()) - { - extendingModuleFiles.addAll(this.extensibilityModuleHandler.getExtendingModuleFiles(module, pathBeingProcessed)); - } - return extendingModuleFiles; - } - - /** - *

The list of {@link org.springframework.extensions.surf.types.ExtensionModule} instances that have been evaluated as applicable to - * this RequestContext. This is set to null when during instantiation and is only - * properly set the first time the getEvaluatedModules method is invoked. This ensures - * that module evaluation only occurs once per request.

- */ - private ThreadLocal> evaluatedModules = new ThreadLocal>(); - - /** - *

Retrieve the list of {@link org.springframework.extensions.surf.types.ExtensionModule} instances that have been evaluated as applicable - * for the current request. If this list has not yet been populated then use the {@link org.springframework.extensions.surf.extensibility.ExtensibilityModuleHandler} - * configured in the Spring application context to evaluate them.

- * - * @return A list of {@link org.springframework.extensions.surf.types.ExtensionModule} instances that are applicable to the current request. - */ - public List getEvaluatedModules() - { - List evaluatedModules = this.evaluatedModules.get(); - if (evaluatedModules == null) - { - if (this.extensibilityModuleHandler == null) - { - if (logger.isErrorEnabled()) - { - logger.error("No 'extensibilityModuleHandler' has been configured for this request context. Extensions cannot be processed"); - } - evaluatedModules = new ArrayList(); - this.evaluatedModules.set(evaluatedModules); - } - else - { - evaluatedModules = this.extensibilityModuleHandler.getExtensionModules(); - this.evaluatedModules.set(evaluatedModules); - } - } - return evaluatedModules; - } - - /** - *

This is a local {@link ConfigImpl} instance that will only be used when extension modules are employed. It will - * initially be populated with the default "static" global configuration taken from the {@link ConfigService} associated - * with this {@link org.springframework.extensions.surf.RequestContext} but then updated to include global configuration provided by extension modules that - * have been evaluated to be applied to the current request.

- */ - private ThreadLocal globalConfig = new ThreadLocal(); - - /** - *

This map represents {@link ConfigSection} instances mapped by area. It will only be used when extension modules are - * employed. It will initially be populated with the default "static" configuration taken from the {@link ConfigService} associated - * with this {@link org.springframework.extensions.surf.RequestContext} but then updated to include configuration provided by extension modules that have been evaluated - * to be applied to the current request.

- */ - private ThreadLocal>> sectionsByArea = new ThreadLocal>>(); - - /** - *

A list of {@link ConfigSection} instances that are only applicable to the current request. It will only be used when extension modules are - * employed. It will initially be populated with the default "static" configuration taken from the {@link ConfigService} associated - * with this {@link org.springframework.extensions.surf.RequestContext} but then updated to include configuration provided by extension modules that have been evaluated - * to be applied to the current request.

- */ - private ThreadLocal> sections = new ThreadLocal>(); - - /** - *

Creates a new {@link ExtendedScriptConfigModel} instance using the local configuration generated for this request. - * If configuration for the request will be generated if it does not yet exist. It is likely that this method will be - * called multiple times within the context of a single request and although the configuration containers will always - * be the same a new {@link ExtendedScriptConfigModel} instance will always be created as the the supplied xmlConfig - * string could be different for each call (because each WebScript invoked in the request will supply different - * configuration.

- */ - public ScriptConfigModel getExtendedScriptConfigModel(String xmlConfig) - { - if (this.globalConfig.get() == null && this.sectionsByArea.get() == null && this.sections.get() == null) - { - this.getConfigExtensions(); - } - return new ExtendedScriptConfigModel(getConfigService(), xmlConfig, this.globalConfig.get(), this.sectionsByArea.get(), this.sections.get()); - } - - /** - *

Creates a new {@link TemplateConfigModel} instance using the local configuration generated for this request. - * If configuration for the request will be generated if it does not yet exist. It is likely that this method will be - * called multiple times within the context of a single request and although the configuration containers will always - * be the same a new {@link TemplateConfigModel} instance will always be created as the the supplied xmlConfig - * string could be different for each call (because each WebScript invoked in the request will supply different - * configuration.

- */ - public TemplateConfigModel getExtendedTemplateConfigModel(String xmlConfig) - { - if (this.globalConfig.get() == null && this.sectionsByArea.get() == null && this.sections.get() == null) - { - this.getConfigExtensions(); - } - return new ExtendedTemplateConfigModel(getConfigService(), xmlConfig, this.globalConfig.get(), this.sectionsByArea.get(), this.sections.get()); - } - - /** - *

Creates and populates the request specific configuration container objects (globalConfig, sectionsByArea & - * sections with a combination of the default static configuration (taken from files accessed by the {@link ConfigService}) and - * dynamic configuration taken from extension modules evaluated for the current request.

- */ - private void getConfigExtensions() - { - // Extended configuration is only possible if config service is an XMLConfigService... - // - // ...also, it's only necessary to populate the configuration containers if they have not already been populated. This test should also - // be carried out by the two methods ("getExtendedTemplateConfigModel" & "getExtendedTemplateConfigModel") to prevent duplication - // of effort... but in case other methods attempt to access it we will make these additional tests. - if (getConfigService() instanceof XMLConfigService && this.globalConfig == null && this.sectionsByArea == null && this.sections == null) - { - // Cast the config service for ease of access - XMLConfigService xmlConfigService = (XMLConfigService) getConfigService(); - - // Get the current configuration from the ConfigService - we don't want to permanently pollute - // the standard configuration with additions from the modules... - this.globalConfig.set(new ConfigImpl((ConfigImpl)xmlConfigService.getGlobalConfig())); // Make a copy of the current global config - - // Initialise these with the config service values... - this.sectionsByArea.set(new HashMap>(xmlConfigService.getSectionsByArea())); - this.sections.set(new ArrayList(xmlConfigService.getSections())); - - // Check to see if there are any modules that we need to apply... - List evaluatedModules = this.getEvaluatedModules(); - if (evaluatedModules != null && !evaluatedModules.isEmpty()) - { - for (BasicExtensionModule currModule: evaluatedModules) - { - for (Element currentConfigElement: currModule.getConfigurations()) - { - // Set up containers for our request specific configuration - this will contain data taken from the evaluated modules... - Map parsedElementReaders = new HashMap(); - Map parsedEvaluators = new HashMap(); - List parsedConfigSections = new ArrayList(); - - // Parse and process the parses configuration... - String currentArea = xmlConfigService.parseFragment(currentConfigElement, parsedElementReaders, parsedEvaluators, parsedConfigSections); - for (Map.Entry entry : parsedEvaluators.entrySet()) - { - // add the evaluators to the config service - parsedEvaluators.put(entry.getKey(), entry.getValue()); - } - for (Map.Entry entry : parsedElementReaders.entrySet()) - { - // add the element readers to the config service - parsedElementReaders.put(entry.getKey(), entry.getValue()); - } - for (ConfigSection section : parsedConfigSections) - { - // Update local configuration with our updated data... - xmlConfigService.addConfigSection(section, currentArea, this.globalConfig.get(), this.sectionsByArea.get(), this.sections.get()); - } - } - } - } - } - } - - /** - *

Adds the <{@code}@markup> directive to the container which allows FreeMarker templates to be extended.

- */ - public void addExtensibilityDirectives(Map freeMarkerModel, ExtensibilityModel extModel) - { - MarkupDirective mud = new MarkupDirective("markup", extModel); - freeMarkerModel.put("markup", mud); - } -} +package org.alfresco.repo.web.scripts; + + + +import java.io.IOException; + +import java.io.Writer; + +import java.util.ArrayList; + +import java.util.HashMap; + +import java.util.List; + +import java.util.Map; + +import java.util.ResourceBundle; + + + +import org.apache.commons.logging.Log; + +import org.apache.commons.logging.LogFactory; + +import org.dom4j.Element; + +import org.springframework.extensions.config.ConfigImpl; + +import org.springframework.extensions.config.ConfigSection; + +import org.springframework.extensions.config.ConfigService; + +import org.springframework.extensions.config.evaluator.Evaluator; + +import org.springframework.extensions.config.xml.XMLConfigService; + +import org.springframework.extensions.config.xml.elementreader.ConfigElementReader; + +import org.springframework.extensions.surf.extensibility.BasicExtensionModule; + +import org.springframework.extensions.surf.extensibility.ExtensibilityModel; + +import org.springframework.extensions.surf.extensibility.HandlesExtensibility; + +import org.springframework.extensions.surf.extensibility.WebScriptExtensibilityModuleHandler; + +import org.springframework.extensions.surf.extensibility.impl.ExtensibilityModelImpl; + +import org.springframework.extensions.surf.extensibility.impl.MarkupDirective; + +import org.springframework.extensions.webscripts.Authenticator; + +import org.springframework.extensions.webscripts.ExtendedScriptConfigModel; + +import org.springframework.extensions.webscripts.ExtendedTemplateConfigModel; + +import org.springframework.extensions.webscripts.ScriptConfigModel; + +import org.springframework.extensions.webscripts.TemplateConfigModel; + +import org.springframework.extensions.webscripts.WebScriptPropertyResourceBundle; + +import org.springframework.extensions.webscripts.WebScriptRequest; + +import org.springframework.extensions.webscripts.WebScriptResponse; + + + +/** + + *

A simple extensibility {@link org.springframework.extensions.webscripts.Container} for processing WebScripts. This extends the {@link RepositoryContainer} and + + * implements the {@link HandlesExtensibility} interface to provide extensibility capabilities.

+ + * + + * @author David Draper + + */ + +public class ExtensibilityContainer extends RepositoryContainer implements HandlesExtensibility + +{ + + private static final Log logger = LogFactory.getLog(ExtensibilityContainer.class); + + + + public boolean isExtensibilitySuppressed() + + { + + return false; + + } + + + + /** + + *

Opens a new {@link ExtensibilityModel}, defers execution to the extended {@link RepositoryContainer} and + + * then closes the {@link ExtensibilityModel}.

+ + */ + + @Override + + public void executeScript(WebScriptRequest scriptReq, + + WebScriptResponse scriptRes, + + Authenticator auth) throws IOException + + { + + ExtensibilityModel extModel = this.openExtensibilityModel(); + + try + + { + + super.executeScript(scriptReq, scriptRes, auth); + + } + + finally + + { + + // It's only necessary to close the model if it's actually been used. Not all WebScripts will make use of the + + // model. An example of this would be the StreamContent WebScript. It is important not to attempt to close + + // an unused model since the WebScript executed may have already flushed the response if it has overridden + + // the default .execute() method. + + if (this.modelUsed.get()) + + { + + try + + { + + this.closeExtensibilityModel(extModel, scriptRes.getWriter()); + + } + + catch (IOException e) + + { + + logger.error("An error occurred getting the Writer when closing an ExtensibilityModel", e); + + } + + } + + } + + } + + + + /** + + *

This keeps track of whether or not the {@link ExtensibilityModel} for the current thread has been used. The + + * thread local value will only be set to true if the getCurrentExtensibilityModel method + + * is called.

+ + */ + + private ThreadLocal modelUsed = new ThreadLocal(); + + + + /** + + *

A {@link WebScriptExtensibilityModuleHandler} is required for retrieving information on what + + * {@link BasicExtensionModule} instances have been configured and the extension files that need + + * to be processed. This variable should be set thorugh the Spring application context configuration.

+ + */ + + private WebScriptExtensibilityModuleHandler extensibilityModuleHandler = null; + + + + /** + + *

Sets the {@link WebScriptExtensibilityModuleHandler} for this {@link org.springframework.extensions.webscripts.Container}.

+ + * @param extensibilityModuleHandler WebScriptExtensibilityModuleHandler + + */ + + public void setExtensibilityModuleHandler(WebScriptExtensibilityModuleHandler extensibilityModuleHandler) + + { + + this.extensibilityModuleHandler = extensibilityModuleHandler; + + } + + + + /** + + *

Maintains a list of all the {@link ExtensibilityModel} instances being used across all the + + * available threads.

+ + */ + + private ThreadLocal extensibilityModel = new ThreadLocal(); + + + + /** + + *

Creates a new {@link ExtensibilityModel} and sets it on the current thread

+ + */ + + public ExtensibilityModel openExtensibilityModel() + + { + + if (logger.isDebugEnabled()) + + { + + logger.debug("Opening for thread: " + Thread.currentThread().getName()); + + } + + this.extendedBundleCache.set(new HashMap()); + + this.evaluatedModules.set(null); + + this.fileBeingProcessed.set(null); + + this.globalConfig.set(null); + + this.sections.set(null); + + this.sectionsByArea.set(null); + + + + ExtensibilityModel model = new ExtensibilityModelImpl(null, this); + + this.extensibilityModel.set(model); + + this.modelUsed.set(Boolean.FALSE); + + return model; + + } + + + + /** + + *

Flushes the {@link ExtensibilityModel} provided and sets its parent as the current {@link ExtensibilityModel} + + * for the current thread.

+ + */ + + public void closeExtensibilityModel(ExtensibilityModel model, Writer out) + + { + + if (logger.isDebugEnabled()) + + { + + logger.debug("Closing for thread: " + Thread.currentThread().getName()); + + } + + + + model.flushModel(out); + + this.modelUsed.set(Boolean.FALSE); + + this.extensibilityModel.set(null); + + } + + + + /** + + *

Returns the {@link ExtensibilityModel} for the current thread.

+ + */ + + public ExtensibilityModel getCurrentExtensibilityModel() + + { + + if (logger.isDebugEnabled()) + + { + + logger.debug("Getting current for thread: " + Thread.currentThread().getName()); + + } + + this.modelUsed.set(Boolean.TRUE); + + return this.extensibilityModel.get(); + + } + + + + /** + + *

This method is implemented to perform no action as it is not necessary for a standalone WebScript + + * container to add dependencies for processing.

+ + */ + + public void updateExtendingModuleDependencies(String pathBeingProcessed, Map model) + + { + + // NOT REQUIRED FOR STANDALONE WEBSCRIPT CONTAINER + + } + + + + /** + + *

A thread-safe cache of extended {@link ResourceBundle} instances for the current request.

+ + */ + + private ThreadLocal> extendedBundleCache = new ThreadLocal>(); + + + + /** + + *

Checks the cache to see if it has cached an extended bundle (that is a basic {@link ResourceBundle} that + + * has had extension modules applied to it. Extended bundles can only be safely cached once per request as the modules + + * applied can vary for each request.

+ + * + + * @param webScriptId The id of the WebScript to retrieve the extended bundle for. + + * @return A cached bundle or null if the bundle has not previously been cached. + + */ + + public ResourceBundle getCachedExtendedBundle(String webScriptId) + + { + + ResourceBundle cachedExtendedBundle = null; + + Map threadLocal = this.extendedBundleCache.get(); + + if (threadLocal != null) + + { + + cachedExtendedBundle = this.extendedBundleCache.get().get(webScriptId); + + } + + return cachedExtendedBundle; + + } + + + + /** + + *

Adds a new extended bundle to the cache. An extended bundle is a WebScript {@link ResourceBundle} that has had + + * {@link ResourceBundle} instances merged into it from extension modules that have been applied. These can only be cached + + * for the lifetime of the request as different modules may be applied to the same WebScript for different requests.

+ + * + + * @param webScriptId The id of the WebScript to cache the extended bundle against. + + * @param extensionBundle The extended bundle to cache. + + */ + + public void addExtensionBundleToCache(String webScriptId, WebScriptPropertyResourceBundle extensionBundle) + + { + + Map threadLocal = this.extendedBundleCache.get(); + + if (threadLocal == null) + + { + + // This should never be the case because when a new model is opened this value should be reset + + // but we will double-check to avoid the potential of NPEs... + + threadLocal = new HashMap(); + + this.extendedBundleCache.set(threadLocal); + + } + + threadLocal.put(webScriptId, extensionBundle); + + } + + + + /** + + *

A {@link ThreadLocal} reference to the file currently being processed in the model.

+ + */ + + private ThreadLocal fileBeingProcessed = new ThreadLocal(); + + + + /** + + *

Returns the path of the file currently being processed in the model by the current thread. + + * This information is primarily provided for the purposes of generating debug information.

+ + * + + * @return The path of the file currently being processed. + + */ + + public String getFileBeingProcessed() + + { + + return this.fileBeingProcessed.get(); + + } + + + + /** + + *

Sets the path of the file currently being processed in the model by the current thread. + + * This information should be collected to assist with providing debug information.

+ + * @param file The path of the file currently being processed. + + */ + + public void setFileBeingProcessed(String file) + + { + + this.fileBeingProcessed.set(file); + + } + + + + /** + + *

Retrieves an files for the evaluated modules that are extending the WebScript files being processed.

+ + */ + + public List getExtendingModuleFiles(String pathBeingProcessed) + + { + + List extendingModuleFiles = new ArrayList(); + + for (BasicExtensionModule module: this.getEvaluatedModules()) + + { + + extendingModuleFiles.addAll(this.extensibilityModuleHandler.getExtendingModuleFiles(module, pathBeingProcessed)); + + } + + return extendingModuleFiles; + + } + + + + /** + + *

The list of {@link org.springframework.extensions.surf.types.ExtensionModule} instances that have been evaluated as applicable to + + * this RequestContext. This is set to null when during instantiation and is only + + * properly set the first time the getEvaluatedModules method is invoked. This ensures + + * that module evaluation only occurs once per request.

+ + */ + + private ThreadLocal> evaluatedModules = new ThreadLocal>(); + + + + /** + + *

Retrieve the list of {@link org.springframework.extensions.surf.types.ExtensionModule} instances that have been evaluated as applicable + + * for the current request. If this list has not yet been populated then use the {@link org.springframework.extensions.surf.extensibility.ExtensibilityModuleHandler} + + * configured in the Spring application context to evaluate them.

+ + * + + * @return A list of {@link org.springframework.extensions.surf.types.ExtensionModule} instances that are applicable to the current request. + + */ + + public List getEvaluatedModules() + + { + + List evaluatedModules = this.evaluatedModules.get(); + + if (evaluatedModules == null) + + { + + if (this.extensibilityModuleHandler == null) + + { + + if (logger.isErrorEnabled()) + + { + + logger.error("No 'extensibilityModuleHandler' has been configured for this request context. Extensions cannot be processed"); + + } + + evaluatedModules = new ArrayList(); + + this.evaluatedModules.set(evaluatedModules); + + } + + else + + { + + evaluatedModules = this.extensibilityModuleHandler.getExtensionModules(); + + this.evaluatedModules.set(evaluatedModules); + + } + + } + + return evaluatedModules; + + } + + + + /** + + *

This is a local {@link ConfigImpl} instance that will only be used when extension modules are employed. It will + + * initially be populated with the default "static" global configuration taken from the {@link ConfigService} associated + + * with this {@link org.springframework.extensions.surf.RequestContext} but then updated to include global configuration provided by extension modules that + + * have been evaluated to be applied to the current request.

+ + */ + + private ThreadLocal globalConfig = new ThreadLocal(); + + + + /** + + *

This map represents {@link ConfigSection} instances mapped by area. It will only be used when extension modules are + + * employed. It will initially be populated with the default "static" configuration taken from the {@link ConfigService} associated + + * with this {@link org.springframework.extensions.surf.RequestContext} but then updated to include configuration provided by extension modules that have been evaluated + + * to be applied to the current request.

+ + */ + + private ThreadLocal>> sectionsByArea = new ThreadLocal>>(); + + + + /** + + *

A list of {@link ConfigSection} instances that are only applicable to the current request. It will only be used when extension modules are + + * employed. It will initially be populated with the default "static" configuration taken from the {@link ConfigService} associated + + * with this {@link org.springframework.extensions.surf.RequestContext} but then updated to include configuration provided by extension modules that have been evaluated + + * to be applied to the current request.

+ + */ + + private ThreadLocal> sections = new ThreadLocal>(); + + + + /** + + *

Creates a new {@link ExtendedScriptConfigModel} instance using the local configuration generated for this request. + + * If configuration for the request will be generated if it does not yet exist. It is likely that this method will be + + * called multiple times within the context of a single request and although the configuration containers will always + + * be the same a new {@link ExtendedScriptConfigModel} instance will always be created as the the supplied xmlConfig + + * string could be different for each call (because each WebScript invoked in the request will supply different + + * configuration.

+ + */ + + public ScriptConfigModel getExtendedScriptConfigModel(String xmlConfig) + + { + + if (this.globalConfig.get() == null && this.sectionsByArea.get() == null && this.sections.get() == null) + + { + + this.getConfigExtensions(); + + } + + return new ExtendedScriptConfigModel(getConfigService(), xmlConfig, this.globalConfig.get(), this.sectionsByArea.get(), this.sections.get()); + + } + + + + /** + + *

Creates a new {@link TemplateConfigModel} instance using the local configuration generated for this request. + + * If configuration for the request will be generated if it does not yet exist. It is likely that this method will be + + * called multiple times within the context of a single request and although the configuration containers will always + + * be the same a new {@link TemplateConfigModel} instance will always be created as the the supplied xmlConfig + + * string could be different for each call (because each WebScript invoked in the request will supply different + + * configuration.

+ + */ + + public TemplateConfigModel getExtendedTemplateConfigModel(String xmlConfig) + + { + + if (this.globalConfig.get() == null && this.sectionsByArea.get() == null && this.sections.get() == null) + + { + + this.getConfigExtensions(); + + } + + return new ExtendedTemplateConfigModel(getConfigService(), xmlConfig, this.globalConfig.get(), this.sectionsByArea.get(), this.sections.get()); + + } + + + + /** + + *

Creates and populates the request specific configuration container objects (globalConfig, sectionsByArea & + + * sections with a combination of the default static configuration (taken from files accessed by the {@link ConfigService}) and + + * dynamic configuration taken from extension modules evaluated for the current request.

+ + */ + + private void getConfigExtensions() + + { + + // Extended configuration is only possible if config service is an XMLConfigService... + + // + + // ...also, it's only necessary to populate the configuration containers if they have not already been populated. This test should also + + // be carried out by the two methods ("getExtendedTemplateConfigModel" & "getExtendedTemplateConfigModel") to prevent duplication + + // of effort... but in case other methods attempt to access it we will make these additional tests. + + if (getConfigService() instanceof XMLConfigService && this.globalConfig == null && this.sectionsByArea == null && this.sections == null) + + { + + // Cast the config service for ease of access + + XMLConfigService xmlConfigService = (XMLConfigService) getConfigService(); + + + + // Get the current configuration from the ConfigService - we don't want to permanently pollute + + // the standard configuration with additions from the modules... + + this.globalConfig.set(new ConfigImpl((ConfigImpl)xmlConfigService.getGlobalConfig())); // Make a copy of the current global config + + + + // Initialise these with the config service values... + + this.sectionsByArea.set(new HashMap>(xmlConfigService.getSectionsByArea())); + + this.sections.set(new ArrayList(xmlConfigService.getSections())); + + + + // Check to see if there are any modules that we need to apply... + + List evaluatedModules = this.getEvaluatedModules(); + + if (evaluatedModules != null && !evaluatedModules.isEmpty()) + + { + + for (BasicExtensionModule currModule: evaluatedModules) + + { + + for (Element currentConfigElement: currModule.getConfigurations()) + + { + + // Set up containers for our request specific configuration - this will contain data taken from the evaluated modules... + + Map parsedElementReaders = new HashMap(); + + Map parsedEvaluators = new HashMap(); + + List parsedConfigSections = new ArrayList(); + + + + // Parse and process the parses configuration... + + String currentArea = xmlConfigService.parseFragment(currentConfigElement, parsedElementReaders, parsedEvaluators, parsedConfigSections); + + for (Map.Entry entry : parsedEvaluators.entrySet()) + + { + + // add the evaluators to the config service + + parsedEvaluators.put(entry.getKey(), entry.getValue()); + + } + + for (Map.Entry entry : parsedElementReaders.entrySet()) + + { + + // add the element readers to the config service + + parsedElementReaders.put(entry.getKey(), entry.getValue()); + + } + + for (ConfigSection section : parsedConfigSections) + + { + + // Update local configuration with our updated data... + + xmlConfigService.addConfigSection(section, currentArea, this.globalConfig.get(), this.sectionsByArea.get(), this.sections.get()); + + } + + } + + } + + } + + } + + } + + + + /** + + *

Adds the <{@code}@markup> directive to the container which allows FreeMarker templates to be extended.

+ + */ + + public void addExtensibilityDirectives(Map freeMarkerModel, ExtensibilityModel extModel) + + { + + MarkupDirective mud = new MarkupDirective("markup", extModel); + + freeMarkerModel.put("markup", mud); + + } + +} +