/*
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
*
* 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.kie.server.client.impl;
import static org.kie.server.api.rest.RestURI.DMN_URI;
import static org.kie.server.api.rest.RestURI.CONTAINER_ID;
import static org.kie.server.api.rest.RestURI.build;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.MathContext;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;
import org.kie.dmn.api.core.DMNContext;
import org.kie.dmn.api.core.DMNDecisionResult;
import org.kie.dmn.api.core.DMNResult;
import org.kie.server.api.KieServerConstants;
import org.kie.server.api.commands.CommandScript;
import org.kie.server.api.commands.DescriptorCommand;
import org.kie.server.api.marshalling.MarshallingFormat;
import org.kie.server.api.model.KieServerCommand;
import org.kie.server.api.model.ServiceResponse;
import org.kie.server.api.model.Wrapped;
import org.kie.server.api.model.dmn.DMNContextKS;
import org.kie.server.api.model.dmn.DMNModelInfoList;
import org.kie.server.api.model.dmn.DMNResultKS;
import org.kie.server.client.DMNServicesClient;
import org.kie.server.client.KieServicesConfiguration;
public class DMNServicesClientImpl extends AbstractKieServicesClientImpl implements DMNServicesClient {
public DMNServicesClientImpl(KieServicesConfiguration config) {
super(config);
}
public DMNServicesClientImpl(KieServicesConfiguration config, ClassLoader classLoader) {
super(config, classLoader);
}
@Override
public ServiceResponse<DMNModelInfoList> getModels(String containerId) {
ServiceResponse<DMNModelInfoList> result = null;
if( config.isRest() ) {
Map<String, Object> valuesMap = new HashMap<String, Object>();
valuesMap.put(CONTAINER_ID, containerId);
result = (ServiceResponse<DMNModelInfoList>)(ServiceResponse<?>)
makeHttpGetRequestAndCreateServiceResponse(build(loadBalancer.getUrl(), DMN_URI, valuesMap), DMNModelInfoList.class);
} else {
CommandScript script = new CommandScript( Collections.singletonList(
(KieServerCommand) new DescriptorCommand("DMNService", "getModels", new Object[]{containerId})) );
result = (ServiceResponse<DMNModelInfoList>) executeJmsCommand( script, DescriptorCommand.class.getName(), KieServerConstants.CAPABILITY_DMN, containerId ).getResponses().get(0);
throwExceptionOnFailure( result );
if (shouldReturnWithNullResponse(result)) {
return null;
}
}
return result;
}
@Override
public ServiceResponse<DMNResult> evaluateAll(String containerId, DMNContext dmnContext) {
return evaluateAll(containerId, null, null, dmnContext);
}
@Override
public ServiceResponse<DMNResult> evaluateAll(String containerId, String namespace, String modelName, DMNContext dmnContext) {
DMNContextKS payload = new DMNContextKS(namespace, modelName, dmnContext.getAll());
return evaluateDecisions(containerId, payload);
}
@Override
public ServiceResponse<DMNResult> evaluateDecisionByName(String containerId, String namespace, String modelName, String decisionName, DMNContext dmnContext) {
Objects.requireNonNull(decisionName, "Parameter decisionName cannot be null; method evaluateAllDecisions() can be used to avoid the need of supplying decisionName");
DMNContextKS payload = new DMNContextKS(namespace, modelName, dmnContext.getAll());
payload.setDecisionName(decisionName);
return evaluateDecisions(containerId, payload);
}
@Override
public ServiceResponse<DMNResult> evaluateDecisionById(String containerId, String namespace, String modelName, String decisionId, DMNContext dmnContext) {
Objects.requireNonNull(decisionId, "Parameter decisionId cannot be null; method evaluateAllDecisions() can be used to avoid the need of supplying decisionId");
DMNContextKS payload = new DMNContextKS(namespace, modelName, dmnContext.getAll());
payload.setDecisionId(decisionId);
return evaluateDecisions(containerId, payload);
}
private ServiceResponse<DMNResult> evaluateDecisions(String containerId, DMNContextKS payload) {
ServiceResponse<DMNResult> result = null;
if( config.isRest() ) {
Map<String, Object> valuesMap = new HashMap<String, Object>();
valuesMap.put(CONTAINER_ID, containerId);
result = (ServiceResponse<DMNResult>)(ServiceResponse<?>) makeHttpPostRequestAndCreateServiceResponse(
build(loadBalancer.getUrl(), DMN_URI, valuesMap), payload, DMNResultKS.class);
} else {
CommandScript script = new CommandScript( Collections.singletonList(
(KieServerCommand) new DescriptorCommand("DMNService", "evaluateDecisions", serialize(payload), marshaller.getFormat().getType(), new Object[]{containerId})) );
result = (ServiceResponse<DMNResult>) executeJmsCommand( script, DescriptorCommand.class.getName(), KieServerConstants.CAPABILITY_DMN, containerId ).getResponses().get(0);
throwExceptionOnFailure( result );
if (shouldReturnWithNullResponse(result)) {
return null;
}
}
if (result instanceof Wrapped) {
return (ServiceResponse<DMNResult>) ((Wrapped) result).unwrap();
}
ServiceResponse<DMNResult> result2 = (ServiceResponse<DMNResult>) result;
// coerce numbers to BigDecimal as per DMN spec.
// alternative to the below will require instructing special config of kie-server JSONMarshaller
// to manage scalar values when deserializing from JSON always as a BigDecimal instead of default Jackson NumberDeserializers
if ( config.getMarshallingFormat() == MarshallingFormat.JSON ) {
recurseAndModifyByCoercingNumbers(result2.getResult().getContext());
for ( DMNDecisionResult dr : result2.getResult().getDecisionResults() ) {
recurseAndModifyByCoercingNumbers( dr.getResult() );
}
}
return result2;
}
private static Object recurseAndModifyByCoercingNumbers(Object result) {
if ( result instanceof DMNContext ) {
DMNContext ctx = (DMNContext) result;
ctx.getAll().replaceAll( (k, v) -> recurseAndModifyByCoercingNumbers(v) );
return ctx;
} else if ( result instanceof Map<?, ?> ) {
((Map) result).replaceAll( (k, v) -> recurseAndModifyByCoercingNumbers(v) );
} else if ( result instanceof List<?> ) {
((List<Object>) result).replaceAll( DMNServicesClientImpl::recurseAndModifyByCoercingNumbers );
return result;
} else if ( result instanceof Set<?> ) {
Set<?> originalSet = (Set<?>) result;
Collection mappedSet = originalSet.stream().map( DMNServicesClientImpl::recurseAndModifyByCoercingNumbers ).collect(Collectors.toSet());
originalSet.clear();
originalSet.addAll(mappedSet);
return result;
} else if ( result instanceof Number ) {
return coerceNumber(result);
}
return result;
}
@Override
public DMNContext newContext() {
// in order to leverage the already existing client inner private class
return new DMNResultKS().getContext();
}
// copied from DMN FEEL utils
private static BigDecimal getBigDecimalOrNull(Object value) {
if ( !(value instanceof Number || value instanceof String) ) {
return null;
}
if ( !BigDecimal.class.isAssignableFrom( value.getClass() ) ) {
if ( value instanceof Long || value instanceof Integer || value instanceof Short || value instanceof Byte ||
value instanceof AtomicLong || value instanceof AtomicInteger ) {
value = new BigDecimal( ((Number) value).longValue(), MathContext.DECIMAL128 );
} else if ( value instanceof BigInteger ) {
value = new BigDecimal( ((BigInteger) value).toString(), MathContext.DECIMAL128 );
} else if ( value instanceof String ) {
// we need to remove leading zeros to prevent octal conversion
value = new BigDecimal( ((String) value).replaceFirst("^0+(?!$)", ""), MathContext.DECIMAL128 );
} else {
value = new BigDecimal( ((Number) value).doubleValue(), MathContext.DECIMAL128 );
}
}
return (BigDecimal) value;
}
// copied from DMN FEEL utils
private static Object coerceNumber(Object value) {
if ( value instanceof Number && !(value instanceof BigDecimal) ) {
return getBigDecimalOrNull( value );
} else {
return value;
}
}
}