package com.constellio.model.entities.calculators;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.jexl3.JexlBuilder;
import org.apache.commons.jexl3.JexlContext;
import org.apache.commons.jexl3.JexlEngine;
import org.apache.commons.jexl3.JexlException;
import org.apache.commons.jexl3.JexlInfo;
import org.apache.commons.jexl3.JexlScript;
import org.apache.commons.jexl3.MapContext;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.constellio.model.entities.calculators.dependencies.Dependency;
import com.constellio.model.entities.calculators.dependencies.LocalDependency;
import com.constellio.model.entities.calculators.dependencies.ReferenceDependency;
import com.constellio.model.entities.schemas.Metadata;
import com.constellio.model.entities.schemas.MetadataSchema;
import com.constellio.model.entities.schemas.MetadataSchemaTypes;
import com.constellio.model.entities.schemas.MetadataValueType;
import com.constellio.model.utils.Parametrized;
public class JEXLMetadataValueCalculator implements InitializedMetadataValueCalculator<Object>, Parametrized {
MetadataValueType type;
String metadataCode;
JexlScript jexlScript;
Set<List<String>> variables;
private static final Logger LOGGER = LoggerFactory.getLogger(JEXLMetadataValueCalculator.class);
String expression;
List<Dependency> dependencies = new ArrayList<>();
public JEXLMetadataValueCalculator(String expression) {
this.expression = expression;
}
public JexlScript getJexlScript() {
return jexlScript;
}
@Override
public Object calculate(CalculatorParameters parameters) {
JexlContext jc = prepareJexlContext(parameters);
try {
Object calculatedValue = jexlScript.execute(jc);
return "null".equals(calculatedValue) ? null : calculatedValue;
} catch (JexlException e) {
logJexlException(e);
return null;
} catch (Exception e) {
Throwable t = e.getCause();
e.printStackTrace();
return null;
}
}
private void logJexlException(JexlException e) {
JexlInfo info = e.getInfo();
String cause = StringUtils.substringAfterLast(e.getCause().getClass().getName(), ".");
StringBuilder message = new StringBuilder().append("Jexl Script of metadata '").append(metadataCode)
.append("' failed at column ").append(info.getColumn()).append(" of line ").append(info.getLine())
.append(" : ").append(cause);
if (e.getCause().getMessage() != null) {
message.append(" ").append(e.getCause().getMessage());
}
LOGGER.error(message.toString());
}
private JexlContext prepareJexlContext(CalculatorParameters parameters) {
JexlContext jc = new MapContext();
for (Dependency dependency : dependencies) {
if (dependency instanceof LocalDependency) {
LocalDependency<?> localDependency = (LocalDependency<?>) dependency;
String key = localDependency.getLocalMetadataCode();
jc.set(key, parameters.get(localDependency));
} else {
ReferenceDependency<?> referenceDependency = (ReferenceDependency<?>) dependency;
String key = referenceDependency.getLocalMetadataCode();
Map<String, Object> map = new HashMap<>();
map.put(referenceDependency.getDependentMetadataCode(), parameters.get(referenceDependency));
jc.set(key, map);
}
}
return jc;
}
@Override
public Object getDefaultValue() {
return null;
}
@Override
public MetadataValueType getReturnType() {
return type;
}
@Override
public boolean isMultiValue() {
return false;
}
@Override
public List<? extends Dependency> getDependencies() {
return dependencies;
}
@Override
public Object[] getInstanceParameters() {
return new Object[] { expression };
}
@Override
public void initialize(MetadataSchemaTypes types, MetadataSchema schema, Metadata calculatedMetadata) {
metadataCode = calculatedMetadata.getCode();
try {
JexlEngine jexl = new JexlBuilder().create();
jexlScript = jexl.createScript(expression);
for (List<String> variable : jexlScript.getVariables()) {
if (variable.size() == 2) {
dependencies.add(toReferenceDependency(types, schema, variable));
}
}
} catch (Exception e) {
e.printStackTrace();
dependencies.clear();
}
}
@Override
public void initialize(List<Metadata> schemaMetadatas, Metadata calculatedMetadata) {
metadataCode = calculatedMetadata.getCode();
type = calculatedMetadata.getType();
dependencies.clear();
try {
JexlEngine jexl = new JexlBuilder().create();
jexlScript = jexl.createScript(expression);
variables = jexlScript.getVariables();
} catch (Exception e) {
e.printStackTrace();
}
if (variables != null) {
for (List<String> variable : jexlScript.getVariables()) {
if (variable.size() == 1) {
dependencies.add(toLocalDependency(schemaMetadatas, variable));
}
}
}
}
private LocalDependency toLocalDependency(MetadataSchema schema, List<String> variable) {
boolean isRequired = false;
Metadata metadata = schema.getMetadata(variable.get(0));
return new LocalDependency<>(variable.get(0), isRequired, metadata.isMultivalue(),
metadata.getType(), false);
}
private LocalDependency toLocalDependency(List<Metadata> metadatas, List<String> variable) {
boolean isRequired = false;
Metadata metadata = null;
for (Metadata aMetadata : metadatas) {
if (aMetadata.getLocalCode().equals(variable.get(0))) {
metadata = aMetadata;
break;
}
}
if (metadata == null) {
throw new IllegalArgumentException("No such metadata with code " + variable.get(0));
}
return new LocalDependency<>(variable.get(0), isRequired, metadata.isMultivalue(),
metadata.getType(), false);
}
private ReferenceDependency toReferenceDependency(MetadataSchemaTypes types, MetadataSchema schema, List<String> variable) {
Metadata referenceMetadata = schema.getMetadata(variable.get(0));
String referencedSchemaTypeCode = referenceMetadata.getAllowedReferences().getTypeWithAllowedSchemas();
MetadataSchema referencedSchema = types.getDefaultSchema(referencedSchemaTypeCode);
Metadata copiedMetadata = referencedSchema.getMetadata(variable.get(1));
boolean isRequired = false;
boolean isMultivalue = copiedMetadata.isMultivalue() || referenceMetadata.isMultivalue();
boolean isGroupedByReferences = false;
return new ReferenceDependency<>(variable.get(0), variable.get(1), isRequired,
isMultivalue,
copiedMetadata.getType(), isGroupedByReferences, false);
}
}