blob: f19d1ac004baed773670c3a13ec103f18c534d8f [file] [log] [blame]
/*
* 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;
}
}