biz.c24.io.mule.C24Connector.java Source code

Java tutorial

Introduction

Here is the source code for biz.c24.io.mule.C24Connector.java

Source

/*
 * Copyright C24 Technologies Limited.
 *
 * 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 biz.c24.io.mule;

import biz.c24.api.LicenseException;
import biz.c24.io.api.C24;
import biz.c24.io.api.C24.C24Reader;
import biz.c24.io.api.C24.C24Writer;
import biz.c24.io.api.data.ComplexDataObject;
import biz.c24.io.api.data.SimpleDataObject;
import biz.c24.io.api.data.ValidationException;
import biz.c24.io.api.transform.Transform;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.mule.api.MuleEvent;
import org.mule.api.annotations.*;
import org.mule.api.annotations.display.Summary;
import org.mule.api.annotations.param.Default;
import org.mule.api.annotations.param.Optional;
import org.mule.common.DefaultResult;
import org.mule.common.Result;
import org.mule.common.metadata.*;
import org.mule.config.i18n.CoreMessages;
import org.reflections.Reflections;
import org.reflections.scanners.SubTypesScanner;
import org.reflections.util.ClasspathHelper;
import org.reflections.util.ConfigurationBuilder;
import org.reflections.util.FilterBuilder;

import javax.inject.Inject;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringWriter;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;

/**
 * C24 Mule Connector
 * 
 * @author C24 Technologies Limited
 * 
 * The C24 Connector allows you to use C24 iO message parsing, validation, transformation and generation
 * in your Mule flows.
 *
 */
@Connector(name = "c24", schemaVersion = "1.1.0", friendlyName = "C24-iO Connector", minMuleVersion = "3.4", description = "The C24 Connector allows you to use C24 iO message parsing, validation, transformation and generation in your Mule flows")
public class C24Connector {

    /**
     * The C24 iO licence to use (required for licensed data models only)
     */
    @Optional
    @Configurable
    @Summary("The path to the licence file to use. Not required for C24-iO Open Edition or if you are only using unlicensed models.")
    private String licenceFile;

    private static final transient Log LOG = LogFactory.getLog(C24Connector.class);

    /**
     * C24-iO Message Parsing
     * <p/>
     * {@sample.xml ../../../doc/C24-connector.xml.sample c24:parse}
     *
     * @param type The fully-qualified classname of the type that we expect to parse. The returned object will be an instance of this type.
     * @param source The String, Reader or InputStream to parse into a C24 Object
     * @param encoding The encoding of the input data
     * @param format The format to read (defaults to the model's default input format).
     * @param event The Mule event
     * @return A Java object representing the parsed form of the source
     * @throws C24Exception if the message cannot be parsed
     */
    @Processor
    @Inject
    public ComplexDataObject parse(String type, @Optional @Default("#[payload]") Object source,
            @Optional String encoding, @Optional C24.Format format, MuleEvent event) throws C24Exception {

        try {
            Class<? extends ComplexDataObject> typeClass = (Class<? extends ComplexDataObject>) Class.forName(type);

            return parse(typeClass, source, encoding, format, event);

        } catch (ClassNotFoundException e) {
            throw new C24Exception(CoreMessages.createStaticMessage(type + " cannot be found."), event, e);
        }

    }

    /**
     * C24-iO validation.
     * Validates the message, either returning the message or throwing an exception if it is invalid.
     * <p/>
     * {@sample.xml ../../../doc/C24-connector.xml.sample c24:validate}
     *
     * @param source The parsed message to validate
     * @param event The Mule event
     * @return The message
     * @throws C24Exception if the message is invalid.
     */
    @Processor
    @Inject
    public ComplexDataObject validate(@Optional @Default("#[payload]") Object source, MuleEvent event)
            throws C24Exception {
        try {
            if (source instanceof ComplexDataObject) {
                ComplexDataObject cdo = (ComplexDataObject) source;
                C24.validate(cdo);
                return cdo;
            } else {
                throw new C24Exception(
                        CoreMessages
                                .createStaticMessage("Message supplied to validate was not a C24-iO object (type "
                                        + source.getClass().getName() + " does not extend ComplexDataObject)."),
                        event);
            }
        } catch (ValidationException e) {
            throw new C24Exception(CoreMessages.createStaticMessage("Message is invalid."), event, e);
        }

    }

    /**
     * C24-iO Transform.
     * Transforms an object between models using a C24-iO 1:1 transform
     * <p/>
     * {@sample.xml ../../../doc/C24-connector.xml.sample c24:transform}
     *
     * @param transform The Transform to use
     * @param source The source data to be transformed
     * @param event The Mule event
     * @return The output from the transform
     * @throws C24Exception if the message cannot be transformed
     */
    @Processor
    @Inject
    public Object transform(String transform, @Optional @Default("#[payload]") Object source, MuleEvent event)
            throws C24Exception {

        Transform xform = getTransform(transform, event);

        return transform(xform, source, event);
    }

    /**
     * C24-iO Transform n:m
     * Transforms an object between models using a C24-iO transform
     * <p/>
     * {@sample.xml ../../../doc/C24-connector.xml.sample c24:transform}
     *
     * @param transform The Transform to use
     * @param inputs The inputs to the C24-iO transform.
     * @param event The Mule event
     * @return The outputs from the transform
     * @throws C24Exception if the message cannot be transformed
     */
    @Processor
    @Inject
    public List<List<Object>> transformAdvanced(String transform, List<Object> inputs, MuleEvent event)
            throws C24Exception {

        try {
            Transform xform = getTransform(transform, event);

            int numInputs = xform.getInputCount();
            if (numInputs != inputs.size()) {
                throw new C24Exception(CoreMessages.createStaticMessage("Transform " + transform + " requires "
                        + numInputs + " inputs, but " + inputs.size() + " were supplied"), event);
            }

            Object[][] typedInputs = new Object[numInputs][];
            for (int i = 0; i < numInputs; i++) {

                Object curr = inputs.get(i);
                typedInputs[i] = new Object[] { curr };
            }

            return toList(xform.transform(typedInputs));

        } catch (ValidationException e) {
            throw new C24Exception(CoreMessages.createStaticMessage("Message is invalid."), event, e);
        }

    }

    /**
     * C24-iO marshal
     * Converts a bound Java message to a wire format.
     * <p/>
     * {@sample.xml ../../../doc/C24-connector.xml.sample c24:marshal}
     *
     * @param source The Java message
     * @param format The format to generate (defaults to the model's default output format).
     * @param encoding The character encoding to use (defaults to the model's default encoding)
     * @param event The Mule Event
     * @return A String containing the marshalled form of the source
     * @throws C24Exception if the message cannot be marshalled
     */
    @Processor
    @Inject
    public String marshal(@Optional @Default("#[payload]") Object source, @Optional C24.Format format,
            @Optional String encoding, MuleEvent event) throws C24Exception {
        C24Writer<? extends ComplexDataObject> writer;

        try {
            if (source instanceof ComplexDataObject) {
                writer = C24.write((ComplexDataObject) source);
            } else if (source instanceof SimpleDataObject) {
                writer = C24.write((SimpleDataObject) source);
            } else {
                throw new C24Exception(CoreMessages.createStaticMessage(
                        "Message supplied to marshal was not a C24-iO object (type " + source.getClass().getName()
                                + " does not extend ComplexDataObject or SimpleDataObject)."),
                        event);
            }

            if (format != null) {
                writer.as(format);
            }
            if (encoding != null) {
                writer.using(encoding);
            }

            StringWriter str = new StringWriter();
            writer.to(str);
            return str.toString();

        } catch (IOException e) {
            throw new C24Exception(CoreMessages.createStaticMessage("Failed to marshal message."), event, e);
        }

    }

    /**
     * c24 Converter.
     * A convenience method to parse, validate, transform and marshal a message in a single operation.
     * <p/>
     * {@sample.xml ../../../doc/C24-connector.xml.sample c24:convert}
     *
     * @param transform The Transform to use
     * @param source The String, Reader or InputStream to parse into a C24 Object
     * @param encoding The encoding of the input data
     * @param validateInput Whether the source message should be validated post-parsing
     * @param validateOutput Whether the result of the transformation should be checked for validity
     * @param event The Mule event
     * @return The marshaled result of the transformation
     * @throws C24Exception An exception when there's an error
     */
    @Processor
    @Inject
    public String convert(/* @MetaDataKeyParam */ String transform, @Optional @Default("#[payload]") Object source,
            @Optional @Default("") String encoding, @Optional @Default("true") boolean validateInput,
            @Optional @Default("true") boolean validateOutput, MuleEvent event) throws C24Exception {

        try {

            Transform t = getTransform(transform, event);

            if (t.getInputCount() != 1 || t.getOutputCount() != 1) {
                throw new C24Exception(CoreMessages.createStaticMessage(
                        "The Convert operation only supports 1:1 transformations. Please use the Transform Advanced operation for n:m transforms."),
                        event);
            }

            ComplexDataObject src = null;
            if (source instanceof ComplexDataObject) {
                src = (ComplexDataObject) source;
            } else {
                src = parse(t.getInput(0).getType().getValidObjectClass(), source, encoding, null, event);
            }

            if (validateInput) {
                C24.validate(src);
            }

            ComplexDataObject result = (ComplexDataObject) transform(t, src, event);

            if (validateOutput) {
                C24.validate(result);
            }

            return marshal(result, null, null, event);

        } catch (ValidationException vEx) {
            throw new C24Exception(CoreMessages.createStaticMessage("Message is invalid"), event, vEx);
        }
    }

    protected void debug(String str) {
        LOG.debug(str);
    }

    private String basePackage = null;

    /**
     * Restricts the options provided to DataSense to matches beneath the specified package
     * @param basePackage
     */
    public void setBasePackage(String basePackage) {
        this.basePackage = basePackage;
        // Force a reload of metadata
        this.reflections = null;
    }

    /**
     * Returns the root package to which all DataSense matches must belong to
     * @return
     */
    public String getBasePackage() {
        return basePackage;
    }

    /**
     * Returns the path to the C24 iO licence file that will be used
     * See the C24 iO C24.init().withLicence(...) documentation to understand
     * how the file system & classpath will be searched to find an appropriate licence 
     * @return
     */
    public String getLicenceFile() {
        return licenceFile;
    }

    /**
     * Set the path to the C24 iO licence file that will be used
     * See the C24 iO C24.init().withLicence(...) documentation to understand
     * how the file system & classpath will be searched to find an appropriate licence 
     * @param licenceFile
     * @throws IOException 
     * @throws LicenseException 
     */
    public void setLicenceFile(String licenceFile) throws LicenseException, IOException {
        this.licenceFile = licenceFile;
        C24.init().withLicence(licenceFile);
    }

    /**
     * Returns an instance of the specified transform
     * @param transformName
     * @param event
     * @return
     * @throws C24Exception
     */
    private Transform getTransform(String transformName, MuleEvent event) throws C24Exception {

        try {
            Class<?> transformClass = Class.forName(transformName);

            // Check we have a valid transformation
            if (!Transform.class.isAssignableFrom(transformClass)) {
                throw new C24Exception(CoreMessages
                        .createStaticMessage(transformClass.getName() + " is not a valid C24 Transform"), event);
            }

            return (Transform) transformClass.newInstance();
        } catch (ClassNotFoundException e) {
            throw new C24Exception(CoreMessages.createStaticMessage("Failed to instantiate transform"), event, e);
        } catch (InstantiationException e) {
            throw new C24Exception(CoreMessages.createStaticMessage("Failed to instantiate transform"), event, e);
        } catch (IllegalAccessException e) {
            throw new C24Exception(CoreMessages.createStaticMessage("Failed to instantiate transform"), event, e);
        }
    }

    /**
     * Create a C24Reader capable of parsing the specified type using the (optional) encoding and format
     * @param type
     * @param encoding
     * @param format
     * @return
     */
    private C24Reader<ComplexDataObject> getReader(Class<? extends ComplexDataObject> type, String encoding,
            C24.Format format) {

        C24Reader<ComplexDataObject> reader = C24.parse(type);

        if (encoding != null && encoding.length() > 0) {
            reader.using(encoding);
        }
        if (format != null) {
            reader.as(format);
        }

        return reader;

    }

    /**
     * Parse an instance of the specified type from source
     * @param type
     * @param source
     * @param encoding
     * @param format
     * @param event
     * @return
     * @throws C24Exception
     */
    private ComplexDataObject parse(Class<? extends ComplexDataObject> type, Object source, String encoding,
            C24.Format format, MuleEvent event) throws C24Exception {

        if (!ComplexDataObject.class.isAssignableFrom(type)) {
            throw new C24Exception(CoreMessages.createStaticMessage(
                    type + " is not a valid C24-iO type. It must extend ComplexDataObject."), event);
        }

        C24Reader<? extends ComplexDataObject> reader = getReader(type, encoding, format);

        try {

            ComplexDataObject result;

            if (source instanceof Reader) {
                result = reader.from((Reader) source);
                ((Reader) source).close();
            } else if (source instanceof InputStream) {
                result = reader.from((InputStream) source);
                ((InputStream) source).close();
            } else if (source instanceof String) {
                result = reader.from((String) source);
            } else {
                throw new C24Exception(CoreMessages.createStaticMessage(
                        "Can't instantiate reader for unknown payload type: " + source.getClass()), event);
            }

            return result;

        } catch (IOException e) {
            throw new C24Exception(CoreMessages.createStaticMessage("Failed to parse message."), event, e);
        } finally {
            closeQuietly(source);
        }
    }

    private void closeQuietly(Object source) {
        try {
            if (source instanceof Reader) {
                ((Reader) source).close();
            } else if (source instanceof InputStream) {
                ((InputStream) source).close();
            }
        } catch (IOException e) {
            //ignore as an earlier exception takes precedence
        }
    }

    /**
     * Invoke a 1:1 C24-iO transform and return the result
     * @param xform
     * @param source
     * @param event
     * @return
     * @throws C24Exception
     */
    private Object transform(Transform xform, Object source, MuleEvent event) throws C24Exception {

        try {
            if (xform.getInputCount() != 1 || xform.getOutputCount() != 1) {
                throw new C24Exception(CoreMessages.createStaticMessage(
                        "The Transform operation only supports 1:1 transformations. Please use the Transform Advanced operation for n:m transforms."),
                        event);
            }

            return C24.transform(source, xform);
        } catch (ValidationException e) {
            throw new C24Exception(CoreMessages.createStaticMessage("Message is invalid."), event, e);
        }
    }

    /**
     * Convert the output of a C24-iO transform to List form
     * @param array
     * @return
     */
    private static List<List<Object>> toList(Object[][] array) {

        List<List<Object>> ret = null;

        if (array != null) {
            ret = new ArrayList<List<Object>>(array.length);

            for (int i = 0; i < array.length; i++) {
                ret.add(Arrays.asList(array[i]));
            }
        }

        return ret;
    }

    private volatile Reflections reflections = null;

    private Reflections getReflections() {
        debug("getting reflections");
        if (reflections == null) {
            synchronized (this) {
                if (reflections == null) {
                    debug("reflections is null");
                    // See if we can use pre-built metadata
                    Reflections r = Reflections.collect();
                    debug("tried to pick up collected metadata");
                    if (r == null) {
                        debug("got none");
                        // No. Scan the classloader instead.
                        ConfigurationBuilder builder = new ConfigurationBuilder();

                        if (getBasePackage() != null) {
                            builder.filterInputsBy(new FilterBuilder().include(getBasePackage()));
                        }

                        debug("build config");

                        Collection<URL> urls = ClasspathHelper.forJavaClassPath();
                        urls.addAll(ClasspathHelper.forClassLoader());

                        builder.setUrls(urls).setScanners(new SubTypesScanner());

                        r = new Reflections(builder);
                        debug("created reflections");
                        reflections = r;
                    }
                }
            }
        }

        return reflections;

    }

    /**
     * DataSense implementation to get the metadata keys
     * @return The metadata keys
     */
    public Result<List<MetaDataKey>> getMetaDataKeys() {
        try {
            debug("Entering getMetaDataKeys");
            List<MetaDataKey> keys = new ArrayList<MetaDataKey>();
            Collection<Class<? extends Transform>> classes = getReflections().getSubTypesOf(Transform.class);
            debug("Creating metadata keys");
            for (Class<?> c : classes) {
                keys.add(new DefaultMetaDataKey(c.getName(), c.getName().replaceAll(".*\\.", "")));
            }
            debug("done");

            return new DefaultResult<List<MetaDataKey>>(keys);
        } catch (Exception ex) {
            LOG.debug(ex);
            return null;
        }

    }

    /**
     * DataSense implementation to get the metadata for the supplied key
     * @param key The metadata key
     * @return The metadata corresponding to key
     */
    public Result<MetaData> getMetaData(MetaDataKey key) {
        try {
            debug("Entering getMetaData");
            debug("Getting class for " + key.getId());
            Class<?> object = Class.forName(key.getId());
            debug("got class");
            MetaDataModel model = new DefaultPojoMetaDataModel(object);
            debug("Constructing and returning");

            return new DefaultResult<MetaData>(new DefaultMetaData(model));
        } catch (Exception ex) {
            LOG.debug(ex);
            return null;
        }
    }

}