Skip to content

Commit

Permalink
Finishing JasperReport expression evaluator for midPoint
Browse files Browse the repository at this point in the history
  • Loading branch information
semancik committed Apr 8, 2019
1 parent 9154412 commit b784f40
Show file tree
Hide file tree
Showing 8 changed files with 167 additions and 50 deletions.
Expand Up @@ -99,8 +99,12 @@ public <T, V extends PrismValue> List<V> evaluate(ScriptExpressionEvaluationCont
}

if (context.getOutputDefinition() == null) {
// No outputDefinition means "void" return type, we can return right now
return null;
// No outputDefinition may mean "void" return type
// or it can mean that we do not have definition, because this is something non-prism (e.g. report template)
// Either way we can return immediately, without any value conversion. Just wrap the value in fake PrismPropertyValue
List<V> evalPrismValues = new ArrayList<>(1);
evalPrismValues.add((V) getPrismContext().itemFactory().createPropertyValue(evalRawResult));
return evalPrismValues;
}

QName xsdReturnType = context.getOutputDefinition().getTypeName();
Expand Down
Expand Up @@ -18,19 +18,12 @@
import java.io.Serializable;
import java.util.Map;

import com.evolveum.midpoint.model.api.ModelService;
import com.evolveum.midpoint.prism.PrismObject;
import com.evolveum.midpoint.report.api.ReportService;
import com.evolveum.midpoint.schema.expression.VariablesMap;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.schema.util.ReportTypeUtil;
import com.evolveum.midpoint.task.api.Task;
import com.evolveum.midpoint.util.exception.CommunicationException;
import com.evolveum.midpoint.util.exception.ConfigurationException;
import com.evolveum.midpoint.util.exception.ExpressionEvaluationException;
import com.evolveum.midpoint.util.exception.ObjectNotFoundException;
import com.evolveum.midpoint.util.exception.SchemaException;
import com.evolveum.midpoint.util.exception.SecurityViolationException;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ReportType;
Expand Down Expand Up @@ -71,36 +64,33 @@ public class JRMidpointEvaluator extends JREvaluator {


public JRMidpointEvaluator(Serializable compileData, String unitName) {
LOGGER.info("NEW1: {}, {}", compileData, unitName);
this.compileData = compileData;
this.unitName = unitName;
}

public JRMidpointEvaluator(JasperReport jasperReprot, JRDataset dataset) {
LOGGER.info("NEW2: {}, {}", jasperReprot, dataset);
this.jasperReport = jasperReprot;
this.dataset = dataset;
}

public JRMidpointEvaluator(JasperReport jasperReprot) {
LOGGER.info("NEW3: {}", jasperReprot);
this.jasperReport = jasperReprot;
}

@Override
protected void customizedInit(Map<String, JRFillParameter> parametersMap, Map<String, JRFillField> fieldsMap,
public void customizedInit(Map<String, JRFillParameter> parametersMap, Map<String, JRFillField> fieldsMap,
Map<String, JRFillVariable> variablesMap) throws JRException {
LOGGER.info("cutomized init: ");
LOGGER.info("parametersMap : {}", parametersMap);
LOGGER.info("fieldsMap : {}", fieldsMap);
LOGGER.info("variablesMap : {}", variablesMap);
LOGGER.trace("customizedInit: ");
LOGGER.trace(" parametersMap : {}", parametersMap);
LOGGER.trace(" fieldsMap : {}", fieldsMap);
LOGGER.trace(" variablesMap : {}", variablesMap);

this.parametersMap = parametersMap;
this.fieldsMap = fieldsMap;
this.variablesMap = variablesMap;

PrismObject<ReportType> midPointReportObject = (PrismObject<ReportType>) parametersMap.get(ReportTypeUtil.PARAMETER_REPORT_OBJECT).getValue();
LOGGER.info("midPointReportObject : {}", midPointReportObject);
LOGGER.trace("midPointReportObject : {}", midPointReportObject);

reportService = SpringApplicationContext.getBean(ReportService.class);

Expand Down Expand Up @@ -136,83 +126,173 @@ private PrismObject<ReportType> getReport() {

@Override
public Object evaluate(JRExpression expression) throws JRExpressionEvalException {
LOGGER.info("evaluate expression: {}", expression);
return evaluateExpression(expression, Mode.DEFAULT);
}

@Override
public Object evaluateOld(JRExpression expression) throws JRExpressionEvalException {
return evaluateExpression(expression, Mode.OLD);
}

@Override
public Object evaluateEstimated(JRExpression expression) throws JRExpressionEvalException {
return evaluateExpression(expression, Mode.ESTIMATED);
}

private void logEvaluate(Mode mode, JRExpression expression) {
LOGGER.trace("JasperReport expression: evaluate({}): {} (type:{})", mode, expression, expression==null?null:expression.getType());
}

private Object evaluateExpression(JRExpression expression, Mode mode) {
logEvaluate(mode, expression);
if (expression == null) {
return null;
}
JRExpressionChunk[] ch = expression.getChunks();

VariablesMap parameters = new VariablesMap();
VariablesMap variables = new VariablesMap();

String groovyCode = "";

for (JRExpressionChunk chunk : expression.getChunks()) {
if (chunk == null) {
break;
}
// LOGGER.trace("JR chunk: {}: {}", chunk.getType(), chunk.getText());

groovyCode += chunk.getText();
switch (chunk.getType()){
switch (chunk.getType()) {
case JRExpressionChunk.TYPE_FIELD:
JRFillField field = fieldsMap.get(chunk.getText());
parameters.put(field.getName(), field.getValue(), field.getValueClass());
variables.put(field.getName(), getFieldValue(chunk.getText(), mode), field.getValueClass());
break;
case JRExpressionChunk.TYPE_PARAMETER:
JRFillParameter param = parametersMap.get(chunk.getText());
parameters.put(param.getName(), param.getValue(), param.getValueClass());
// Mode does not influence this one
variables.put(param.getName(), param.getValue(), param.getValueClass());
break;
case JRExpressionChunk.TYPE_VARIABLE:
JRFillVariable var = variablesMap.get(chunk.getText());
parameters.put(var.getName(), var.getValue(), var.getValueClass());
variables.put(var.getName(), var.getValue(), var.getValueClass());
break;
case JRExpressionChunk.TYPE_TEXT:
break;
default :
LOGGER.trace("nothing to do.");
default:
LOGGER.trace("nothing to do for chunk type {}", chunk.getType());

}

}

LOGGER.info("### EVALUATE ###\nParameters:\n{}\nCode:\n {}\n################", parameters.debugDump(1), groovyCode);

if (reportService == null) {
throw new JRRuntimeException("No report service");
}

try {
// TODO:

Object evaluationResult = reportService.evaluate(getReport(), groovyCode, parameters, getTask(), getOperationResult());

LOGGER.info("### evaluation result: {}", evaluationResult);
Object evaluationResult = reportService.evaluate(getReport(), groovyCode, variables, getTask(), getOperationResult());

traceEvaluationSuccess(mode, variables, groovyCode, evaluationResult);
return evaluationResult;

} catch (SchemaException | ExpressionEvaluationException | ObjectNotFoundException | CommunicationException
| ConfigurationException | SecurityViolationException e) {
} catch (Throwable e) {
traceEvaluationFailure(mode, variables, groovyCode, e);
throw new JRRuntimeException(e.getMessage(), e);
}

}

private void traceEvaluationSuccess(Mode mode, VariablesMap variables, String code, Object result) {
if (!LOGGER.isTraceEnabled()) {
return;
}
StringBuilder sb = traceEvaluationHead(mode, variables, code);
sb.append("Result: ").append(result).append("\n");
traceEvaluationTail(sb);
}

private void traceEvaluationFailure(Mode mode, VariablesMap variables, String code, Throwable e) {
if (!LOGGER.isTraceEnabled()) {
return;
}
StringBuilder sb = traceEvaluationHead(mode, variables, code);
sb.append("Error: ").append(e).append("\n");
traceEvaluationTail(sb);
}


private StringBuilder traceEvaluationHead(Mode mode, VariablesMap variables, String code) {
StringBuilder sb = new StringBuilder();
sb.append("---[ JasperReport expression evaluation ]---\n");
sb.append("Report: ").append(getReport()).append("\n");
sb.append("Mode: ").append(mode).append("\n");
sb.append("Variables:\n");
sb.append(variables.debugDump(1)).append("\n");
sb.append("Code:\n");
sb.append(code).append("\n");
return sb;
}


private void traceEvaluationTail(StringBuilder sb) {
sb.append("---------------------------------------------");
LOGGER.trace("\n{}", sb.toString());
}

private Object getFieldValue(String name, Mode mode) {
JRFillField field = fieldsMap.get(name);
if (field == null) {
return null;
}
switch (mode) {
case DEFAULT:
case ESTIMATED:
return field.getValue();
case OLD:
return field.getOldValue();
default:
throw new IllegalArgumentException("Wrong mode "+mode);
}
}

private Object getVariableValue(String name, Mode mode) {
JRFillVariable var = variablesMap.get(name);
if (var == null) {
return null;
}
switch (mode) {
case DEFAULT:
return var.getValue();
case ESTIMATED:
return var.getEstimatedValue();
case OLD:
return var.getOldValue();
default:
throw new IllegalArgumentException("Wrong mode "+mode);
}
}

enum Mode { DEFAULT, OLD, ESTIMATED };

// NOT USED

@Override
protected Object evaluate(int id) throws Throwable {
LOGGER.info("evaluate: {}", id);
return null;

// Not used. Not even invoked.
throw new UnsupportedOperationException("Boom! This code should not be reached");
}

@Override
protected Object evaluateOld(int id) throws Throwable {
LOGGER.info("evaluateOld: {}", id);
return null;
// Not used. Not even invoked.
throw new UnsupportedOperationException("Boom! This code should not be reached");
}

@Override
protected Object evaluateEstimated(int id) throws Throwable {
LOGGER.info("evaluateEstimated: {}", id);
return null;
// Not used. Not even invoked.
throw new UnsupportedOperationException("Boom! This code should not be reached");
}


}
Expand Up @@ -53,6 +53,7 @@
import com.evolveum.midpoint.model.impl.expr.ExpressionEnvironment;
import com.evolveum.midpoint.model.impl.expr.ModelExpressionThreadLocalHolder;
import com.evolveum.midpoint.prism.Objectable;
import com.evolveum.midpoint.prism.PrimitiveType;
import com.evolveum.midpoint.prism.PrismContext;
import com.evolveum.midpoint.prism.PrismObject;
import com.evolveum.midpoint.prism.PrismPropertyValue;
Expand All @@ -63,6 +64,7 @@
import com.evolveum.midpoint.report.api.ReportService;
import com.evolveum.midpoint.schema.GetOperationOptions;
import com.evolveum.midpoint.schema.SelectorOptions;
import com.evolveum.midpoint.schema.constants.ExpressionConstants;
import com.evolveum.midpoint.schema.expression.ExpressionEvaluatorProfile;
import com.evolveum.midpoint.schema.expression.ExpressionProfile;
import com.evolveum.midpoint.schema.expression.ScriptExpressionProfile;
Expand Down Expand Up @@ -361,9 +363,14 @@ public PrismContext getPrismContext() {

public <T> Object evaluateReportScript(String codeString, ScriptExpressionEvaluationContext context) throws ExpressionEvaluationException,
ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException, SchemaException {

ScriptExpressionEvaluatorType expressionType = new ScriptExpressionEvaluatorType();
expressionType.setCode(codeString);
context.setExpressionType(expressionType);
// Be careful about output definition here. We really do NOT want to set it.
// Not setting the definition means that we want raw value without any conversion.
// This is what we really want, because there may be exotic things such as JRTemplate going through those expressions
// We do not have any reasonable prism definitions for those.

context.setFunctions(createFunctionLibraries());
context.setObjectResolver(objectResolver);
Expand Down
Expand Up @@ -112,22 +112,22 @@ public void test110ReportUserListExpressionsCsv() throws Exception {
/**
* Reports with poisonous operations in the query. This should work with null profile.
* But it should fail with safe profile.
* Field operations are safe.
* Field operations are safe in this report, just the query is poisonous.
*/
@Test
public void test112ReportUserListExpressionsPoisonousQueryCsv() throws Exception {
final String TEST_NAME = "test110ReportUserListExpressionsCsv";
final String TEST_NAME = "test112ReportUserListExpressionsPoisonousQueryCsv";
testReportListUsersCsv(TEST_NAME, REPORT_USER_LIST_EXPRESSIONS_POISONOUS_QUERY_CSV_OID);
}

/**
* Reports with poisonous operations in the field expression. This should work with null profile.
* But it should fail with safe profile.
* Query expressions are safe.
* Query expression is safe in this report, just fields are poisonous.
*/
@Test
public void test114ReportUserListExpressionsPoisonousFieldCsv() throws Exception {
final String TEST_NAME = "test110ReportUserListExpressionsCsv";
final String TEST_NAME = "test114ReportUserListExpressionsPoisonousFieldCsv";
testReportListUsersCsv(TEST_NAME, REPORT_USER_LIST_EXPRESSIONS_POISONOUS_FIELD_CSV_OID);
}

Expand Down
Expand Up @@ -45,7 +45,7 @@ protected File getSystemConfigurationFile() {
/**
* Reports with poisonous operations in the query. This should work with null profile.
* But it should fail with safe profile.
* Field operations are safe.
* Field operations are safe in this report, just the query is poisonous.
*/
@Test
@Override
Expand All @@ -54,6 +54,15 @@ public void test112ReportUserListExpressionsPoisonousQueryCsv() throws Exception
testReportListUsersCsvFailure(TEST_NAME, REPORT_USER_LIST_EXPRESSIONS_POISONOUS_QUERY_CSV_OID);
}

// TODO TODO TODO: override test114ReportUserListExpressionsPoisonousFieldCsv

/**
* Reports with poisonous operations in the field expression. This should work with null profile.
* But it should fail with safe profile.
* Query expression is safe in this report, just fields are poisonous.
*/
@Test
public void test114ReportUserListExpressionsPoisonousFieldCsv() throws Exception {
final String TEST_NAME = "test114ReportUserListExpressionsPoisonousFieldCsv";
testReportListUsersCsvFailure(TEST_NAME, REPORT_USER_LIST_EXPRESSIONS_POISONOUS_FIELD_CSV_OID);
}

}
Expand Up @@ -90,6 +90,10 @@
<description>Prism schema - bean classes</description>
<decision>allow</decision>
</package>
<class>
<name>java.lang.Integer</name>
<decision>allow</decision>
</class>
<class>
<name>java.lang.String</name>
<description>String operations are generally safe. But Groovy is adding execute() method which is very dangerous.</description>
Expand All @@ -112,6 +116,18 @@
<decision>deny</decision>
</method>
</class>
<class>
<name>java.util.Map</name>
<decision>allow</decision>
</class>
<class>
<name>java.util.HashMap</name>
<decision>allow</decision>
</class>
<class>
<name>java.util.Date</name>
<decision>allow</decision>
</class>
<class>
<name>java.lang.System</name>
<description>Just a few methods of System are safe enough.</description>
Expand Down
2 changes: 2 additions & 0 deletions model/report-impl/src/test/resources/logback-test.xml
Expand Up @@ -26,6 +26,8 @@
<logger name="org.hibernate.engine.jdbc.spi.SqlExceptionHelper" level="OFF"/>
<logger name="java.sql.DatabaseMetaData" level="WARN"/>
<logger name="com.evolveum.midpoint.report" level="TRACE"/>
<logger name="com.evolveum.midpoint.repo.common.expression" level="TRACE"/>
<logger name="com.evolveum.midpoint.model.common.expression" level="TRACE"/>
<logger name="PROFILING" level="OFF" />

<root level="INFO">
Expand Down

0 comments on commit b784f40

Please sign in to comment.