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

org.debux.webmotion.server.parser.DefaultMappingParser Maven / Gradle / Ivy

There is a newer version: 10.0.0
Show newest version
/*
 * #%L
 * Toolkit :: Server
 * %%
 * Copyright (C) 2017 - 2024 Ultreia.io
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program.  If not, see
 * .
 * #L%
 */
package org.debux.webmotion.server.parser;

import org.apache.commons.io.IOUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.debux.webmotion.server.WebMotionException;
import org.debux.webmotion.server.mapping.Action;
import org.debux.webmotion.server.mapping.ActionRule;
import org.debux.webmotion.server.mapping.Config;
import org.debux.webmotion.server.mapping.ErrorRule;
import org.debux.webmotion.server.mapping.FilterRule;
import org.debux.webmotion.server.mapping.FragmentUrl;
import org.debux.webmotion.server.mapping.Mapping;
import org.debux.webmotion.server.mapping.Properties;
import org.debux.webmotion.server.mapping.Properties.PropertiesItem;
import org.parboiled.Node;
import org.parboiled.Parboiled;
import org.parboiled.buffers.InputBuffer;
import org.parboiled.errors.ErrorUtils;
import org.parboiled.parserunners.ReportingParseRunner;
import org.parboiled.support.ParseTreeUtils;
import org.parboiled.support.ParsingResult;
import org.parboiled.support.Position;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Deque;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

/**
 * Parboiled tree has been read to generate the mapping object.
 *
 * @author jruchaud
 */
public class DefaultMappingParser extends MappingParser {

    private static final Logger log = LogManager.getLogger(DefaultMappingParser.class);

    /**
     * Visit on type of element.
     */
    public static class Visit {

        /**
         * Accept visit before explore child, by default do nothing.
         *
         * @param inputBuffer current buffer
         * @param node        node of element
         */
        public void acceptBefore(Node node, InputBuffer inputBuffer) {
            String value = org.parboiled.common.StringUtils.escape(ParseTreeUtils.getNodeText(node, inputBuffer));
            log.debug("with value = " + value);
            acceptBefore(value);
        }

        /**
         * Accept visit before explore child, by default do nothing.
         *
         * @param value value of element
         */
        public void acceptBefore(String value) {
        }

        /**
         * Accept visit after explore child, by default do nothing.
         *
         * @param inputBuffer current buffer
         * @param node        node of element
         */
        public void acceptAfter(Node node, InputBuffer inputBuffer) {
            String value = org.parboiled.common.StringUtils.escape(ParseTreeUtils.getNodeText(node, inputBuffer));
            acceptAfter(value);
        }

        /**
         * Accept visit after explore child, by default do nothing.
         *
         * @param value value of element
         */
        public void acceptAfter(String value) {
        }

        /**
         * @param inputBuffer current buffer
         * @param node        node of element
         * @return line number of node
         */
        public int getLine(Node node, InputBuffer inputBuffer) {
            int index = node.getStartIndex();
            Position position = inputBuffer.getPosition(index);
            return position.line;
        }
    }

    /**
     * Implement tree visitor to execute the parsing with rules based on path.
     */
    public static class TreeVisitor {

        /**
         * Current mapping parsed
         */
        protected Mapping mapping;

        /**
         * Rules uses to parse
         */
        protected Map rules;

        /**
         * Stack during the parsing
         */
        protected Deque stack;

        public TreeVisitor() {
            this.mapping = new Mapping();
            this.stack = new LinkedList<>();
            this.rules = new HashMap<>();

            createRules();
        }

        public Mapping getMapping() {
            return mapping;
        }

        protected String addPackageName(String packageName, String value) {
            if (packageName != null && !packageName.isEmpty()) {
                value = packageName + "." + value;
            }
            return value;
        }

        protected void createRules() {
            rules.put("/sectionConfig/configRule", new Visit() {
                @Override
                public void acceptAfter(String value) {
                    stack.removeLast();
                }
            });

            rules.put("/sectionConfig/configRule/configName", new Visit() {
                @Override
                public void acceptBefore(String value) {
                    stack.addLast(value.trim());
                }
            });

            rules.put("/sectionConfig/configRule/configValue", new Visit() {
                @Override
                public void acceptBefore(String value) {
                    Config config = mapping.getConfig();

                    String name = (String) stack.peekLast();
                    config.set(name, value.trim());
                }
            });

            rules.put("/sectionProperties", new Visit() {
                @Override
                public void acceptAfter(String value) {
                    stack.removeLast();
                }
            });

            rules.put("/sectionProperties/propertiesSection/propertiesIdentifier", new Visit() {
                @Override
                public void acceptBefore(String value) {
                    PropertiesItem item = new PropertiesItem(value);
                    stack.addLast(item);

                    Properties properties = mapping.getProperties();
                    properties.addItem(item);
                }
            });

            rules.put("/sectionProperties/propertiesRule/propertiesName", new Visit() {
                @Override
                public void acceptBefore(String value) {
                    stack.addLast(value.trim());
                }
            });

            rules.put("/sectionProperties/propertiesRule/propertiesValue", new Visit() {
                @Override
                public void acceptBefore(String value) {
                    String key = (String) stack.removeLast();
                    PropertiesItem item = (PropertiesItem) stack.peekLast();

                    value = value.replaceAll("\\\\\\\\n\\s*", "");
                    Objects.requireNonNull(item).addProperty(key, value.trim());
                }
            });

            rules.put("/sectionErrors/errorRule", new Visit() {
                @Override
                public void acceptBefore(Node node, InputBuffer inputBuffer) {
                    ErrorRule errorRule = new ErrorRule();
                    errorRule.setLine(getLine(node, inputBuffer));
                    errorRule.setMapping(mapping);
                    stack.addLast(errorRule);

                    List errorRules = mapping.getErrorRules();
                    errorRules.add(errorRule);
                }

                @Override
                public void acceptAfter(String value) {
                    stack.removeLast();
                }
            });

            rules.put("/sectionErrors/errorRule/errorCode", new Visit() {
                @Override
                public void acceptBefore(String value) {
                    ErrorRule errorRule = (ErrorRule) stack.peekLast();
                    Objects.requireNonNull(errorRule).setError(value);
                }
            });

            rules.put("/sectionErrors/errorRule/errorException", new Visit() {
                @Override
                public void acceptBefore(String value) {
                    ErrorRule errorRule = (ErrorRule) stack.peekLast();
                    Objects.requireNonNull(errorRule).setError(value);
                }

                @Override
                public void acceptAfter(String value) {
                    ErrorRule errorRule = (ErrorRule) stack.peekLast();
                    Objects.requireNonNull(errorRule).setMapping(mapping);
                }
            });

            rules.put("/sectionErrors/errorRule/errorJava/errorJavaValue", new Visit() {
                @Override
                public void acceptBefore(String value) {
                    Action action = new Action();
                    action.setType(Action.Type.ACTION);
                    String packageName = mapping.getConfig().getPackageErrors();
                    action.setFullName(addPackageName(packageName, value));

                    ErrorRule errorRule = (ErrorRule) stack.peekLast();
                    Objects.requireNonNull(errorRule).setAction(action);
                }
            });

            rules.put("/sectionErrors/errorRule/errorView/errorViewValue", new Visit() {
                @Override
                public void acceptBefore(String value) {
                    Action action = new Action();
                    action.setType(Action.Type.VIEW);
                    action.setFullName(value);

                    ErrorRule errorRule = (ErrorRule) stack.peekLast();
                    Objects.requireNonNull(errorRule).setAction(action);
                }
            });

            rules.put("/sectionErrors/errorRule/errorRedirect/errorUrlValue", new Visit() {
                @Override
                public void acceptBefore(String value) {
                    Action action = new Action();
                    action.setType(Action.Type.REDIRECT);
                    action.setFullName(value);

                    ErrorRule errorRule = (ErrorRule) stack.peekLast();
                    Objects.requireNonNull(errorRule).setAction(action);
                }
            });

            rules.put("/sectionErrors/errorRule/errorForward/errorUrlValue", new Visit() {
                @Override
                public void acceptBefore(String value) {
                    Action action = new Action();
                    action.setType(Action.Type.FORWARD);
                    action.setFullName(value);

                    ErrorRule errorRule = (ErrorRule) stack.peekLast();
                    Objects.requireNonNull(errorRule).setAction(action);
                }
            });

            rules.put("/sectionFilters/filterRule", new Visit() {
                @Override
                public void acceptBefore(Node node, InputBuffer inputBuffer) {
                    FilterRule filterRule = new FilterRule();
                    filterRule.setLine(getLine(node, inputBuffer));
                    filterRule.setMapping(mapping);
                    stack.addLast(filterRule);

                    List filterRules = mapping.getFilterRules();
                    filterRules.add(filterRule);
                }

                @Override
                public void acceptAfter(String value) {
                    stack.removeLast();
                }
            });

            rules.put("/sectionFilters/filterRule/Method/MethodValue", new Visit() {
                @Override
                public void acceptBefore(String value) {
                    FilterRule filterRule = (FilterRule) stack.peekLast();
                    List methods = Objects.requireNonNull(filterRule).getMethods();
                    methods.add(value);
                }
            });

            rules.put("/sectionFilters/filterRule/filterPath", new Visit() {
                @Override
                public void acceptBefore(String value) {
                    value = value.replaceAll("/\\*/", "/[^/]*/");
                    value = value.replaceAll("/\\*", "/.*");
                    value = "^" + value + "$";

                    FilterRule filterRule = (FilterRule) stack.peekLast();
                    Objects.requireNonNull(filterRule).setPattern(Pattern.compile(value));
                }
            });

            rules.put("/sectionFilters/filterRule/filterJava/filterJavaValue", new Visit() {
                @Override
                public void acceptBefore(String value) {
                    Action action = new Action();
                    action.setType(Action.Type.ACTION);
                    String packageName = mapping.getConfig().getPackageFilters();
                    action.setFullName(addPackageName(packageName, value));

                    FilterRule filterRule = (FilterRule) stack.peekLast();
                    Objects.requireNonNull(filterRule).setAction(action);
                }
            });

            rules.put("/sectionFilters/filterRule/filterParameters/filterParameter", new Visit() {
                @Override
                public void acceptAfter(String value) {
                    stack.removeLast();
                }
            });

            rules.put("/sectionFilters/filterRule/filterParameters/filterParameter/filterName", new Visit() {
                @Override
                public void acceptBefore(String value) {
                    FilterRule filterRule = (FilterRule) stack.peekLast();
                    Map defaultParameters = Objects.requireNonNull(filterRule).getDefaultParameters();
                    defaultParameters.put(value, null);
                    stack.addLast(value);
                }
            });

            rules.put("/sectionFilters/filterRule/filterParameters/filterParameter/filterValue", new Visit() {
                @Override
                public void acceptBefore(String value) {
                    String key = (String) stack.peekLast();
                    FilterRule actionRule = (FilterRule) stack.getFirst();
                    Map defaultParameters = actionRule.getDefaultParameters();
                    defaultParameters.put(key, new String[]{value});
                }
            });

            rules.put("/sectionActions/actionRule", new Visit() {
                @Override
                public void acceptBefore(Node node, InputBuffer inputBuffer) {
                    ActionRule actionRule = new ActionRule();
                    actionRule.setLine(getLine(node, inputBuffer));
                    actionRule.setMapping(mapping);
                    stack.addLast(actionRule);

                    List actionRules = mapping.getActionRules();
                    actionRules.add(actionRule);
                }

                @Override
                public void acceptAfter(String value) {
                    stack.removeLast();
                }
            });

            rules.put("/sectionActions/actionRule/Method/MethodValue", new Visit() {
                @Override
                public void acceptBefore(String value) {
                    ActionRule actionRule = (ActionRule) stack.peekLast();
                    List methods = Objects.requireNonNull(actionRule).getMethods();
                    methods.add(value);
                }
            });

            rules.put("/sectionActions/actionRule/actionPath/actionPathSlash", new Visit() {
                @Override
                public void acceptBefore(String value) {
                    FragmentUrl fragment = new FragmentUrl();
                    fragment.setValue(value);

                    Pattern pattern = Pattern.compile("^/$");
                    fragment.setPattern(pattern);

                    ActionRule actionRule = (ActionRule) stack.peekLast();
                    List ruleUrl = Objects.requireNonNull(actionRule).getRuleUrl();
                    ruleUrl.add(fragment);
                }
            });

            rules.put("/sectionActions/actionRule/actionPath/actionPathStatic", new Visit() {
                @Override
                public void acceptBefore(String value) {
                    FragmentUrl fragment = new FragmentUrl();
                    fragment.setValue(value);

                    Pattern pattern = Pattern.compile("^" + value + "$");
                    fragment.setPattern(pattern);

                    ActionRule actionRule = (ActionRule) stack.peekLast();
                    List ruleUrl = Objects.requireNonNull(actionRule).getRuleUrl();
                    ruleUrl.add(fragment);
                }
            });

            rules.put("/sectionActions/actionRule/actionPath/actionVariable", new Visit() {
                @Override
                public void acceptBefore(String value) {
                    ActionRule actionRule = (ActionRule) stack.peekLast();
                    List ruleUrl = Objects.requireNonNull(actionRule).getRuleUrl();

                    FragmentUrl fragment = new FragmentUrl();
                    ruleUrl.add(fragment);

                    stack.addLast(fragment);
                }

                @Override
                public void acceptAfter(String value) {
                    stack.removeLast();
                }
            });

            rules.put("/sectionActions/actionRule/actionPath/actionVariable/QualifiedIdentifier", new Visit() {
                @Override
                public void acceptBefore(String value) {
                    FragmentUrl fragment = (FragmentUrl) stack.peekLast();
                    Objects.requireNonNull(fragment).setName(value);
                }
            });

            rules.put("/sectionActions/actionRule/actionPath/actionVariable/actionPattern", new Visit() {
                @Override
                public void acceptBefore(String value) {
                    FragmentUrl fragment = (FragmentUrl) stack.peekLast();
                    value = value.replaceAll("\\\\\\{", "{");
                    value = value.replaceAll("\\\\}", "}");
                    Objects.requireNonNull(fragment).setValue(value);

                    try {
                        Pattern pattern = Pattern.compile("^" + value + "$");
                        fragment.setPattern(pattern);
                    } catch (PatternSyntaxException ex) {
                        log.debug("Invalid pattern " + value, ex);
                    }
                }
            });

            rules.put("/sectionActions/actionRule/actionJava", new Visit() {
                @Override
                public void acceptBefore(String value) {
                    Action action = new Action();
                    action.setType(Action.Type.ACTION);

                    ActionRule actionRule = (ActionRule) stack.peekLast();
                    Objects.requireNonNull(actionRule).setAction(action);

                    stack.addLast(action);
                }

                @Override
                public void acceptAfter(String value) {
                    stack.removeLast();
                }
            });

            rules.put("/sectionActions/actionRule/actionJava/actionJavaValue", new Visit() {
                @Override
                public void acceptBefore(String value) {
                    Action action = (Action) stack.peekLast();
                    String packageName = mapping.getConfig().getPackageActions();
                    Objects.requireNonNull(action).setFullName(addPackageName(packageName, value));
                }
            });

            rules.put("/sectionActions/actionRule/actionJava/actionJavaType", new Visit() {
                @Override
                public void acceptBefore(String value) {
                    Action action = (Action) stack.peekLast();
                    if (value == null || value.isEmpty()) {
                        Objects.requireNonNull(action).setAsync(null);
                    } else if (value.equalsIgnoreCase("ASYNC:")) {
                        Objects.requireNonNull(action).setAsync(true);
                    } else if (value.equalsIgnoreCase("SYNC:")) {
                        Objects.requireNonNull(action).setAsync(false);
                    }
                }
            });

            rules.put("/sectionActions/actionRule/actionView/actionViewValue", new Visit() {
                @Override
                public void acceptBefore(String value) {
                    Action action = new Action();
                    action.setType(Action.Type.VIEW);
                    String packageName = mapping.getConfig().getPackageViews();
                    action.setFullName(addPackageName(packageName, value));

                    ActionRule actionRule = (ActionRule) stack.peekLast();
                    Objects.requireNonNull(actionRule).setAction(action);
                }
            });

            rules.put("/sectionActions/actionRule/actionRedirect/actionRedirectValue", new Visit() {
                @Override
                public void acceptBefore(String value) {
                    Action action = new Action();
                    action.setType(Action.Type.REDIRECT);
                    action.setFullName(value);

                    ActionRule actionRule = (ActionRule) stack.peekLast();
                    Objects.requireNonNull(actionRule).setAction(action);
                }
            });

            rules.put("/sectionActions/actionRule/actionForward/actionForwardValue", new Visit() {
                @Override
                public void acceptBefore(String value) {
                    Action action = new Action();
                    action.setType(Action.Type.FORWARD);
                    action.setFullName(value);

                    ActionRule actionRule = (ActionRule) stack.peekLast();
                    Objects.requireNonNull(actionRule).setAction(action);
                }
            });

            rules.put("/sectionActions/actionRule/actionDefaultParameters/actionDefaultParameter", new Visit() {
                @Override
                public void acceptAfter(String value) {
                    stack.removeLast();
                }
            });

            rules.put("/sectionActions/actionRule/actionDefaultParameters/actionDefaultParameter/actionDefaultName", new Visit() {
                @Override
                public void acceptBefore(String value) {
                    ActionRule actionRule = (ActionRule) stack.peekLast();
                    Map defaultParameters = Objects.requireNonNull(actionRule).getDefaultParameters();
                    defaultParameters.put(value, null);
                    stack.addLast(value);
                }
            });

            rules.put("/sectionActions/actionRule/actionDefaultParameters/actionDefaultParameter/actionDefaultValue", new Visit() {
                @Override
                public void acceptBefore(String value) {
                    String key = (String) stack.peekLast();
                    ActionRule actionRule = (ActionRule) stack.getFirst();
                    Map defaultParameters = actionRule.getDefaultParameters();
                    defaultParameters.put(key, new String[]{value});
                }
            });

            rules.put("/sectionActions/actionRule/actionParameters/actionParameter", new Visit() {
                @Override
                public void acceptBefore(String value) {
                    ActionRule actionRule = (ActionRule) stack.peekLast();
                    List ruleParameters = Objects.requireNonNull(actionRule).getRuleParameters();

                    FragmentUrl fragment = new FragmentUrl();
                    stack.addLast(fragment);
                    ruleParameters.add(fragment);
                }

                @Override
                public void acceptAfter(String value) {
                    stack.removeLast();
                }
            });

            rules.put("/sectionActions/actionRule/actionParameters/actionParameter/actionName", new Visit() {
                @Override
                public void acceptBefore(String value) {
                    FragmentUrl fragment = (FragmentUrl) stack.peekLast();
                    Objects.requireNonNull(fragment).setParam(value);
                }
            });

            rules.put("/sectionActions/actionRule/actionParameters/actionParameter/actionVariable/QualifiedIdentifier", new Visit() {
                @Override
                public void acceptBefore(String value) {
                    FragmentUrl fragment = (FragmentUrl) stack.peekLast();
                    Objects.requireNonNull(fragment).setName(value);
                }
            });

            rules.put("/sectionActions/actionRule/actionParameters/actionParameter/actionVariable/actionPattern", new Visit() {
                @Override
                public void acceptBefore(String value) {
                    FragmentUrl fragment = (FragmentUrl) stack.peekLast();
                    value = value.replaceAll("\\\\\\{", "{");
                    value = value.replaceAll("\\\\}", "}");
                    Objects.requireNonNull(fragment).setValue(value);

                    try {
                        Pattern pattern = Pattern.compile("^" + value + "$");
                        fragment.setPattern(pattern);
                    } catch (PatternSyntaxException ex) {
                        log.debug("Invalid pattern " + value, ex);
                    }
                }
            });

            rules.put("/sectionActions/actionRule/actionParameters/actionParameter/actionValue", new Visit() {
                @Override
                public void acceptBefore(String value) {
                    FragmentUrl fragment = (FragmentUrl) stack.peekLast();
                    Objects.requireNonNull(fragment).setValue(value);

                    Pattern pattern = Pattern.compile("^" + value + "$");
                    fragment.setPattern(pattern);
                }
            });

            rules.put("/sectionExtensions/extensionRule/extensionPath", new Visit() {
                @Override
                public void acceptBefore(String value) {
                    stack.addLast(value);
                }
            });

            rules.put("/sectionExtensions/extensionRule/extensionFile", new Visit() {
                @Override
                public void acceptBefore(String value) {
                    String path = (String) stack.removeLast();
                    List extensionsRules = mapping.getExtensionsRules();

                    Enumeration resources;
                    try {
                        resources = getClass().getClassLoader().getResources(value);
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                    while (resources.hasMoreElements()) {
                        URL url = resources.nextElement();
                        DefaultMappingParser parser = new DefaultMappingParser();
                        Mapping extensionMapping = parser.parse(url, mapping.getConfig());
                        extensionMapping.setExtensionPath(path);
                        extensionMapping.setParentMapping(mapping);
                        extensionsRules.add(extensionMapping);

                        Properties extensionProperties = extensionMapping.getProperties();
                        Properties properties = mapping.getProperties();
                        properties.addProperties(extensionProperties);
                    }
                }
            });
        }

        public void visitTree(Node node, InputBuffer inputBuffer, String path) {
            String label = node.getLabel();
            boolean notSkip = !label.startsWith("'") &&
                    !label.equals("mapping") &&
                    !label.equals("sections") &&
                    !label.equals("EOI") &&
                    !label.equals("Sequence") &&
                    !label.equals("Optional") &&
                    !label.equals("FirstOf") &&
                    !label.equals("ZeroOrMore") &&
                    !label.equals("OneOrMore");

            if (notSkip) {
                path += "/" + label;

                log.debug("Before " + path);
                Visit visit = rules.get(path);
                if (visit != null) {
                    visit.acceptBefore(node, inputBuffer);
                }
            }

            for (Node sub : node.getChildren()) {
                visitTree(sub, inputBuffer, path);
            }

            if (notSkip) {
                log.debug("After " + path);
                Visit visit = rules.get(path);
                if (visit != null) {
                    visit.acceptAfter(node, inputBuffer);
                }
            }
        }
    }

    @Override
    public Mapping parse(URL url, Config config) {
        try {
            String name = url.toExternalForm();

            // Read the content in file
            InputStream stream = url.openStream();
            String content = IOUtils.toString(stream, StandardCharsets.UTF_8);

            // Parse the content
            @SuppressWarnings("unchecked") ParboiledGrammar parser = Parboiled.createParser(ParboiledGrammar.class);
            ReportingParseRunner runner = new ReportingParseRunner<>(parser.mapping());
            ParsingResult result = runner.run(content);

            // Detect if the parser has an errors
            if (result.hasErrors()) {
                log.error(ErrorUtils.printParseErrors(result));
                throw new WebMotionException("Error to parse the mapping file");
            }

            // Visit tree
            TreeVisitor tree = new TreeVisitor();
            if (config != null) {
                tree.getMapping().setConfig(new Config(config));
            }
            tree.visitTree(result.parseTreeRoot, result.inputBuffer, "");

            // Get the mapping after the visit
            Mapping mapping = tree.getMapping();
            mapping.setName(name);
            log.info("Parsed mapping " + name);

            return mapping;

        } catch (IOException ioe) {
            throw new WebMotionException("Error to read the file mapping", ioe);
        }
    }

}