org.thymeleaf.standard.expression.FragmentExpression Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of thymeleaf Show documentation
Show all versions of thymeleaf Show documentation
Modern server-side Java template engine for both web and standalone environments
/*
* =============================================================================
*
* Copyright (c) 2011-2016, The THYMELEAF team (http://www.thymeleaf.org)
*
* 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.
*
* =============================================================================
*/
package org.thymeleaf.standard.expression;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.thymeleaf.IEngineConfiguration;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.IExpressionContext;
import org.thymeleaf.context.ITemplateContext;
import org.thymeleaf.engine.TemplateModel;
import org.thymeleaf.exceptions.TemplateInputException;
import org.thymeleaf.exceptions.TemplateProcessingException;
import org.thymeleaf.util.StringUtils;
import org.thymeleaf.util.Validate;
/**
*
* @author Daniel Fernández
*
* @since 3.0.0
*
*/
public final class FragmentExpression extends SimpleExpression {
private static final Logger logger = LoggerFactory.getLogger(FragmentExpression.class);
private static final long serialVersionUID = -130371297698708001L;
/**
* This constant contains the {@link FragmentExpression} object representing the EMPTY FRAGMENT (~{})
*/
public static final FragmentExpression EMPTY_FRAGMENT_EXPRESSION = new FragmentExpression();
private static final String TEMPLATE_NAME_CURRENT_TEMPLATE = "this";
private static final String SEPARATOR = "::";
static final String UNNAMED_PARAMETERS_PREFIX = "_arg";
public static final char SELECTOR = '~';
private static final Pattern FRAGMENT_PATTERN =
Pattern.compile("^\\s*~\\{(.*?)\\}\\s*$", Pattern.DOTALL);
private final IStandardExpression templateName;
private final IStandardExpression fragmentSelector;
private final AssignationSequence parameters;
private final boolean syntheticParameters;
public FragmentExpression(
final IStandardExpression templateName, final IStandardExpression fragmentSelector,
final AssignationSequence parameters, final boolean syntheticParameters) {
super();
// templateName can be null if fragment is to be executed on the current template, but then there should
// at least be a fragment selector
if (templateName == null && fragmentSelector == null) {
throw new IllegalArgumentException(
"Fragment Expression cannot have null template name and null fragment selector");
}
this.templateName = templateName;
this.fragmentSelector = fragmentSelector;
this.parameters = parameters;
this.syntheticParameters = (this.parameters != null && this.parameters.size() > 0 && syntheticParameters);
}
// Creates the empty Fragment Expression
private FragmentExpression() {
super();
this.templateName = null;
this.fragmentSelector = null;
this.parameters = null;
this.syntheticParameters = false;
}
public IStandardExpression getTemplateName() {
return this.templateName;
}
public IStandardExpression getFragmentSelector() {
return this.fragmentSelector;
}
public boolean hasFragmentSelector() {
return this.fragmentSelector != null;
}
public AssignationSequence getParameters() {
return this.parameters;
}
public boolean hasParameters() {
return this.parameters != null && this.parameters.size() > 0;
}
public boolean hasSyntheticParameters() {
return this.syntheticParameters;
}
@Override
public String getStringRepresentation() {
final StringBuilder sb = new StringBuilder();
sb.append(SELECTOR);
sb.append(SimpleExpression.EXPRESSION_START_CHAR);
sb.append(this.templateName != null? this.templateName.getStringRepresentation() : "");
if (this.fragmentSelector != null) {
sb.append(' ');
sb.append(SEPARATOR);
sb.append(' ');
sb.append(this.fragmentSelector.getStringRepresentation());
}
if (this.parameters != null && this.parameters.size() > 0) {
sb.append(' ');
sb.append('(');
sb.append(StringUtils.join(this.parameters.getAssignations(), ','));
sb.append(')');
}
sb.append(SimpleExpression.EXPRESSION_END_CHAR);
return sb.toString();
}
public static FragmentExpression parseFragmentExpression(final String input) {
final Matcher matcher = FRAGMENT_PATTERN.matcher(input);
if (!matcher.matches()) {
return null;
}
final String expression = matcher.group(1);
if (StringUtils.isEmptyOrWhitespace(expression)) {
return EMPTY_FRAGMENT_EXPRESSION;
}
return parseFragmentExpressionContent(expression.trim());
}
static FragmentExpression parseFragmentExpressionContent(final String input) {
if (StringUtils.isEmptyOrWhitespace(input)) {
return EMPTY_FRAGMENT_EXPRESSION;
}
final String trimmedInput = input.trim();
final int lastParenthesesGroupPos = indexOfLastParenthesesGroup(trimmedInput);
final String inputWithoutParameters;
String parametersStr;
if (lastParenthesesGroupPos != -1) {
parametersStr = trimmedInput.substring(lastParenthesesGroupPos).trim();
inputWithoutParameters = trimmedInput.substring(0, lastParenthesesGroupPos).trim();
} else {
parametersStr = null;
inputWithoutParameters = trimmedInput;
}
String templateNameStr;
String fragmentSpecStr;
final int operatorPos = inputWithoutParameters.indexOf(SEPARATOR);
if (operatorPos == -1) {
// no operator means everything is considered "before operator" (there is template name, but no
// fragment name -- template is to be included in its entirety).
templateNameStr = inputWithoutParameters;
fragmentSpecStr = null;
if (StringUtils.isEmptyOrWhitespace(templateNameStr)) {
if (parametersStr != null) {
// Parameters weren't parameters, they actually were the template name!
templateNameStr = parametersStr;
parametersStr = null;
} else {
// parameters are null, so template name is empty, and therefore wrong.
return null;
}
}
} else {
// There IS operator: we should divide between template name (which can be empty) and fragment spec.
templateNameStr = inputWithoutParameters.substring(0, operatorPos).trim();
fragmentSpecStr = inputWithoutParameters.substring(operatorPos + SEPARATOR.length()).trim();
if (StringUtils.isEmptyOrWhitespace(fragmentSpecStr)) {
if (parametersStr != null) {
// Parameters weren't parameters, they actually were the fragment spec!
fragmentSpecStr = parametersStr;
parametersStr = null;
} else {
// parameters are null, so fragment specification is empty, and therefore wrong (because we
// have already established that the :: operator IS present.
return null;
}
}
}
final Expression templateNameExpression;
if (!StringUtils.isEmptyOrWhitespace(templateNameStr)) {
templateNameExpression = parseDefaultAsLiteral(templateNameStr);
if (templateNameExpression == null) {
return null;
}
} else {
templateNameExpression = null;
}
final Expression fragmentSpecExpression;
if (!StringUtils.isEmptyOrWhitespace(fragmentSpecStr)) {
fragmentSpecExpression = parseDefaultAsLiteral(fragmentSpecStr);
if (fragmentSpecExpression == null) {
return null;
}
} else {
fragmentSpecExpression = null;
}
if (!StringUtils.isEmptyOrWhitespace(parametersStr)) {
// When parsing this, we don't allow parameters without value because we would be mistakenly
// parsing as parameter names what in fact are values for synthetically named parameters.
final AssignationSequence parametersAsSeq =
AssignationUtils.internalParseAssignationSequence(parametersStr, false);
if (parametersAsSeq != null) {
return new FragmentExpression(templateNameExpression, fragmentSpecExpression, parametersAsSeq, false);
}
// Parameters wheren't parsable as an assignation sequence. So we should try parsing as Expression
// sequence and create a synthetically named parameter sequence with the expressions in the sequence as
// values.
final ExpressionSequence parametersExpSeq =
ExpressionSequenceUtils.internalParseExpressionSequence(parametersStr);
if (parametersExpSeq != null) {
final AssignationSequence parametersAsSeqFromExp =
createSyntheticallyNamedParameterSequence(parametersExpSeq);
return new FragmentExpression(templateNameExpression, fragmentSpecExpression, parametersAsSeqFromExp, true);
}
// The parameters str is not parsable neither as an assignation sequence nor as an expression sequence,
// so we can come to the conclusion it is wrong.
return null;
}
return new FragmentExpression(templateNameExpression, fragmentSpecExpression, null, false);
}
private static Expression parseDefaultAsLiteral(final String input) {
if (StringUtils.isEmptyOrWhitespace(input)) {
return null;
}
final Expression expr = Expression.parse(input);
if (expr == null) {
return Expression.parse(TextLiteralExpression.wrapStringIntoLiteral(input));
}
return expr;
}
private static int indexOfLastParenthesesGroup(final String input) {
final int inputLen = input.length();
final char finalC = input.charAt(inputLen - 1);
if (finalC != ')') {
// If there are parentheses, the last char must be an ending one.
return -1;
}
int parenLevel = 1;
for (int i = inputLen - 2; i >= 0; i--) {
final char c = input.charAt(i);
if (c == '(') {
parenLevel--;
if (parenLevel == 0) {
// We have closed a parenthesis at level 0, this might be what we were looking for.
if (i == (inputLen - 2)) {
// These are not real parameters, but "()", which might be a "text()" node selector.
return -1;
}
return i;
}
} else if (c == ')') {
parenLevel++;
}
}
// Cannot parse: will never be able to determine whether there are parameters or not, because they aren't
// correctly closed. Just return -1 as if we didn't find parentheses at all.
return -1;
}
private static AssignationSequence createSyntheticallyNamedParameterSequence(final ExpressionSequence expSeq) {
final List assignations = new ArrayList(expSeq.size() + 2);
int argIndex = 0;
for (final IStandardExpression expression : expSeq.getExpressions()) {
final IStandardExpression parameterName =
Expression.parse(TextLiteralExpression.wrapStringIntoLiteral(UNNAMED_PARAMETERS_PREFIX + argIndex++));
assignations.add(new Assignation(parameterName, expression));
}
return new AssignationSequence(assignations);
}
static Fragment executeFragmentExpression(
final IExpressionContext context,
final FragmentExpression expression, final StandardExpressionExecutionContext expContext) {
if (!(context instanceof ITemplateContext)) {
throw new TemplateProcessingException(
"Cannot evaluate expression \"" + expression + "\". Fragment expressions " +
"can only be evaluated in a template-processing environment (as a part of an in-template expression) " +
"where processing context is an implementation of " + ITemplateContext.class.getClass() + ", which it isn't (" +
context.getClass().getName() + ")");
}
if (expression == EMPTY_FRAGMENT_EXPRESSION) {
return Fragment.EMPTY_FRAGMENT;
}
return resolveExecutedFragmentExpression(
(ITemplateContext) context,
createExecutedFragmentExpression(context, expression, expContext),
// By default we will NOT consider a non existing template a failure, so that we give the system the chance
// to return null here (in exchange for a call to resource.exists()). So this false will always be
// applied in scenarios such as when the fragment expression is used as a parameter in a larger
// fragment expression: "template : fragment (part=~{expr})"
false);
}
public static ExecutedFragmentExpression createExecutedFragmentExpression(
final IExpressionContext context,
final FragmentExpression expression, final StandardExpressionExecutionContext expContext) {
Validate.notNull(context, "Context cannot be null");
Validate.notNull(expression, "Fragment Expression cannot be null");
if (logger.isTraceEnabled()) {
logger.trace("[THYMELEAF][{}] Evaluating fragment: \"{}\"", TemplateEngine.threadIndex(), expression.getStringRepresentation());
}
/*
* CHECK THE EMPTY EXPRESSION FRAGMENT
*/
if (expression == EMPTY_FRAGMENT_EXPRESSION) {
return ExecutedFragmentExpression.EMPTY_EXECUTED_FRAGMENT_EXPRESSION;
}
/*
* COMPUTE THE TEMPLATE NAME
*/
final IStandardExpression templateNameExpression = expression.getTemplateName();
final Object templateNameExpressionResult;
if (templateNameExpression != null) {
// Note we will apply restricted variable access for resolving template names in fragment specs. This
// protects against the possibility of code injection attacks from request parameters.
templateNameExpressionResult = templateNameExpression.execute(context, StandardExpressionExecutionContext.RESTRICTED);
// We actually DO allow this expression to return null. It can be a way to signal we want to execute the
// fragment on the current template.
} else {
// If template name expression is null, we will execute the fragment on the "current" template
templateNameExpressionResult = null;
}
/*
* RESOLVE FRAGMENT PARAMETERS if specified (null if not)
*/
final Map fragmentParameters =
createExecutedFragmentExpressionParameters(context, expression.getParameters(), expression.hasSyntheticParameters(), expContext);
/*
* COMPUTE THE FRAGMENT SELECTOR
*/
final Object fragmentSelectorExpressionResult;
if (expression.hasFragmentSelector()) {
// Note we will NOT apply restricted variable access for resolving fragment selectors
fragmentSelectorExpressionResult = expression.getFragmentSelector().execute(context, expContext);
// We actually DO allow this expression to return null. It can be a way to signal we want to use the entire
// template as a fragment
} else {
fragmentSelectorExpressionResult = null;
}
/*
* CREATE the final resulting object
*/
return new ExecutedFragmentExpression(
expression,
templateNameExpressionResult, fragmentSelectorExpressionResult,
fragmentParameters, expression.hasSyntheticParameters());
}
private static Map createExecutedFragmentExpressionParameters(
final IExpressionContext context,
final AssignationSequence parameters, final boolean syntheticParameters,
final StandardExpressionExecutionContext expContext) {
if (parameters == null || parameters.size() == 0) {
return null;
}
final Map parameterValues = new HashMap(parameters.size() + 2);
final List assignationValues = parameters.getAssignations();
final int assignationValuesLen = assignationValues.size();
for (int i = 0; i < assignationValuesLen; i++) {
final Assignation assignation = assignationValues.get(i);
final IStandardExpression parameterNameExpr = assignation.getLeft();
final String parameterName;
if (!syntheticParameters) {
final Object parameterNameValue = parameterNameExpr.execute(context, expContext);
parameterName = (parameterNameValue == null ? null : parameterNameValue.toString());
} else {
// Parameters are synthetic so we know this is a mere literal like "_argX", no need to perform an exec
parameterName = ((TextLiteralExpression)parameterNameExpr).getValue().getValue();
}
final IStandardExpression parameterValueExpr = assignation.getRight();
final Object parameterValueValue = parameterValueExpr.execute(context, expContext);
parameterValues.put(parameterName, parameterValueValue);
}
return parameterValues;
}
public static Fragment resolveExecutedFragmentExpression(
final ITemplateContext context, final ExecutedFragmentExpression executedFragmentExpression,
final boolean failIfNotExists) {
if (executedFragmentExpression == ExecutedFragmentExpression.EMPTY_EXECUTED_FRAGMENT_EXPRESSION) {
return Fragment.EMPTY_FRAGMENT;
}
final IEngineConfiguration configuration = context.getConfiguration();
/*
* COMPUTE template name as String
*/
String templateName = resolveTemplateName(executedFragmentExpression);
/*
* COMPUTE fragment selector as String
*/
final Set fragments = resolveFragments(executedFragmentExpression);
/*
* RESOLVE THE FRAGMENT MODEL by using the TemplateManager. This means the fragment will be parsed and maybe
* cached, and we will be returned an immutable model object
*/
List templateNameStack = null;
// scan the template stack if template name is 'this' or an empty name is being used
if (StringUtils.isEmptyOrWhitespace(templateName)) {
if (fragments == null || fragments.isEmpty()) {
return null;
}
templateNameStack = new ArrayList(3);
for (int i = context.getTemplateStack().size() - 1; i >= 0; i--) {
templateNameStack.add(context.getTemplateStack().get(i).getTemplate());
}
templateName = templateNameStack.get(0);
}
TemplateModel fragmentModel;
int i = 0;
do {
fragmentModel =
configuration.getTemplateManager().parseStandalone(
context, templateName, fragments,
null, // we will not force the template mode
true, // use the cache if possible, fragments are from template files
failIfNotExists); // depending on the scenario we will consider a non exiting template a fail or not
i++;
} while (fragmentModel != null && // template not found (only if resolver configuration allows)
fragmentModel.size() <= 2 && // template found, but selector not found
templateNameStack != null && // we have more templates to look into
i < templateNameStack.size() && // we have more templates to look into
(templateName = templateNameStack.get(i)) != null); //post test -- need to parse at least 1x
if (fragmentModel == null) {
// FragmentExpressions can actually return null, so that this null value can be used in standard expressions
// such as "~{template} ? ~{default}"
return null;
}
/*
* We should now check if the resolved fragment actually exists or not (we know the template exists but,
* did the fragment actually return anything at all?
*/
final boolean fragmentIsEmpty = (fragmentModel.size() == 2); //only templatestart/end
if (fragmentIsEmpty) {
// Fragment is empty, so we should take action: depending on whether we are allowing this or not, we should
// either fail or simply return null
if (failIfNotExists) {
throw new TemplateInputException(
"Error resolving fragment: \"" + executedFragmentExpression.fragmentExpression.getStringRepresentation() + "\": " +
"template or fragment could not be resolved");
}
return null;
}
/*
* RETURN the expected Fragment object
*/
return new Fragment(fragmentModel, executedFragmentExpression.fragmentParameters, executedFragmentExpression.syntheticParameters);
}
public static String resolveTemplateName(final ExecutedFragmentExpression executedFragmentExpression) {
final Object templateNameObject = executedFragmentExpression.templateNameExpressionResult;
if (templateNameObject == null) {
// If template name expression result is null, we will execute the fragment on the "current" template
return null;
}
final String evaluatedTemplateName = templateNameObject.toString();
if (TEMPLATE_NAME_CURRENT_TEMPLATE.equals(evaluatedTemplateName)) {
// Template name is "this" and therefore we are including a fragment from the same template.
return null;
}
return templateNameObject.toString();
}
public static Set resolveFragments(final ExecutedFragmentExpression executedFragmentExpression) {
final Object fragmentSelectorObject = executedFragmentExpression.fragmentSelectorExpressionResult;
if (fragmentSelectorObject != null) {
String fragmentSelector = fragmentSelectorObject.toString();
if (fragmentSelector.length() > 3 &&
fragmentSelector.charAt(0) == '[' && fragmentSelector.charAt(fragmentSelector.length() - 1) == ']' &&
fragmentSelector.charAt(fragmentSelector.length() - 2) != '\'') {
// For legacy compatibility reasons, we allow fragment DOM Selector expressions to be specified
// between brackets. Just remove them.
fragmentSelector = fragmentSelector.substring(1, fragmentSelector.length() - 1).trim();
}
if (fragmentSelector.trim().length() > 0) {
return Collections.singleton(fragmentSelector);
}
}
return null;
}
public static final class ExecutedFragmentExpression {
public static final ExecutedFragmentExpression EMPTY_EXECUTED_FRAGMENT_EXPRESSION =
new ExecutedFragmentExpression(EMPTY_FRAGMENT_EXPRESSION, null, null, null, false);
private final FragmentExpression fragmentExpression;
private final Object templateNameExpressionResult;
private final Object fragmentSelectorExpressionResult;
private final Map fragmentParameters;
private final boolean syntheticParameters;
ExecutedFragmentExpression(
final FragmentExpression fragmentExpression,
final Object templateNameExpressionResult, final Object fragmentSelectorExpressionResult,
final Map fragmentParameters, final boolean syntheticParameters) {
super();
this.fragmentExpression = fragmentExpression;
this.templateNameExpressionResult = templateNameExpressionResult;
this.fragmentSelectorExpressionResult = fragmentSelectorExpressionResult;
this.fragmentParameters = fragmentParameters;
this.syntheticParameters = syntheticParameters;
}
FragmentExpression getFragmentExpression() {
return this.fragmentExpression;
}
public Object getTemplateNameExpressionResult() {
return this.templateNameExpressionResult;
}
public Object getFragmentSelectorExpressionResult() {
return this.fragmentSelectorExpressionResult;
}
public Map getFragmentParameters() {
return this.fragmentParameters;
}
public boolean hasSyntheticParameters() {
return this.syntheticParameters;
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy