Search in sources :

Example 1 with NamedParameter

use of org.kie.dmn.feel.lang.impl.NamedParameter in project drools by kiegroup.

the class BaseFEELFunction method invokeReflectively.

@Override
public Object invokeReflectively(EvaluationContext ctx, Object[] params) {
    // use reflection to call the appropriate invoke method
    try {
        boolean isNamedParams = params.length > 0 && params[0] instanceof NamedParameter;
        if (!isCustomFunction()) {
            List<String> available = null;
            if (isNamedParams) {
                available = Stream.of(params).map(p -> ((NamedParameter) p).getName()).collect(Collectors.toList());
            }
            CandidateMethod cm = getCandidateMethod(ctx, params, isNamedParams, available);
            if (cm != null) {
                Object result = cm.apply.invoke(this, cm.actualParams);
                if (result instanceof Either) {
                    @SuppressWarnings("unchecked") Either<FEELEvent, Object> either = (Either<FEELEvent, Object>) result;
                    Object eitherResult = either.cata((left) -> {
                        ctx.notifyEvt(() -> {
                            if (left instanceof InvalidParametersEvent) {
                                InvalidParametersEvent invalidParametersEvent = (InvalidParametersEvent) left;
                                invalidParametersEvent.setNodeName(getName());
                                invalidParametersEvent.setActualParameters(Stream.of(cm.apply.getParameters()).map(p -> p.getAnnotation(ParameterName.class).value()).collect(Collectors.toList()), Arrays.asList(cm.actualParams));
                            }
                            return left;
                        });
                        return null;
                    }, Function.identity());
                    return eitherResult;
                }
                return result;
            } else {
                String ps = getClass().toString();
                logger.error("Unable to find function '" + getName() + "( " + ps.substring(1, ps.length() - 1) + " )'");
                ctx.notifyEvt(() -> {
                    return new FEELEventBase(Severity.ERROR, "Unable to find function '" + getName() + "( " + ps.substring(1, ps.length() - 1) + " )'", null);
                });
            }
        } else {
            if (isNamedParams) {
                params = rearrangeParameters(params, this.getParameterNames().get(0));
            }
            Object result = invoke(ctx, params);
            if (result instanceof Either) {
                @SuppressWarnings("unchecked") Either<FEELEvent, Object> either = (Either<FEELEvent, Object>) result;
                final Object[] usedParams = params;
                Object eitherResult = either.cata((left) -> {
                    ctx.notifyEvt(() -> {
                        if (left instanceof InvalidParametersEvent) {
                            InvalidParametersEvent invalidParametersEvent = (InvalidParametersEvent) left;
                            invalidParametersEvent.setNodeName(getName());
                            invalidParametersEvent.setActualParameters(IntStream.of(0, usedParams.length).mapToObj(i -> "arg" + i).collect(Collectors.toList()), Arrays.asList(usedParams));
                        }
                        return left;
                    });
                    return null;
                }, Function.identity());
                return normalizeResult(eitherResult);
            }
            return normalizeResult(result);
        }
    } catch (Exception e) {
        logger.error("Error trying to call function " + getName() + ".", e);
        ctx.notifyEvt(() -> {
            return new FEELEventBase(Severity.ERROR, "Error trying to call function " + getName() + ".", e);
        });
    }
    return null;
}
Also used : NamedParameter(org.kie.dmn.feel.lang.impl.NamedParameter) InvalidParametersEvent(org.kie.dmn.feel.runtime.events.InvalidParametersEvent) FEELEventBase(org.kie.dmn.feel.runtime.events.FEELEventBase) Either(org.kie.dmn.feel.util.Either) FEELEvent(org.kie.dmn.api.feel.runtime.events.FEELEvent)

Example 2 with NamedParameter

use of org.kie.dmn.feel.lang.impl.NamedParameter in project drools by kiegroup.

the class BaseFEELFunction method calculateActualParams.

private Object[] calculateActualParams(EvaluationContext ctx, Method m, Object[] params, List<String> available) {
    Annotation[][] pas = m.getParameterAnnotations();
    List<String> names = new ArrayList<>(m.getParameterCount());
    for (int i = 0; i < m.getParameterCount(); i++) {
        for (int p = 0; p < pas[i].length; i++) {
            if (pas[i][p] instanceof ParameterName) {
                names.add(((ParameterName) pas[i][p]).value());
                break;
            }
        }
        if (names.get(i) == null) {
            // no name found
            return null;
        }
    }
    Object[] actualParams = new Object[names.size()];
    boolean isVariableParameters = m.getParameterCount() > 0 && m.getParameterTypes()[m.getParameterCount() - 1].isArray();
    String variableParamPrefix = isVariableParameters ? names.get(names.size() - 1) : null;
    List<Object> variableParams = isVariableParameters ? new ArrayList<>() : null;
    for (Object o : params) {
        NamedParameter np = (NamedParameter) o;
        if (names.contains(np.getName())) {
            actualParams[names.indexOf(np.getName())] = np.getValue();
        } else if (isVariableParameters) {
            // check if it is a variable parameters method
            if (np.getName().matches(variableParamPrefix + "\\d+")) {
                int index = Integer.parseInt(np.getName().substring(variableParamPrefix.length())) - 1;
                if (variableParams.size() <= index) {
                    for (int i = variableParams.size(); i < index; i++) {
                        // need to add nulls in case the user skipped indexes
                        variableParams.add(null);
                    }
                    variableParams.add(np.getValue());
                } else {
                    variableParams.set(index, np.getValue());
                }
            } else {
                // invalid parameter, method is incompatible
                return null;
            }
        } else {
            // invalid parameter, method is incompatible
            return null;
        }
    }
    if (isVariableParameters) {
        actualParams[actualParams.length - 1] = variableParams.toArray();
    }
    return actualParams;
}
Also used : NamedParameter(org.kie.dmn.feel.lang.impl.NamedParameter) ArrayList(java.util.ArrayList)

Example 3 with NamedParameter

use of org.kie.dmn.feel.lang.impl.NamedParameter in project drools by kiegroup.

the class DMNInvocationEvaluator method evaluate.

@Override
public EvaluatorResult evaluate(DMNRuntimeEventManager eventManager, DMNResult dmnr) {
    final List<FEELEvent> events = new ArrayList<>();
    DMNResultImpl result = (DMNResultImpl) dmnr;
    DMNContext previousContext = result.getContext();
    DMNContextImpl dmnContext = (DMNContextImpl) previousContext.clone();
    result.setContext(dmnContext);
    Object invocationResult = null;
    try {
        FEELFunction function = this.functionLocator.apply(previousContext, functionName);
        if (function == null) {
            // check if it is a configured/built-in function
            Object r = null;
            if (feel != null) {
                r = ((FEELImpl) feel).newEvaluationContext(Collections.emptyList(), Collections.emptyMap()).getValue(functionName);
            } else {
                r = RootExecutionFrame.INSTANCE.getValue(functionName);
            }
            if (r != null && r instanceof FEELFunction) {
                function = (FEELFunction) r;
            }
        }
        if (function == null) {
            MsgUtil.reportMessage(logger, DMNMessage.Severity.ERROR, node, result, null, null, Msg.FUNCTION_NOT_FOUND, functionName, nodeName);
            return new EvaluatorResultImpl(null, ResultType.FAILURE);
        }
        Object[] namedParams = new Object[parameters.size()];
        int index = 0;
        for (ActualParameter param : parameters) {
            try {
                EvaluatorResult value = param.expression.evaluate(eventManager, result);
                if (value.getResultType() == ResultType.SUCCESS) {
                    namedParams[index++] = new NamedParameter(param.name, value.getResult());
                } else {
                    MsgUtil.reportMessage(logger, DMNMessage.Severity.ERROR, node, result, null, null, Msg.ERR_EVAL_PARAM_FOR_INVOCATION_ON_NODE, param.name, functionName, nodeName);
                    return new EvaluatorResultImpl(null, ResultType.FAILURE);
                }
            } catch (Exception e) {
                MsgUtil.reportMessage(logger, DMNMessage.Severity.ERROR, node, result, e, null, Msg.ERR_INVOKING_PARAM_EXPR_FOR_PARAM_ON_NODE, param.name, nodeName);
                return new EvaluatorResultImpl(null, ResultType.FAILURE);
            }
        }
        FEELEventListenersManager listenerMgr = new FEELEventListenersManager();
        listenerMgr.addListener(events::add);
        EvaluationContextImpl ctx = new EvaluationContextImpl(listenerMgr, eventManager.getRuntime());
        invocationResult = function.invokeReflectively(ctx, namedParams);
        boolean hasErrors = hasErrors(events, eventManager, result);
        return new EvaluatorResultImpl(invocationResult, hasErrors ? ResultType.FAILURE : ResultType.SUCCESS);
    } catch (Throwable t) {
        MsgUtil.reportMessage(logger, DMNMessage.Severity.ERROR, node, result, t, null, Msg.ERR_INVOKING_FUNCTION_ON_NODE, functionName, nodeName);
        return new EvaluatorResultImpl(null, ResultType.FAILURE);
    } finally {
        result.setContext(previousContext);
    }
}
Also used : DMNResultImpl(org.kie.dmn.core.impl.DMNResultImpl) EvaluatorResult(org.kie.dmn.core.api.EvaluatorResult) FEELFunction(org.kie.dmn.feel.runtime.FEELFunction) NamedParameter(org.kie.dmn.feel.lang.impl.NamedParameter) ArrayList(java.util.ArrayList) DMNContext(org.kie.dmn.api.core.DMNContext) FEELEventListenersManager(org.kie.dmn.feel.lang.impl.FEELEventListenersManager) EvaluationContextImpl(org.kie.dmn.feel.lang.impl.EvaluationContextImpl) FEELImpl(org.kie.dmn.feel.lang.impl.FEELImpl) DMNContextImpl(org.kie.dmn.core.impl.DMNContextImpl) FEELEvent(org.kie.dmn.api.feel.runtime.events.FEELEvent)

Example 4 with NamedParameter

use of org.kie.dmn.feel.lang.impl.NamedParameter in project drools by kiegroup.

the class BaseFEELFunction method getCandidateMethod.

private CandidateMethod getCandidateMethod(EvaluationContext ctx, Object[] params, boolean isNamedParams, List<String> available) {
    CandidateMethod candidate = null;
    // first, look for exact matches
    for (Method m : getClass().getDeclaredMethods()) {
        if (!Modifier.isPublic(m.getModifiers()) || !m.getName().equals("invoke")) {
            continue;
        }
        Object[] actualParams = null;
        boolean injectCtx = Arrays.stream(m.getParameterTypes()).anyMatch(p -> EvaluationContext.class.isAssignableFrom(p));
        if (injectCtx) {
            actualParams = new Object[params.length + 1];
            int j = 0;
            for (int i = 0; i < m.getParameterCount(); i++) {
                if (EvaluationContext.class.isAssignableFrom(m.getParameterTypes()[i])) {
                    if (isNamedParams) {
                        actualParams[i] = new NamedParameter("ctx", ctx);
                    } else {
                        actualParams[i] = ctx;
                    }
                } else if (j < params.length) {
                    actualParams[i] = params[j];
                    j++;
                }
            }
        } else {
            actualParams = params;
        }
        if (isNamedParams) {
            actualParams = calculateActualParams(ctx, m, actualParams, available);
            if (actualParams == null) {
                // incompatible method
                continue;
            }
        }
        CandidateMethod cm = new CandidateMethod(actualParams);
        Class<?>[] parameterTypes = m.getParameterTypes();
        if (!isNamedParams) {
            // if named parameters, then it has been adjusted already in the calculateActualParams method,
            // otherwise adjust here
            adjustForVariableParameters(cm, parameterTypes);
        }
        if (parameterTypes.length != cm.getActualParams().length) {
            continue;
        }
        boolean found = true;
        for (int i = 0; i < parameterTypes.length; i++) {
            Class<?> currentIdxActualParameterType = cm.getActualClasses()[i];
            if (currentIdxActualParameterType != null && !parameterTypes[i].isAssignableFrom(currentIdxActualParameterType)) {
                // and vice-versa
                if (Collection.class.isAssignableFrom(currentIdxActualParameterType)) {
                    Collection<?> valueCollection = (Collection<?>) actualParams[i];
                    if (valueCollection.size() == 1) {
                        Object singletonValue = valueCollection.iterator().next();
                        // re-perform the assignable-from check, this time using the element itself the singleton value from the original parameter list
                        if (parameterTypes[i].isAssignableFrom(singletonValue.getClass())) {
                            Object[] newParams = new Object[cm.getActualParams().length];
                            // can't rely on adjustForVariableParameters() have actually copied
                            System.arraycopy(cm.getActualParams(), 0, newParams, 0, cm.getActualParams().length);
                            newParams[i] = singletonValue;
                            cm.setActualParams(newParams);
                            continue;
                        }
                    }
                }
                found = false;
                break;
            }
        }
        if (found) {
            cm.setApply(m);
            if (candidate == null || cm.getScore() > candidate.getScore()) {
                candidate = cm;
            }
        }
    }
    return candidate;
}
Also used : NamedParameter(org.kie.dmn.feel.lang.impl.NamedParameter) Method(java.lang.reflect.Method) Collection(java.util.Collection) EvaluationContext(org.kie.dmn.feel.lang.EvaluationContext)

Aggregations

NamedParameter (org.kie.dmn.feel.lang.impl.NamedParameter)4 ArrayList (java.util.ArrayList)2 FEELEvent (org.kie.dmn.api.feel.runtime.events.FEELEvent)2 Method (java.lang.reflect.Method)1 Collection (java.util.Collection)1 DMNContext (org.kie.dmn.api.core.DMNContext)1 EvaluatorResult (org.kie.dmn.core.api.EvaluatorResult)1 DMNContextImpl (org.kie.dmn.core.impl.DMNContextImpl)1 DMNResultImpl (org.kie.dmn.core.impl.DMNResultImpl)1 EvaluationContext (org.kie.dmn.feel.lang.EvaluationContext)1 EvaluationContextImpl (org.kie.dmn.feel.lang.impl.EvaluationContextImpl)1 FEELEventListenersManager (org.kie.dmn.feel.lang.impl.FEELEventListenersManager)1 FEELImpl (org.kie.dmn.feel.lang.impl.FEELImpl)1 FEELFunction (org.kie.dmn.feel.runtime.FEELFunction)1 FEELEventBase (org.kie.dmn.feel.runtime.events.FEELEventBase)1 InvalidParametersEvent (org.kie.dmn.feel.runtime.events.InvalidParametersEvent)1 Either (org.kie.dmn.feel.util.Either)1