| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.sling.scripting.thymeleaf.internal; |
| |
| import java.util.Dictionary; |
| import java.util.HashSet; |
| import java.util.Hashtable; |
| import java.util.List; |
| import java.util.Properties; |
| import java.util.Set; |
| |
| import javax.script.ScriptEngine; |
| import javax.script.ScriptEngineFactory; |
| import javax.servlet.ServletContext; |
| |
| import org.apache.sling.api.resource.ResourceResolver; |
| import org.apache.sling.scripting.api.AbstractScriptEngineFactory; |
| import org.apache.sling.scripting.api.resource.ScriptingResourceResolverProvider; |
| import org.osgi.framework.BundleContext; |
| import org.osgi.framework.Constants; |
| import org.osgi.framework.ServiceRegistration; |
| import org.osgi.service.component.annotations.Activate; |
| import org.osgi.service.component.annotations.Component; |
| import org.osgi.service.component.annotations.Deactivate; |
| import org.osgi.service.component.annotations.Modified; |
| import org.osgi.service.component.annotations.Reference; |
| import org.osgi.service.component.annotations.ReferenceCardinality; |
| import org.osgi.service.component.annotations.ReferencePolicy; |
| import org.osgi.service.component.annotations.ReferencePolicyOption; |
| import org.osgi.service.metatype.annotations.Designate; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| import org.thymeleaf.ITemplateEngine; |
| import org.thymeleaf.TemplateEngine; |
| import org.thymeleaf.cache.ICacheManager; |
| import org.thymeleaf.context.IEngineContextFactory; |
| import org.thymeleaf.dialect.IDialect; |
| import org.thymeleaf.linkbuilder.ILinkBuilder; |
| import org.thymeleaf.linkbuilder.StandardLinkBuilder; |
| import org.thymeleaf.messageresolver.IMessageResolver; |
| import org.thymeleaf.messageresolver.StandardMessageResolver; |
| import org.thymeleaf.standard.StandardDialect; |
| import org.thymeleaf.templateparser.markup.decoupled.IDecoupledTemplateLogicResolver; |
| import org.thymeleaf.templateresolver.ITemplateResolver; |
| |
| @Component( |
| service = ScriptEngineFactory.class, |
| immediate = true, |
| property = { |
| Constants.SERVICE_DESCRIPTION + "=ScriptEngineFactory for Sling Scripting Thymeleaf", |
| Constants.SERVICE_VENDOR + "=The Apache Software Foundation" |
| } |
| ) |
| @Designate( |
| ocd = ThymeleafScriptEngineFactoryConfiguration.class |
| ) |
| @SuppressWarnings("java:S3077") |
| public final class ThymeleafScriptEngineFactory extends AbstractScriptEngineFactory { |
| |
| @Reference( |
| cardinality = ReferenceCardinality.AT_LEAST_ONE, |
| policy = ReferencePolicy.DYNAMIC, |
| bind = "addTemplateResolver", |
| unbind = "removeTemplateResolver" |
| ) |
| private volatile List<ITemplateResolver> templateResolvers; |
| |
| @Reference( |
| cardinality = ReferenceCardinality.AT_LEAST_ONE, |
| policy = ReferencePolicy.DYNAMIC, |
| bind = "addMessageResolver", |
| unbind = "removeMessageResolver" |
| ) |
| private volatile List<IMessageResolver> messageResolvers; |
| |
| @Reference( |
| cardinality = ReferenceCardinality.AT_LEAST_ONE, |
| policy = ReferencePolicy.DYNAMIC, |
| bind = "addDialect", |
| unbind = "removeDialect" |
| ) |
| private volatile List<IDialect> dialects; |
| |
| @Reference( |
| cardinality = ReferenceCardinality.MULTIPLE, |
| policy = ReferencePolicy.DYNAMIC, |
| bind = "addLinkBuilder", |
| unbind = "removeLinkBuilder" |
| ) |
| private volatile List<ILinkBuilder> linkBuilders; |
| |
| @Reference( |
| cardinality = ReferenceCardinality.OPTIONAL, |
| policy = ReferencePolicy.DYNAMIC, |
| policyOption = ReferencePolicyOption.GREEDY, |
| bind = "setDecoupledTemplateLogicResolver", |
| unbind = "unsetDecoupledTemplateLogicResolver" |
| ) |
| private volatile IDecoupledTemplateLogicResolver decoupledTemplateLogicResolver; |
| |
| @Reference( |
| cardinality = ReferenceCardinality.OPTIONAL, |
| policy = ReferencePolicy.DYNAMIC, |
| policyOption = ReferencePolicyOption.GREEDY, |
| bind = "setCacheManager", |
| unbind = "unsetCacheManager" |
| ) |
| private volatile ICacheManager cacheManager; |
| |
| @Reference( |
| cardinality = ReferenceCardinality.OPTIONAL, |
| policy = ReferencePolicy.DYNAMIC, |
| policyOption = ReferencePolicyOption.GREEDY, |
| bind = "setEngineContextFactory", |
| unbind = "unsetEngineContextFactory" |
| ) |
| private volatile IEngineContextFactory engineContextFactory; |
| |
| @Reference( |
| policy = ReferencePolicy.DYNAMIC, |
| policyOption = ReferencePolicyOption.GREEDY |
| ) |
| private volatile ScriptingResourceResolverProvider scriptingResourceResolverProvider; |
| |
| @Reference( |
| policy = ReferencePolicy.DYNAMIC, |
| policyOption = ReferencePolicyOption.GREEDY, |
| target = "(name=org.apache.sling)" |
| ) |
| private volatile ServletContext servletContext; |
| |
| private ThymeleafScriptEngineFactoryConfiguration configuration; |
| |
| private BundleContext bundleContext; |
| |
| private TemplateEngine templateEngine; |
| |
| private ServiceRegistration<ITemplateEngine> serviceRegistration; |
| |
| private final Object lock = new Object(); |
| |
| private static final String THYMELEAF_PROPERTIES = "/org/thymeleaf/thymeleaf.properties"; |
| |
| private final Logger logger = LoggerFactory.getLogger(ThymeleafScriptEngineFactory.class); |
| |
| public ThymeleafScriptEngineFactory() { // |
| } |
| |
| protected void addTemplateResolver(final ITemplateResolver templateResolver) { |
| synchronized (lock) { |
| logger.debug("adding template resolver '{}'", templateResolver.getName()); |
| if (templateEngine == null || templateEngine.isInitialized()) { |
| serviceTemplateEngine(); |
| } else { |
| templateEngine.addTemplateResolver(templateResolver); |
| } |
| } |
| } |
| |
| protected void removeTemplateResolver(final ITemplateResolver templateResolver) { |
| synchronized (lock) { |
| logger.debug("removing template resolver '{}'", templateResolver.getName()); |
| serviceTemplateEngine(); |
| } |
| } |
| |
| protected void addMessageResolver(final IMessageResolver messageResolver) { |
| synchronized (lock) { |
| logger.debug("adding message resolver '{}'", messageResolver.getName()); |
| if (templateEngine == null || templateEngine.isInitialized()) { |
| serviceTemplateEngine(); |
| } else { |
| templateEngine.addMessageResolver(messageResolver); |
| } |
| } |
| } |
| |
| protected void removeMessageResolver(final IMessageResolver messageResolver) { |
| synchronized (lock) { |
| logger.debug("removing message resolver '{}'", messageResolver.getName()); |
| serviceTemplateEngine(); |
| } |
| } |
| |
| protected void addDialect(final IDialect dialect) { |
| synchronized (lock) { |
| logger.debug("adding dialect '{}'", dialect.getName()); |
| if (templateEngine == null || templateEngine.isInitialized()) { |
| serviceTemplateEngine(); |
| } else { |
| templateEngine.addDialect(dialect); |
| } |
| } |
| } |
| |
| protected void removeDialect(final IDialect dialect) { |
| synchronized (lock) { |
| logger.debug("removing dialect '{}'", dialect.getName()); |
| serviceTemplateEngine(); |
| } |
| } |
| |
| protected void addLinkBuilder(final ILinkBuilder linkBuilder) { |
| synchronized (lock) { |
| logger.debug("adding link builder '{}'", linkBuilder.getName()); |
| if (templateEngine == null || templateEngine.isInitialized()) { |
| serviceTemplateEngine(); |
| } else { |
| templateEngine.addLinkBuilder(linkBuilder); |
| } |
| } |
| } |
| |
| protected void removeLinkBuilder(final ILinkBuilder linkBuilder) { |
| synchronized (lock) { |
| logger.debug("removing link builder '{}'", linkBuilder.getName()); |
| serviceTemplateEngine(); |
| } |
| } |
| |
| protected void setDecoupledTemplateLogicResolver(final IDecoupledTemplateLogicResolver decoupledTemplateLogicResolver) { |
| synchronized (lock) { |
| logger.debug("setting decoupled template logic resolver '{}'", decoupledTemplateLogicResolver.getClass().getName()); |
| if (templateEngine == null || templateEngine.isInitialized()) { |
| serviceTemplateEngine(); |
| } else { |
| templateEngine.setDecoupledTemplateLogicResolver(decoupledTemplateLogicResolver); |
| } |
| } |
| } |
| |
| protected void unsetDecoupledTemplateLogicResolver(final IDecoupledTemplateLogicResolver decoupledTemplateLogicResolver) { |
| synchronized (lock) { |
| logger.debug("unsetting decoupled template logic resolver '{}'", decoupledTemplateLogicResolver.getClass().getName()); |
| serviceTemplateEngine(); |
| } |
| } |
| |
| protected void setCacheManager(final ICacheManager cacheManager) { |
| synchronized (lock) { |
| logger.debug("setting cache manager '{}'", cacheManager.getClass().getName()); |
| if (templateEngine == null || templateEngine.isInitialized()) { |
| serviceTemplateEngine(); |
| } else { |
| templateEngine.setCacheManager(cacheManager); |
| } |
| } |
| } |
| |
| protected void unsetCacheManager(final ICacheManager cacheManager) { |
| synchronized (lock) { |
| logger.debug("unsetting cache manager '{}'", cacheManager.getClass().getName()); |
| serviceTemplateEngine(); |
| } |
| } |
| |
| protected void setEngineContextFactory(final IEngineContextFactory engineContextFactory) { |
| synchronized (lock) { |
| logger.debug("setting engine context factory '{}'", engineContextFactory.getClass().getName()); |
| if (templateEngine == null || templateEngine.isInitialized()) { |
| serviceTemplateEngine(); |
| } else { |
| templateEngine.setEngineContextFactory(engineContextFactory); |
| } |
| } |
| } |
| |
| protected void unsetEngineContextFactory(final IEngineContextFactory engineContextFactory) { |
| synchronized (lock) { |
| logger.debug("unsetting engine context factory '{}'", engineContextFactory.getClass().getName()); |
| serviceTemplateEngine(); |
| } |
| } |
| |
| @Activate |
| private void activate(final ThymeleafScriptEngineFactoryConfiguration configuration, final BundleContext bundleContext) { |
| logger.debug("activating"); |
| this.configuration = configuration; |
| this.bundleContext = bundleContext; |
| configure(configuration); |
| setupTemplateEngine(); |
| registerTemplateEngine(); |
| } |
| |
| @Modified |
| private void modified(final ThymeleafScriptEngineFactoryConfiguration configuration) { |
| logger.debug("modifying"); |
| this.configuration = configuration; |
| configure(configuration); |
| } |
| |
| @Deactivate |
| private void deactivate() { |
| logger.debug("deactivating"); |
| unregisterTemplateEngine(); |
| templateEngine = null; |
| bundleContext = null; |
| } |
| |
| private void configure(final ThymeleafScriptEngineFactoryConfiguration configuration) { |
| setExtensions(configuration.extensions()); |
| setMimeTypes(configuration.mimeTypes()); |
| setNames(configuration.names()); |
| } |
| |
| @Override |
| public String getLanguageName() { |
| return "Thymeleaf"; |
| } |
| |
| @Override |
| public String getLanguageVersion() { |
| try { |
| final Properties properties = new Properties(); |
| properties.load(getClass().getResourceAsStream(THYMELEAF_PROPERTIES)); |
| return properties.getProperty("version"); |
| } catch (Exception e) { |
| logger.error("error reading version from " + THYMELEAF_PROPERTIES, e); |
| return ""; // null breaks output of web console |
| } |
| } |
| |
| @Override |
| public ScriptEngine getScriptEngine() { |
| logger.debug("getting script engine for Thymeleaf"); |
| return new ThymeleafScriptEngine(this); |
| } |
| |
| private void serviceTemplateEngine() { |
| unregisterTemplateEngine(); |
| setupTemplateEngine(); |
| registerTemplateEngine(); |
| } |
| |
| private void setupTemplateEngine() { |
| logger.info("setting up new template engine"); |
| templateEngine = null; |
| if (configuration == null) { |
| logger.info("configuration is null, not setting up new template engine"); |
| return; |
| } |
| |
| if (!configuration.useStandardEngineContextFactory() && engineContextFactory == null) { |
| logger.info("no engine context factory available, not setting up new template engine"); |
| return; |
| } |
| |
| // setup template engine |
| final TemplateEngine templateEngine = new TemplateEngine(); |
| // Template Resolvers |
| if (this.templateResolvers != null) { |
| final Set<ITemplateResolver> templateResolvers = new HashSet<>(this.templateResolvers); |
| templateEngine.setTemplateResolvers(templateResolvers); |
| } |
| // Message Resolvers |
| if (this.messageResolvers != null) { |
| final Set<IMessageResolver> messageResolvers = new HashSet<>(this.messageResolvers); |
| templateEngine.setMessageResolvers(messageResolvers); |
| } |
| if (configuration.useStandardMessageResolver()) { |
| final IMessageResolver standardMessageResolver = new StandardMessageResolver(); |
| templateEngine.addMessageResolver(standardMessageResolver); |
| } |
| // Link Builders |
| if (this.linkBuilders != null) { |
| final Set<ILinkBuilder> linkBuilders = new HashSet<>(this.linkBuilders); |
| templateEngine.setLinkBuilders(linkBuilders); |
| } |
| if (configuration.useStandardLinkBuilder()) { |
| final ILinkBuilder standardLinkBuilder = new StandardLinkBuilder(); |
| templateEngine.addLinkBuilder(standardLinkBuilder); |
| } |
| // Dialects |
| if (this.dialects != null) { |
| final Set<IDialect> dialects = new HashSet<>(this.dialects); |
| templateEngine.setDialects(dialects); |
| } |
| if (configuration.useStandardDialect()) { |
| final IDialect standardDialect = new StandardDialect(); |
| templateEngine.addDialect(standardDialect); |
| } |
| // Decoupled Template Logic Resolver |
| if (!configuration.useStandardDecoupledTemplateLogicResolver()) { |
| templateEngine.setDecoupledTemplateLogicResolver(decoupledTemplateLogicResolver); |
| } |
| // Cache Manager |
| if (!configuration.useStandardCacheManager()) { |
| templateEngine.setCacheManager(cacheManager); |
| } |
| // Engine Context Factory |
| if (!configuration.useStandardEngineContextFactory()) { |
| templateEngine.setEngineContextFactory(engineContextFactory); |
| } |
| // |
| this.templateEngine = templateEngine; |
| } |
| |
| private void registerTemplateEngine() { |
| if (templateEngine == null || templateEngine.getTemplateResolvers().isEmpty() || templateEngine.getMessageResolvers().isEmpty() || templateEngine.getDialects().isEmpty()) { |
| return; |
| } |
| final Dictionary<String, String> properties = new Hashtable<>(); |
| properties.put(Constants.SERVICE_DESCRIPTION, "Thymeleaf TemplateEngine"); |
| properties.put(Constants.SERVICE_VENDOR, "The Thymeleaf Team"); |
| logger.info("registering {} as service {} with properties {}", templateEngine, ITemplateEngine.class.getName(), properties); |
| serviceRegistration = bundleContext.registerService(ITemplateEngine.class, templateEngine, properties); |
| } |
| |
| private void unregisterTemplateEngine() { |
| if (serviceRegistration != null) { |
| serviceRegistration.unregister(); |
| serviceRegistration = null; |
| } |
| } |
| |
| TemplateEngine getTemplateEngine() { |
| synchronized (lock) { |
| if (templateEngine == null) { |
| serviceTemplateEngine(); |
| } |
| return templateEngine; |
| } |
| } |
| |
| ResourceResolver getRequestScopedResourceResolver() { |
| return scriptingResourceResolverProvider.getRequestScopedResourceResolver(); |
| } |
| |
| ServletContext getSlingServletContext() { |
| return servletContext; |
| } |
| |
| } |