/*
* #%L
* Wisdom-Framework
* %%
* Copyright (C) 2013 - 2014 Wisdom Framework
* %%
* Licensed 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.
* #L%
*/
package org.wisdom.template.thymeleaf.dialect;
import ognl.ClassResolver;
import ognl.Ognl;
import ognl.OgnlContext;
import ognl.OgnlException;
import org.osgi.framework.Bundle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.thymeleaf.Configuration;
import org.thymeleaf.cache.ICache;
import org.thymeleaf.cache.ICacheManager;
import org.thymeleaf.context.IProcessingContext;
import org.thymeleaf.exceptions.TemplateProcessingException;
import org.thymeleaf.standard.expression.*;
import org.wisdom.api.http.Context;
import java.util.HashMap;
import java.util.Map;
/**
* An extended version of the standard evaluator injecting the `routes` expression object.
*/
public class ExtendedOGNLExpressionEvaluator implements IStandardVariableExpressionEvaluator {
public static final ExtendedOGNLExpressionEvaluator INSTANCE = new ExtendedOGNLExpressionEvaluator();
private static final Logger LOGGER = LoggerFactory.getLogger(OgnlVariableExpressionEvaluator.class);
private static final String OGNL_CACHE_PREFIX = "{ognl}";
public static final String BUNDLE_VAR_KEY = "__bundle__";
protected Map<String, Object> computeAdditionalContextVariables(IProcessingContext processingContext) {
Map<String, Object> var = new HashMap<>();
var.put(Routes.OBJECT_NAME, processingContext.getContext().getVariables().get(Routes.ROUTES_VAR));
final Context context = Context.CONTEXT.get();
if (context != null) {
var.put("http", context);
var.put("session", context.session());
var.put("flash", context.flash());
var.put("request", context.request());
var.put("parameters", context.parameters());
}
return var;
}
@Override
public String toString() {
return "OGNL extended by Wisdom";
}
public final Object evaluate(final Configuration configuration,
final IProcessingContext processingContext, final String expression,
final StandardExpressionExecutionContext expContext, final boolean useSelectionAsRoot) {
try {
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("OGNL expression: evaluating expression \"{}\" on target", expression);
}
Object expressionTree = null;
ICache<String, Object> cache = null;
if (configuration != null) {
final ICacheManager cacheManager = configuration.getCacheManager();
if (cacheManager != null) {
cache = cacheManager.getExpressionCache();
}
}
if (cache != null) {
expressionTree = cache.get(OGNL_CACHE_PREFIX + expression);
}
if (expressionTree == null) {
expressionTree = ognl.Ognl.parseExpression(expression);
if (cache != null && null != expressionTree) {
cache.put(OGNL_CACHE_PREFIX + expression, expressionTree);
}
}
final OgnlContext ctxt = new OgnlContext();
ctxt.putAll(processingContext.getExpressionObjects());
final Map<String, Object> additionalContextVariables = computeAdditionalContextVariables(processingContext);
if (additionalContextVariables != null) {
ctxt.putAll(additionalContextVariables);
}
final Object evaluationRoot =
(useSelectionAsRoot ?
processingContext.getExpressionSelectionEvaluationRoot() :
processingContext.getExpressionEvaluationRoot());
// If we have a bundle set, customize the class loading.
if ((ctxt.get("vars") instanceof Map) && ((Map) ctxt.get("vars")).containsKey(BUNDLE_VAR_KEY)) {
final Bundle bundle = (Bundle) ((Map) ctxt.get("vars")).get(BUNDLE_VAR_KEY);
Ognl.setClassResolver(ctxt, new ClassResolver() {
/**
* Loads a class. This method is called when the processing of a template requires a class. The
* class loading defines in this method uses the {@link org.osgi.framework.Bundle} object passed
* in the context. If the class cannot be found, it falls back to the system bundle.
* @param className the class name
* @param context the context.
* @return the class object
* @throws ClassNotFoundException if the class cannot be found
*/
@Override
public Class classForName(String className, Map context) throws ClassNotFoundException {
try {
return bundle.loadClass(className);
} catch (ClassNotFoundException e) { //NOSONAR
// Ignore it.
}
// Try with the system bundle, if the bundle is not the system bundle
if (bundle.getBundleId() != 0) {
try {
return bundle.getBundleContext().getBundle(0).loadClass(className);
} catch (ClassNotFoundException e) { //NOSONAR
// Ignore it.
}
}
// Nothing we can do.
LOGGER.warn("A template tried to load the '" + className + "' class, " +
"but this class is not available. Try to import it in the bundle containing the " +
"template.");
throw new ClassNotFoundException(className);
}
});
}
final Object result = Ognl.getValue(expressionTree, ctxt, evaluationRoot);
if (!expContext.getPerformTypeConversion()) {
return result;
}
final IStandardConversionService conversionService =
StandardExpressions.getConversionService(configuration);
return conversionService.convert(configuration, processingContext, result, String.class);
} catch (final OgnlException e) {
throw new TemplateProcessingException(
"Exception evaluating OGNL expression: \"" + expression + "\"", e);
}
}
}