All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.syndesis.integration.runtime.handlers.SplitStepHandler Maven / Gradle / Ivy

/*
 * Copyright (C) 2016 Red Hat, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     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 io.syndesis.integration.runtime.handlers;

import java.io.IOException;
import java.util.Optional;

import com.fasterxml.jackson.databind.JsonNode;
import io.syndesis.common.model.DataShape;
import io.syndesis.common.model.DataShapeKinds;
import io.syndesis.common.model.DataShapeMetaData;
import io.syndesis.common.model.action.StepAction;
import io.syndesis.common.model.integration.Step;
import io.syndesis.common.model.integration.StepKind;
import io.syndesis.common.util.Json;
import io.syndesis.common.util.SyndesisServerException;
import io.syndesis.common.util.json.JsonUtils;
import io.syndesis.integration.runtime.IntegrationRouteBuilder;
import io.syndesis.integration.runtime.IntegrationStepHandler;
import org.apache.camel.Exchange;
import org.apache.camel.Expression;
import org.apache.camel.builder.Builder;
import org.apache.camel.model.ProcessorDefinition;
import org.apache.camel.spi.Language;
import org.apache.camel.support.ExpressionAdapter;
import org.apache.camel.util.ObjectHelper;

public class SplitStepHandler implements IntegrationStepHandler {
    @Override
    public boolean canHandle(Step step) {
        return StepKind.split == step.getStepKind();
    }

    @SuppressWarnings({"PMD.AvoidReassigningParameters", "PMD.AvoidDeeplyNestedIfStmts"})
    @Override
    public Optional> handle(Step step, ProcessorDefinition route, IntegrationRouteBuilder builder, String flowIndex, String stepIndex) {
        ObjectHelper.notNull(route, "route");

        SplitExpression splitExpression;
        String languageName = step.getConfiguredProperties().get("language");
        String expressionDefinition = step.getConfiguredProperties().get("expression");

        if (hasUnifiedJsonSchemaOutputShape(step)) {
            splitExpression = new SplitExpression(new UnifiedJsonBodyExpression(Builder.body()));
        } else if (ObjectHelper.isNotEmpty(expressionDefinition)) {
            if (ObjectHelper.isEmpty(languageName)) {
                languageName = "simple";
            }

            // A small hack until https://issues.apache.org/jira/browse/CAMEL-12079
            // gets fixed so we can support the 'bean::method' annotation as done by
            // Function step definition
            if ("bean".equals(languageName) && expressionDefinition.contains("::")) {
                expressionDefinition = expressionDefinition.replace("::", "?method=");
            }

            final Language language = builder.getContext().resolveLanguage(languageName);
            splitExpression = new SplitExpression(language.createExpression(expressionDefinition));
        } else {
            splitExpression = new SplitExpression(Builder.body());
        }

        AggregateStepHandler.AggregationOption aggregation = Optional.ofNullable(step.getConfiguredProperties().get("aggregationStrategy"))
                .map(AggregateStepHandler.AggregationOption::valueOf)
                .orElse(AggregateStepHandler.AggregationOption.body);

        route = route.split(splitExpression).aggregationStrategy(aggregation.getStrategy(step.getConfiguredProperties()));

        return Optional.of(route);
    }

    /**
     * Evaluates if step output shape is a Json schema that is marked to be a unified specification. This means
     * that we have to split the nested unified body property by default. See 'UnifiedJsonDataShapeGenerator'
     * for details.
     *
     * @param step
     * @return
     */
    private boolean hasUnifiedJsonSchemaOutputShape(Step step) {
        Optional stepAction = step.getActionAs(StepAction.class);
        if (stepAction.isPresent()) {
            Optional outputShape = stepAction.get().getOutputDataShape();
            if (outputShape.isPresent() && outputShape.get().getKind() == DataShapeKinds.JSON_SCHEMA) {
                return outputShape.get().getMetadata()
                        .entrySet()
                        .stream()
                        .anyMatch(entry -> entry.getKey().equals(DataShapeMetaData.UNIFIED));
            }
        }

        return false;
    }

    /**
     * Split expression that is aware of special list of Json beans representation provided by some connectors such as
     * SQL. Also handles Json array String representation and splits its array elements accordingly.
     *
     * Expression receives a delegate expression that usually evaluates the part of the body or header that should be split. By
     * default this is a simple body expression.
     *
     * When delegate expression evaluates to something else that a Json array or list of Json beans nothing is performed on top of
     * the delegate expression.
     */
    private static class SplitExpression extends ExpressionAdapter {
        private final Expression delegate;

        SplitExpression(Expression delegate) {
            this.delegate = delegate;
        }

        @Override
        public Object evaluate(Exchange exchange) {
            Object value = delegate.evaluate(exchange, Object.class);

            if (value instanceof String && JsonUtils.isJson(value.toString())) {
                try {
                    JsonNode json = Json.reader().readTree(value.toString());
                    if (json.isArray()) {
                        return JsonUtils.arrayToJsonBeans(json);
                    }
                } catch (IOException e) {
                    throw SyndesisServerException.launderThrowable(e);
                }
            }

            return value;
        }
    }

    /**
     * Expression extracts body property from unified Json schema typed input. The unified Json holds the actual body in
     * a nested property. This property is extracted and set as expression result so follow up expressions can operate on the body.
     *
     * Expression receives a delegate expression that usually evaluates the part of the body or header that should be
     * treated as unified Json. By default this is a simple body expression.
     */
    private static class UnifiedJsonBodyExpression extends ExpressionAdapter {
        private final Expression delegate;

        UnifiedJsonBodyExpression(Expression delegate) {
            this.delegate = delegate;
        }

        @Override
        public Object evaluate(Exchange exchange) {
            Object value = delegate.evaluate(exchange, Object.class);

            if (value instanceof String && JsonUtils.isJson(value.toString())) {
                try {
                    JsonNode json = Json.reader().readTree(value.toString());
                    JsonNode body = json.get("body");
                    if (body != null) {
                        return Json.writer().writeValueAsString(body);
                    }
                } catch (IOException e) {
                    throw SyndesisServerException.launderThrowable(e);
                }
            }

            return value;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy