/** * *************************************************************************** * Copyright (c) 2010 Qcadoo Limited * Project: Qcadoo Framework * Version: 1.4 * * This file is part of Qcadoo. * * Qcadoo is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation; either version 3 of the License, * or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *************************************************************************** */ package com.qcadoo.model.internal; import com.qcadoo.localization.api.TranslationService; import com.qcadoo.model.api.Entity; import com.qcadoo.model.api.ExpressionService; import com.qcadoo.model.api.FieldDefinition; import com.qcadoo.model.api.types.BelongsToType; import com.qcadoo.model.api.types.FieldType; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.expression.EvaluationContext; import org.springframework.expression.Expression; import org.springframework.expression.ExpressionParser; import org.springframework.expression.spel.SpelEvaluationException; import org.springframework.expression.spel.SpelMessage; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Helper class that contains methods to evaluate expression value. */ @Component public final class ExpressionServiceImpl implements ExpressionService { private static final Logger LOG = LoggerFactory.getLogger(ExpressionServiceImpl.class); private static final int ENTITY_FLATTENING_DEPTH = 2; private static final String TOO_MANY_GET_METHOD_INVOCATIONS_HINT = String .format("Be sure tha you don't call .get(String) or [String] more than %s times in a single traverse expression.", ENTITY_FLATTENING_DEPTH); private static final String EVALUATION_RESULT_DEBUG_MESSAGE = "Calculating value of expression \"%s\" for %s : %s"; private static final String EVALUATION_ERROR_MESSAGE = "Error while calculating value of expression \"%s\" for \"%s\"."; private static ExpressionService instance = null; @Autowired private TranslationService translationService; @PostConstruct public void init() { initialise(this); } private static void initialise(final ExpressionService expressionService) { instance = expressionService; } public static ExpressionService getInstance() { return instance; } @Override public String getValue(final Entity entity, final List<FieldDefinition> fieldDefinitions, final Locale locale) { String value = null; if (fieldDefinitions.size() == 1) { FieldDefinition field = fieldDefinitions.get(0); value = field.getValue(entity.getField(field.getName()), locale); } else { List<String> values = new ArrayList<String>(); for (FieldDefinition fieldDefinition : fieldDefinitions) { values.add(fieldDefinition.getValue(entity.getField(fieldDefinition.getName()), locale)); } value = StringUtils.join(values, ", "); } if (LOG.isDebugEnabled()) { LOG.debug("Calculating value of fields " + fieldDefinitions + " for " + entity + " : " + value); } if (StringUtils.isEmpty(value) || "null".equals(value)) { return null; } else { return value; } } @Override public String getValue(final Entity entity, final String expression, final Locale locale) { if (StringUtils.isEmpty(expression) || "null".equals(expression)) { LOG.debug("Calculating empty expressions"); return null; } String value = evaluateExpression(expression, entity, locale); if (StringUtils.isEmpty(value) || "null".equals(value)) { return null; } else { return translate(value, locale); } } private String evaluateExpression(final String expression, final Entity entity, final Locale locale) { ExpressionParser parser = new SpelExpressionParser(); Expression exp = parser.parseExpression(expression); EvaluationContext evaluationContext = getEvaluationContext(entity, locale); try { String value = String.valueOf(exp.getValue(evaluationContext)); if (LOG.isDebugEnabled()) { LOG.debug(String.format(EVALUATION_RESULT_DEBUG_MESSAGE, expression, entity, value)); } return value; } catch (SpelEvaluationException e) { if (SpelMessage.CANNOT_INDEX_INTO_NULL_VALUE.equals(e.getMessageCode())) { return ""; } logFailure(expression, entity, e); return "!!!"; } } private EvaluationContext getEvaluationContext(final Entity entity, final Locale locale) { EvaluationContext context = new StandardEvaluationContext(); if (entity != null) { Map<String, Object> values = getValuesForEntity(entity, locale, ENTITY_FLATTENING_DEPTH); for (Map.Entry<String, Object> entry : values.entrySet()) { context.setVariable(entry.getKey(), entry.getValue()); } } return context; } private void logFailure(final String expression, final Entity entity, final SpelEvaluationException exception) { String errorMsg = String.format(EVALUATION_ERROR_MESSAGE, expression, entity); if (SpelMessage.METHOD_NOT_FOUND.equals(exception.getMessageCode())) { errorMsg += TOO_MANY_GET_METHOD_INVOCATIONS_HINT; } LOG.error(errorMsg, exception); } private String translate(final String expression, final Locale locale) { if (locale == null) { return expression; } Matcher m = Pattern.compile("\\@([a-zA-Z_0-9\\.]+)").matcher(expression); StringBuffer sb = new StringBuffer(); int i = 0; while (m.find()) { sb.append(expression.substring(i, m.start())); sb.append(translationService.translate(m.group(1), locale)); i = m.end(); } if (i == 0) { return expression; } sb.append(expression.substring(i, expression.length())); return sb.toString(); } private Map<String, Object> getValuesForEntity(final Entity entity, final Locale locale, final int level) { if (entity == null) { return null; } Map<String, Object> values = new HashMap<String, Object>(); values.put("id", entity.getId()); if (level == 0) { values.putAll(entity.getFields()); return values; } for (Map.Entry<String, Object> entry : entity.getFields().entrySet()) { if (entry.getValue() instanceof Collection) { values.put(entry.getKey(), entry.getValue()); } else { FieldType type = entity.getDataDefinition().getField(entry.getKey()).getType(); if (type instanceof BelongsToType) { Entity belongsToEntity = getBelongsToEntity(entry.getValue(), (BelongsToType) type); values.put(entry.getKey(), getValuesForEntity(belongsToEntity, locale, level - 1)); } else { String value = null; if (entry.getValue() != null) { value = type.toString(entry.getValue(), locale); } values.put(entry.getKey(), value); } } } return values; } private Entity getBelongsToEntity(final Object value, final BelongsToType type) { if (value instanceof Entity) { return (Entity) value; } else if (value instanceof Number || value instanceof String) { return type.getDataDefinition().get(Long.parseLong(value.toString())); } else { return null; } } }