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

com.crabshue.commons.aggregator.ConfigurationFactory Maven / Gradle / Ivy

The newest version!
package com.crabshue.commons.aggregator;

import com.crabshue.commons.aggregator.model.Class;
import com.crabshue.commons.aggregator.model.Collect;
import com.crabshue.commons.aggregator.model.Execute;
import com.crabshue.commons.aggregator.model.Variable;
import com.crabshue.commons.aggregator.model.*;
import lombok.extern.slf4j.Slf4j;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Slf4j
public class ConfigurationFactory {
    private static final String FIELD = "field";
    private static final String WHEN = "when";
    private static final String EXECUTE = "execute";
    private static final String JEXL = "jexl";
    private static final String COLLECT = "collect";
    private static final String PROCESSING = "processing";
    private static final String TO = "to";
    private static final String WHAT = "what";
    private static final String VARIABLE = "variable";
    private static final String NAME = "name";
    private static final String NAMESPACE = "namespace";
    private static final String REGISTER_CLASS = "registerClass";
    private static final String FUNCTION = "function";
    private static final String PACKAGE = "package";
    private static final String CLASS = "class";
    private static final String AGGREGATOR4J = "aggregator4j";
    private static final String CONTEXT = "context";

    private ConfigurationFactory() {
    }

    /**
     * Unmarshall an XML configuration into AggregatorConfiguration model
     *
     * @param config the XML input stream
     * @return parsed object
     */
    public static AggregatorConfiguration unMarshall(InputStream config) {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder;
        Document docConfig;
        try {
            builder = factory.newDocumentBuilder();

            docConfig = builder.parse(config);
        } catch (Exception e) {
            throw new Error("Could not parse aggeregator4j config", e);
        }
        AggregatorConfiguration aggregatorConfig = new AggregatorConfiguration();
        Element root = docConfig.getDocumentElement();
        if (!AGGREGATOR4J.equals(root.getTagName()))
            throw new Error("config root must be aggregator4j");
        NodeList level1 = root.getChildNodes();
        for (int i = 0; i < level1.getLength(); i++) {
            if (level1.item(i).getNodeType() == Node.ELEMENT_NODE) {
                Node item = level1.item(i);
                String nodeName = item.getNodeName();
                if (PROCESSING.equals(nodeName)) {
                    unMarshallProcessing(aggregatorConfig, item);
                } else if (FUNCTION.equals(nodeName)) {
                    unMarshallFunction(aggregatorConfig, item);
                } else if (PACKAGE.equals(nodeName)) {
                    unMarshallPackage(aggregatorConfig, level1.item(i));
                } else if (CLASS.equals(nodeName)) {
                    unMarshallClass(aggregatorConfig, level1.item(i));
                } else {
                    logger.warn("Unexpected tag :" + nodeName);
                }
            }
        }
        return aggregatorConfig;
    }

    private static void unMarshallProcessing(AggregatorConfiguration config, Node item) {
        String className = getAttribute(item, CLASS);
        if (!StringFunctions.isEmpty(className)) {
            if (config.getProcessings() == null) {
                config.setProcessing(className);
            } else {
                config.getProcessings().add(className);
            }
        }
    }

    private static boolean unMarshallClass(AggregatorConfiguration config, Node item) {
        String className = getAttribute(item, NAME);
        if (!StringFunctions.isEmpty(className)) {
            NodeList level1 = item.getChildNodes();
            String classContext = getAttribute(item, CONTEXT);
            Class aClass = new Class(className, classContext);
            for (int i = 0; i < level1.getLength(); i++) {
                unMarshallClassConfig(aClass, level1.item(i));
            }
            return config.addClass(aClass);
        } else
            logger.warn(NAME + " attribute missing for " + CLASS);
        return false;
    }

    private static boolean unMarshallClassConfig(Class aClass, Node item) {
        if (item.getNodeType() == Node.ELEMENT_NODE) {
            String field = getAttribute(item, FIELD);
            String when = getAttribute(item, WHEN);
            if (EXECUTE.equals(item.getNodeName())) {
                String jexl = getAttribute(item, JEXL);
                if (!StringFunctions.isEmpty(field, jexl))
                    aClass.addExecute(new Execute(field, jexl, when));
                    //analysed.addExecute(field, jexl, when);
                else
                    logger.warn(EXECUTE + " requires " + FIELD + " '" + field + "' and " + JEXL + " '" + jexl + "' to be provided");
            } else if (COLLECT.equals(item.getNodeName())) {
                String to = getAttribute(item, TO);
                String what = getAttribute(item, WHAT);
                if ((!StringFunctions.isEmpty(field) || !StringFunctions.isEmpty(what)) && !StringFunctions.isEmpty(to))
                    aClass.addCollect(new Collect(field, what, to, when));
                else
                    logger.warn(COLLECT + " requires " + TO + " '" + to + "' and either " + FIELD + " '" + field + "' or " + WHAT + " '" + what + "' to be provided");

            } else if (VARIABLE.equals(item.getNodeName())) {
                String name = getAttribute(item, NAME);
                if (!StringFunctions.isEmpty(field) && !StringFunctions.isEmpty(name))
                    aClass.addVariable(new Variable(field, name));
                    //analysed.addVariable(field, name);
                else
                    logger.warn(VARIABLE + " requires " + FIELD + " '" + field + "' and " + NAME + " '" + name + "' to be provided");
            }
        }
        return false;
    }

    private static void unMarshallPackage(AggregatorConfiguration config, Node item) {
        String name = getAttribute(item, NAME);
        if (!StringFunctions.isEmpty(name))
            if (config.getAnalysedPackages() == null) {
                config.setAnalysedPackage(name);
            } else {
                config.getAnalysedPackages().add(name);
            }
        else
            logger.warn(NAME + " attribute missing for " + PACKAGE);
    }

    private static boolean unMarshallFunction(AggregatorConfiguration config, Node item) {
        String namespace = getAttribute(item, NAMESPACE);
        String registerClass = getAttribute(item, REGISTER_CLASS);
        if (!StringFunctions.isEmpty(namespace) && !StringFunctions.isEmpty(registerClass)) {
            return config.addFunction(new Function(namespace, registerClass));
        } else {
            logger.warn("Either " + REGISTER_CLASS + " '" + registerClass + "' or " + NAMESPACE + " '" + namespace + "' is missing");
        }
        return false;
    }

    /**
     * This method will return a new configuration object which is the result of merge of two configurations.
     * In case of conflicts (i.e. same class defined twice) the main config will patched by the included resulting
     * possibly in data loss.  In those cases a warning will be issued in the log.
     *
     * @param main     the main config to merge
     * @param included the config to include (and overwrite) in merge
     * @return the merged configuration
     */
    public static AggregatorConfiguration merge(AggregatorConfiguration main, AggregatorConfiguration included) {
        AggregatorConfiguration merged = new AggregatorConfiguration();
        merged.setAnalysedPackages(mergeList(main.getAnalysedPackages(), included.getAnalysedPackages()));
        merged.setProcessings(mergeList(main.getProcessings(), included.getProcessings()));
        merged.setFunctionList(mergeFunctionList(main.getFunctionList(), included.getFunctionList()));
        merged.setClassList(mergeClassList(main.getClassList(), included.getClassList()));
        return merged;
    }

    private static List mergeClassList(List main, List included) {
        Map merged = main.stream().collect(
            Collectors.toMap(Class::getClassName, (c) -> c)
        );
        included.forEach(
            aClass -> {
                if (merged.containsKey(aClass.getClassName())) {
                    logger.warn("Overwriting class '" + aClass.getClassName() + "' configuration ");
                }
                merged.put(aClass.getClassName(), aClass);
            }
        );
        return new ArrayList<>(merged.values());
    }

    private static List mergeFunctionList(List main, List included) {
        Map merged = main.stream().collect(
            Collectors.toMap(Function::getNamespace, (f) -> f));
        included.forEach(function -> {
            if (merged.containsKey(function.getNamespace())) {
                logger.warn("Overwriting function namespace :'" + function.getNamespace() +
                    "' with class:'" + function.getRegisterClass() + "'");
            }
            merged.put(function.getNamespace(), function);
        });
        return new ArrayList<>(merged.values());
    }


    private static  List mergeList(List main, List included) {
        List merged = new ArrayList<>();
        if (main != null)
            merged.addAll(main);

        if (included != null) {
            merged.removeAll(included);
            merged.addAll(included);
        }
        return new ArrayList<>(merged);

    }

    public static AggregatorConfiguration extractConfig(AggregatorContext context) {
        AggregatorConfiguration config = new AggregatorConfiguration();
        config.setAnalysedPackages(context.getPackageStarts());
        if (context.getProcessings() != null)
            config.setProcessings(
                context.getProcessings().stream()
                    .map(c->c.getClass().getName())
                    .collect(Collectors.toList())
            );
        config.setFunctionList(extractFunctions(context));
        config.setClassList(context.getAnalysedCache().entrySet().stream()
            .filter(ConfigurationFactory::isProcessable)
            .map(ConfigurationFactory::toClass)
            .collect(Collectors.toList()));
        return config;
    }


    private static Class toClass(Map.Entry classAnalysedEntry) {
        Class ret = new Class(classAnalysedEntry.getKey().getName(), classAnalysedEntry.getValue().getClassContext());
        ret.setCollectList(
                Stream.concat(
                        Optional.ofNullable(
                                classAnalysedEntry.getValue().getClassCollects()).orElse(Collections.emptyList())
                                .stream(),
                        Optional.of(
                                classAnalysedEntry.getValue().getCollects().values()).orElse(Collections.emptyList())
                                .stream()
                                .flatMap(Collection::stream))
                        .collect(Collectors.toList()));

        ret.setExecuteList(
                Optional.ofNullable(classAnalysedEntry.getValue().getExecutes())
                        .orElse(Collections.emptyMap())
                        .values().stream()
                        .flatMap(Collection::stream)
                        .collect(Collectors.toList())
        );
        ret.setVariableList(
                classAnalysedEntry.getValue().getVariables().entrySet().stream()
                        .map(variable -> new Variable(variable.getKey(), variable.getValue()))
                        .collect(Collectors.toList())
        );
        return ret;
    }

    private static boolean isProcessable(Map.Entry classAnalysedEntry) {
        return classAnalysedEntry.getValue().getClassType() == Analysed.CLASS_TYPE.PROCESSABLE;
    }

    private static List extractFunctions(AggregatorContext context) {
        List ret = new ArrayList<>();
        if (context.getRegisteredNamespaces() != null)
            context.getRegisteredNamespaces().forEach((name, clazz) ->
                ret.add(new Function(name, clazz.getName()))
            );
        return ret;
    }

    public static void marshall(AggregatorContext context, OutputStream out) {
        marshall(extractConfig(context), out);
    }

    private static Element withAttribute(Element element, String attributeName, String attributeValue) {
        if (!StringFunctions.isEmpty(attributeValue))
            element.setAttribute(attributeName, attributeValue);
        return element;
    }

    /**
     * Transform a configuratin in XML output stream;
     *
     * @param config the config to marshall
     * @param out    the outputStream to marshall to.
     */
    public static void marshall(AggregatorConfiguration config, OutputStream out) {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder;
        Document docConfig;
        try {
            builder = factory.newDocumentBuilder();
        } catch (Exception e) {
            throw new Error("Could not build new Document", e);
        }
        docConfig = builder.newDocument();
        Element root = docConfig.createElement(AGGREGATOR4J);
        docConfig.appendChild(root);
        for (String analysedPackage : config.getAnalysedPackages()) {
            root.appendChild(withAttribute(
                docConfig.createElement(PACKAGE)
                , NAME,
                analysedPackage)
            );
        }
        if (config.getProcessings() != null)
            config.getProcessings().forEach((processing) -> root.appendChild(withAttribute(
                docConfig.createElement(PROCESSING)
                , CLASS, processing)));
        config.getFunctionList().forEach((function) ->
            root.appendChild(withAttribute(withAttribute(
                docConfig.createElement(FUNCTION)
                , NAMESPACE, function.getNamespace())
                , REGISTER_CLASS, function.getRegisterClass())));
        config.getClassList().forEach(clazz -> {
            Element classElement = withAttribute(withAttribute(
                docConfig.createElement(CLASS)
                , NAME, clazz.getClassName())
                , CONTEXT, clazz.getClassContext());
            root.appendChild(classElement);
            clazz.getCollectList().forEach(collect -> classElement.appendChild(withAttribute(withAttribute(withAttribute(withAttribute(
                docConfig.createElement(COLLECT)
                , FIELD, collect.getField())
                , WHAT, collect.getWhat())
                , TO, collect.getTo())
                , WHEN, collect.getWhen())));
            clazz.getExecuteList().forEach(execute -> classElement.appendChild(withAttribute(withAttribute(withAttribute(
                docConfig.createElement(EXECUTE)
                , FIELD, execute.getField())
                , JEXL, execute.getJexl())
                , WHEN, execute.getWhen())));
            clazz.getVariableList().forEach(variable -> classElement.appendChild(withAttribute(withAttribute(
                docConfig.createElement(VARIABLE)
                , FIELD, variable.getField())
                , NAME, variable.getVariable())));

            prettyPrint(docConfig, out);

        });
    }

    private static void prettyPrint(Document xml, OutputStream out) {
        Transformer tf;
        try {
            tf = TransformerFactory.newInstance().newTransformer();
        } catch (TransformerConfigurationException e) {
            throw new Error("Cannot create transformer ", e);
        }
        tf.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
        tf.setOutputProperty(OutputKeys.INDENT, "yes");
        try {
            tf.transform(new DOMSource(xml), new StreamResult(out));
        } catch (TransformerException e) {
            throw new Error("Could not write config to stream", e);
        }
    }

    private static String getAttribute(Node item, String attributeName) {
        Node attribute = item.getAttributes().getNamedItem(attributeName);
        if (attribute == null) return null;
        return attribute.getNodeValue();
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy