io.aino.agents.wso2.mediator.AinoMediator.java Source code

Java tutorial

Introduction

Here is the source code for io.aino.agents.wso2.mediator.AinoMediator.java

Source

/*
 *  Copyright 2016 Aino.io
 *
 *   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.aino.agents.wso2.mediator;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.apache.synapse.MessageContext;
import org.apache.synapse.core.axis2.Axis2MessageContext;
import org.apache.synapse.mediators.AbstractMediator;
import org.apache.synapse.mediators.MediatorProperty;
import org.apache.synapse.util.xpath.SynapseXPath;
import org.jaxen.JaxenException;

import io.aino.agents.core.Agent;
import io.aino.agents.core.Transaction;
import io.aino.agents.core.config.InvalidAgentConfigException;
import io.aino.agents.wso2.mediator.util.Enum;
import io.aino.agents.wso2.mediator.util.Id;
import io.aino.agents.wso2.mediator.util.IdPropertyBuilder;
import io.aino.agents.wso2.mediator.util.MediatorLocation;

import static io.aino.agents.wso2.mediator.config.AinoMediatorConfigConstants.*;

/**
 * Aino.io WSO2 ESB mediator.
 */
public class AinoMediator extends AbstractMediator {

    public Agent ainoAgent;

    private String separator;
    private String operation;
    private String message;
    private String esbServerName;
    private String fromApplication;
    private String toApplication;
    private String payloadType;
    private Enum.Status status;

    private final MediatorLocation mediatorLocation;

    private List<MediatorProperty> customProperties;
    private final List<Id> idList = new ArrayList<Id>();

    @SuppressWarnings("serial")
    private class DataFieldList extends ArrayList<String> {

        public DataFieldList(List<String> data) {
            this.addAll(data);
        }

        public boolean doesNotContain(String item) {
            return !this.contains(item);
        }
    }

    private DataFieldList dataFields = new DataFieldList(Arrays.asList("from", "to", "message", "status",
            "timestamp", "operation", "ids", "flowId", "payloadType"));

    /**
     * Constructor.
     *
     * @param ml mediator location
     * @param agent aino agent instance
     */
    public AinoMediator(MediatorLocation ml, Agent agent) {
        this.mediatorLocation = ml;
        this.ainoAgent = agent;

    }

    private static MediatorProperty getMediatorProperty(String name, String value, String expression)
            throws JaxenException {
        MediatorProperty mp = new MediatorProperty();
        mp.setName(name);
        mp.setValue(value);

        if (null == expression) {
            return mp;
        }

        invokePropertyExpression(expression, mp);
        return mp;
    }

    private static Object invokePropertyExpression(String expression, MediatorProperty mp) {
        try {
            // Workaround for a peculiar WSO2 update approach where an
            // intermediate (SynapsePath) class was added into the class
            // hierarchy
            Class<?> parameterClass = getParameterClass();
            Class<?> synapseXPathClass = getSynapseXpathClass();
            Method setExpression = MediatorProperty.class.getMethod("setExpression", parameterClass);
            return setExpression.invoke(mp, synapseXPathClass.getConstructor(String.class).newInstance(expression));
        } catch (Exception e) {
            throw new InvalidAgentConfigException(
                    "Unable to initialize a AinoMediator due to a reflection-related exception.", e);
        }
    }

    private static Class<?> getSynapseXpathClass() {
        return getClassForName("org.apache.synapse.util.xpath.SynapseXPath");
    }

    private static Class<?> getParameterClass() {
        return getClassForName(getParameterClassName());
    }

    private static Class<?> getClassForName(String name) {
        try {
            return Class.forName(name);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException("Could not instantiate class: " + name);
        }
    }

    private static String getParameterClassName() {
        String esb480OrLater = "org.apache.synapse.config.xml.SynapsePath";
        String beforeEsb480 = "org.apache.synapse.util.xpath.SynapseXPath";
        try {
            // If running in ESB v4.8.0 or newer
            Class.forName(esb480OrLater);
            return esb480OrLater;
        } catch (ClassNotFoundException e) {
            // If running in ESB older than v4.8.0
            return beforeEsb480;
        }
    }

    /**
     * Adds id xpath with id type key.
     *
     * @param typeKey type key
     * @param xPath xpath of ids
     */
    public void addId(String typeKey, SynapseXPath xPath) {
        idList.add(new Id(typeKey, xPath));
    }

    /**
     * Adds custom list of {@link MediatorProperty}.
     * They will be logged by ESB.
     *
     * @param properties properties to add
     */
    public void setProperties(List<MediatorProperty> properties) {
        customProperties = properties;
    }

    /**
     * Gets all custom {@link MediatorProperty}.
     *
     * @return custom properties
     */
    public List<MediatorProperty> getProperties() {
        return customProperties;
    }

    @Override
    public boolean mediate(MessageContext context) {
        try {
            initTransportHeadersMap(context);

            Transaction transaction = createTransaction(context);
            new IdPropertyBuilder(this.idList).buildToContext(context, transaction);

            processTransaction(context, transaction);
            logToEsb(context, transaction);
        } catch (Exception e) {
            log.error("Error occurred while trying to log to aino.io!", e);
        }

        return true;
    }

    private void logToEsb(MessageContext context, Transaction transaction) {
        StringBuilder sb = new StringBuilder();

        for (MediatorProperty prop : customProperties) {
            sb.append(prop.getName()).append(" = ").append(prop.getValue());
            sb.append(this.separator);
        }

        if (transaction != null) {
            appendNormalFieldsToLogMessage(transaction, sb);
            appendIdsToLogMessage(transaction, sb);
        }

        log.info(sb.toString());
    }

    private void appendIdsToLogMessage(Transaction transaction, StringBuilder sb) {
        sb.append("ids = [");
        for (String idName : transaction.getIds().keySet()) {
            sb.append(idName).append(": [");
            List<String> ids = transaction.getIds().get(idName);
            sb.append(StringUtils.join(ids, ",")).append("],");
        }
        sb.append("]");
    }

    private void appendNormalFieldsToLogMessage(Transaction transaction, StringBuilder sb) {
        appendNameAndValueToLogMessage("operation",
                ainoAgent.getAgentConfig().getOperations().getEntry(transaction.getOperationKey()), sb);
        appendNameAndValueToLogMessage("flowId", transaction.getFlowId(), sb);
        appendNameAndValueToLogMessage("message", transaction.getMessage(), sb);
        appendNameAndValueToLogMessage("status", transaction.getStatus(), sb);
        appendNameAndValueToLogMessage("payloadType",
                ainoAgent.getAgentConfig().getPayloadTypes().getEntry(transaction.getPayloadTypeKey()), sb);
        appendNameAndValueToLogMessage("from",
                ainoAgent.getAgentConfig().getApplications().getEntry(transaction.getFromKey()), sb);
        appendNameAndValueToLogMessage("to",
                ainoAgent.getAgentConfig().getApplications().getEntry(transaction.getToKey()), sb);
        appendNameAndValueToLogMessage("ainoTimestamp", String.valueOf(transaction.getTimestamp()), sb);
    }

    private void appendNameAndValueToLogMessage(String name, String value, StringBuilder sb) {
        sb.append(name).append(" = ").append(value).append(this.separator);
    }

    private void processTransaction(MessageContext context, Transaction transaction) {
        if (transaction == null) {
            return;
        }
        addFieldsToTransaction(transaction);

        for (MediatorProperty property : customProperties) {
            if (isMetadataProperty(property)) {
                String propertyValue = property.getValue() != null ? property.getValue()
                        : property.getEvaluatedExpression(context);
                transaction.addMetadata(property.getName(), propertyValue);
            }
        }

        ainoAgent.addTransaction(transaction);
    }

    private boolean isMetadataProperty(MediatorProperty prop) {
        if (prop == null)
            return false;

        return this.dataFields.doesNotContain(prop.getName());
    }

    private void addFieldsToTransaction(Transaction transaction) {
        transaction.setFromKey(this.fromApplication);
        transaction.setToKey(this.toApplication);
        transaction.setMessage(this.message);
        transaction.setStatus(this.status == null ? "" : this.status.toString());
        transaction.setPayloadTypeKey(this.payloadType);
    }

    private Transaction createTransaction(MessageContext context) {

        String flowId = validateOrSetAinoFlowId(context);
        String operationKey = validateOrSetAinoOperationName(context);

        Transaction transaction = null;
        if (ainoAgent.isEnabled()) {
            transaction = ainoAgent.newTransaction();

            transaction.setFlowId(flowId);
            transaction.setOperationKey(operationKey);

            transaction.addMetadata("artifactName", mediatorLocation.getArtifactName());
            transaction.addMetadata("esbServerName", esbServerName);
            transaction.addMetadata("artifactType", mediatorLocation.getArtifactType());
            transaction.addMetadata("artifactName", mediatorLocation.getArtifactName());
            transaction.addMetadata("lineNumber", Integer.toString(mediatorLocation.getLineNumber()));

            if (status == Enum.Status.FAILURE) {
                addErrorMetadata(context, transaction);
            }
        }

        return transaction;
    }

    private void addErrorMetadata(MessageContext context, Transaction transaction) {
        if (context.getProperty("ERROR_CODE") != null) {
            transaction.addMetadata("errorCode", context.getProperty("ERROR_CODE").toString());
        }

        if (context.getProperty("ERROR_MESSAGE") != null) {
            transaction.addMetadata("errorMessage", context.getProperty("ERROR_MESSAGE").toString());
        }

        if (context.getProperty("ERROR_DETAIL") != null) {
            transaction.addMetadata("errorDetails", context.getProperty("ERROR_DETAIL").toString());
        }

        if (context.getProperty("ERROR_EXCEPTION") != null) {
            transaction.addMetadata("errorException", context.getProperty("ERROR_EXCEPTION").toString());
        }
    }

    /**
     * Creates a transport headers map if one does not exist.
     *
     * @param context
     */
    private Map<String, String> initTransportHeadersMap(MessageContext context) {
        org.apache.axis2.context.MessageContext axis2MessageContext = ((Axis2MessageContext) context)
                .getAxis2MessageContext();
        Map<String, String> headersMap = (Map<String, String>) axis2MessageContext
                .getProperty(org.apache.axis2.context.MessageContext.TRANSPORT_HEADERS);

        if (headersMap == null) {
            headersMap = new HashMap<String, String>();
            context.setProperty(org.apache.axis2.context.MessageContext.TRANSPORT_HEADERS, headersMap);
        }
        return headersMap;
    }

    private String validateOrSetAinoOperationName(MessageContext context) {
        String operationKey;

        if (null != operation) {
            operationKey = operation;
        } else {
            operationKey = getOperationFromHeadersAndContext(context);
        }

        setPropertyToTransportHeadersMap(context, AINO_OPERATION_KEY_PROPERTY_NAME, operationKey);

        return operationKey;
    }

    private String getOperationFromHeadersAndContext(MessageContext context) {
        String ainoOperationName = getOperationNameFromTransportHeaders(context);

        if (null == ainoOperationName) {
            ainoOperationName = getOperationNameFromMessageContext(context);
        }

        return ainoOperationName;
    }

    private String getOperationNameFromTransportHeaders(MessageContext context) {
        // This logic is in place for situations where the message is coming back from a system which doesn't return custom transport headers.
        try {
            Map<String, String> headersMap = getTransportHeadersMap(context);
            return headersMap.get(AINO_OPERATION_KEY_PROPERTY_NAME);
        } catch (ClassCastException ignored) {
            return null;
        }
    }

    private String getOperationNameFromMessageContext(MessageContext context) {
        try {
            String name = (String) context.getProperty(AINO_OPERATION_KEY_PROPERTY_NAME);
            return name;
        } catch (ClassCastException ignored) {
            return null;
        }
    }

    private void setPropertyToTransportHeadersMap(MessageContext context, String key, String value) {
        Map<String, String> headersMap = getTransportHeadersMap(context);
        headersMap.put(key, value);
    }

    @SuppressWarnings("unchecked")
    private Map<String, String> getTransportHeadersMap(MessageContext context) {
        return initTransportHeadersMap(context);
    }

    private String validateOrSetAinoFlowId(MessageContext context) {
        Map<String, String> headersMap = getTransportHeadersMap(context);

        String flowId = headersMap.get(AINO_FLOW_ID_PROPERTY_NAME);

        if (null == flowId) {
            flowId = getFlowIdFromMessageContext(context);
        }

        if (null == flowId) {
            flowId = ((Axis2MessageContext) context).getAxis2MessageContext().getMessageID();
        }

        setPropertyToTransportHeadersMap(context, AINO_FLOW_ID_PROPERTY_NAME, flowId);

        return flowId;
    }

    private String getFlowIdFromMessageContext(MessageContext context) {
        try {
            String flowId = (String) context.getProperty(AINO_FLOW_ID_PROPERTY_NAME);

            return flowId;
        } catch (ClassCastException e) {
            return null;
        }
    }

    /**
     * Gets mediator location.
     *
     * @return mediator location
     */
    public MediatorLocation getMediatorLocation() {
        return mediatorLocation;
    }

    /**
     * Gets separator used in logging.
     * Separator is used in between properties.
     *
     * @return separator
     */
    public String getSeparator() {
        return separator;
    }

    /**
     * Sets separator used in logging.
     *
     * @param separator separator
     */
    public void setSeparator(String separator) {
        if (StringUtils.isEmpty(separator)) {
            separator = ",";
        }
        this.separator = separator;
    }

    /**
     * Gets operation key configured to this mediator.
     *
     * @return operation key
     */
    public String getOperation() {
        return operation;
    }

    /**
     * Sets operation key.
     *
     * @param operation operation key
     */
    public void setOperation(String operation) {
        this.operation = operation;
    }

    /**
     * Gets message configured to this mediator.
     *
     * @return message
     */
    public String getMessage() {
        return message;
    }

    /**
     * Sets message.
     *
     * @param message message to set
     */
    public void setMessage(String message) {
        this.message = message;
    }

    /**
     * Gets the server name of ESB.
     *
     * @return name of server
     */
    public String getEsbServerName() {
        return esbServerName;
    }

    /**
     * Sets the server name of ESB.
     *
     * @param esbServerName server name
     */
    public void setEsbServerName(String esbServerName) {
        this.esbServerName = esbServerName;
    }

    /**
     * Gets configured 'to application'.
     *
     * @return to applcation key
     */
    public String getFromApplication() {
        return fromApplication;
    }

    /**
     * Sets 'from' application.
     *
     * @param fromApplication from application key
     */
    public void setFromApplication(String fromApplication) {
        this.fromApplication = fromApplication;
    }

    /**
     * Gets configured 'to' application.
     *
     * @return to application key
     */
    public String getToApplication() {
        return toApplication;
    }

    /**
     * Sets 'to' application.
     *
     * @param toApplication to application key
     */
    public void setToApplication(String toApplication) {
        this.toApplication = toApplication;
    }

    /**
     * Sets payload type.
     *
     * @param payloadTypeKey payload type key
     */
    public void setPayloadType(String payloadTypeKey) {
        this.payloadType = payloadTypeKey;
    }

    /**
     * Gets payload type.
     *
     * @return payload type key
     */
    public String getPayloadType() {
        return this.payloadType;
    }

    /**
     * Gets status.
     *
     * @return status (success, failure, unknown)
     */
    public String getStatus() {
        if (status == null) {
            return null;
        }
        return status.toString();
    }

    /**
     * Sets status.
     * Valid values are: "success", "failure" and "unknown".
     *
     * @param statusString status
     * @throws IllegalArgumentException when status is invalid
     */
    public void setStatus(String statusString) {
        Enum.Status status = Enum.Status.getStatus(statusString);

        if (status == null) {
            StringBuilder sb = new StringBuilder("AinoMediator status must me one of: ");
            sb.append(Arrays.toString(Enum.Status.values()));
            throw new InvalidAgentConfigException(sb.toString());
        }

        this.status = status;
    }

    /**
     * Gets list of {@link Id}s.
     *
     * @return ids
     */
    public List<Id> getIdList() {
        return idList;
    }

    /**
     * Sets 'to' or 'from' application, based on direction.
     *
     * @param direction direction
     * @param applicationKey application key
     */
    public void setApplication(Enum.ApplicationDirection direction, String applicationKey) {
        switch (direction) {
        case TO:
            this.setToApplication(applicationKey);
            break;
        case FROM:
            this.setFromApplication(applicationKey);
            break;
        }
    }

}