org.mycore.frontend.editor.MCREditorDefReader.java Source code

Java tutorial

Introduction

Here is the source code for org.mycore.frontend.editor.MCREditorDefReader.java

Source

/*
 * $Revision$
 * $Date$
 *
 * This file is part of ***  M y C o R e  ***
 * See http://www.mycore.de/ for details.
 *
 * This program is free software; you can use it, redistribute it
 * and / or modify it under the terms of the GNU General Public License
 * (GPL) as published by the Free Software Foundation; either version 2
 * 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, in a file called gpl.txt or license.txt.
 * If not, write to the Free Software Foundation Inc.,
 * 59 Temple Place - Suite 330, Boston, MA  02111-1307 USA
 */

package org.mycore.frontend.editor;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Objects;
import java.util.Properties;
import java.util.Set;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jdom2.Content;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.filter.Filters;
import org.mycore.common.MCRConstants;
import org.mycore.common.config.MCRConfigurationException;
import org.mycore.common.content.MCRJDOMContent;
import org.mycore.common.xml.MCRURIResolver;
import org.mycore.common.xml.MCRXMLParserFactory;

/*
 * Reads in definition of editor forms like search mask and data input forms.
 * Resolves includes and prepares editor form for output.
 */
public class MCREditorDefReader {
    private final static Logger LOGGER = LogManager.getLogger(MCREditorDefReader.class);

    private Element editor;

    HashMap<String, Element> id2component = new LinkedHashMap<String, Element>();

    HashMap<Element, String> referencing2ref = new LinkedHashMap<Element, String>();

    private MCRTokenSubstitutor tokenSubstitutor;

    /**
     * Reads the editor definition from the given URI
     *
     * @param validate
     *            if true, validate editor definition against schema
     * @param parameters
     *            http request parameters
     */
    MCREditorDefReader(String uri, String ref, boolean validate, MCRParameters parameters) {
        long time = System.nanoTime();
        this.tokenSubstitutor = new MCRTokenSubstitutor(parameters);

        Element include = new Element("include").setAttribute("uri", uri);
        if (ref != null && ref.length() > 0) {
            include.setAttribute("ref", ref);
        }

        editor = new Element("editor");
        editor.setAttribute("id", ref);
        editor.addContent(include);
        resolveIncludes(editor);
        checkDuplicateIDs(editor);
        resolveReferences();
        if (validate) {
            validate(uri, ref);
        }

        time = (System.nanoTime() - time) / 1000000;
        LOGGER.info("Finished reading editor definition in " + time + " ms");
    }

    private void checkDuplicateIDs(Element editor) {
        Set<String> ids = new HashSet<String>();
        Iterator<Element> elements = editor.getDescendants(Filters.element());
        while (elements.hasNext()) {
            String id = elements.next().getAttributeValue("id");
            if (id == null || id.trim().length() == 0) {
                continue;
            }
            if (ids.contains(id)) {
                String msg = "Duplicate ID '" + id + "', already used in editor definition";
                throw new MCRConfigurationException(msg);
            } else {
                ids.add(id);
            }
        }
    }

    private void validate(String uri, String ref) {
        if (ref != null && ref.length() > 0) {
            uri += "#" + ref;
        }
        LOGGER.info("Validating editor " + uri + "...");

        Document doc = new Document(editor);
        editor.setAttribute("noNamespaceSchemaLocation", "editor.xsd", MCRConstants.XSI_NAMESPACE);

        try {
            MCRXMLParserFactory.getValidatingParser().parseXML(new MCRJDOMContent(doc));
        } catch (Exception ex) {
            String msg = "Error validating editor " + uri;
            LOGGER.error(msg);
            throw new MCRConfigurationException(msg, ex);
        }

        editor.detach();
        editor.removeAttribute("noNamespaceSchemaLocation", MCRConstants.XSI_NAMESPACE);
        LOGGER.info("Validation succeeded.");
    }

    /**
     * Returns the complete editor with all references resolved
     */
    Element getEditor() {
        return editor;
    }

    /**
     * Recursively removes include elements that are direct or indirect children
     * of the given container element and replaces them with the included
     * resource. Includes that may be contained in included resources are
     * recursively resolved, too.
     *
     * @param element
     *            The element where to start resolving includes
     */
    private boolean resolveIncludes(Element element) {
        boolean replaced = false;

        String ref = element.getAttributeValue("ref", "");
        ref = tokenSubstitutor.substituteTokens(ref);

        if (element.getName().equals("include")) {
            String uri = element.getAttributeValue("uri");
            if (uri != null) {
                uri = tokenSubstitutor.substituteTokens(uri);
                LOGGER.info("Including " + uri + (ref.length() > 0 ? "#" + ref : ""));
                Element parent = element.getParentElement();
                int pos = parent.indexOf(element);

                Element container = MCRURIResolver.instance().resolve(uri);
                List<Content> found;

                if (ref.length() == 0) {
                    found = container.cloneContent();
                } else {
                    found = findContent(container, ref);
                    ref = "";
                }
                replaced = true;
                parent.addContent(pos, found);
                element.detach();
            }
        } else {
            String id = element.getAttributeValue("id", "");
            if (id.length() > 0) {
                id2component.put(id, element);
            }

            setDefaultAttributes(element);
            resolveChildren(element);
        }

        if (ref.length() > 0) {
            referencing2ref.put(element, ref);
        }
        return replaced;
    }

    private void resolveChildren(Element parent) {
        for (int i = 0; i < parent.getContentSize(); i++) {
            Content child = parent.getContent(i);
            if (child instanceof Element && resolveIncludes((Element) child)) {
                i--;
            }
        }
    }

    private List<Content> findContent(Element candidate, String id) {
        if (id.equals(candidate.getAttributeValue("id"))) {
            return candidate.cloneContent();
        } else {
            return ((List<Element>) candidate.getChildren()).stream().map(child -> findContent(child, id))
                    .filter(Objects::nonNull).findFirst().orElse(null);
        }
    }

    /**
     * Returns that direct or indirect child element of the given element, thats
     * ID attribute has the given value.
     *
     * @param id
     *            the value the ID attribute must have
     * @param candidate
     *            the element to start searching with
     * @return the element below that has the given ID, or null if no such
     *         element exists.
     */
    public static Element findElementByID(String id, Element candidate) {
        if (id.equals(candidate.getAttributeValue("id"))) {
            return candidate;
        } else {
            return ((List<Element>) candidate.getChildren()).stream().map(child -> findElementByID(id, child))
                    .filter(Objects::nonNull).findFirst().orElse(null);
        }
    }

    /**
     * Recursively resolves references by the @ref attribute and
     * replaces them with the referenced component.
     */
    private void resolveReferences() {
        for (Iterator<Element> it = referencing2ref.keySet().iterator(); it.hasNext();) {
            Element referencing = it.next();
            String id = referencing2ref.get(referencing);
            LOGGER.debug("Resolving reference to " + id);

            Element found = id2component.get(id);
            if (found == null) {
                String msg = "Reference to component " + id + " could not be resolved";
                throw new MCRConfigurationException(msg);
            }

            String name = referencing.getName();
            referencing.removeAttribute("ref");
            it.remove();

            if (name.equals("cell") || name.equals("repeater")) {
                if (found.getParentElement().getName().equals("components")) {
                    referencing.addContent(0, found.detach());
                } else {
                    referencing.addContent(0, (Element) found.clone());
                }
            } else if (name.equals("panel")) {
                if (referencing2ref.containsValue(id)) {
                    referencing.addContent(0, found.cloneContent());
                } else {
                    found.detach();
                    List<Content> content = found.getContent();
                    for (int i = 0; !content.isEmpty(); i++) {
                        Content child = content.remove(0);
                        referencing.addContent(i, child);
                    }
                }
            } else if (name.equals("include")) {
                Element parent = referencing.getParentElement();
                int pos = parent.indexOf(referencing);
                referencing.detach();

                if (referencing2ref.containsValue(id)) {
                    parent.addContent(pos, found.cloneContent());
                } else {
                    found.detach();
                    List<Content> content = found.getContent();
                    for (int i = pos; !content.isEmpty(); i++) {
                        Content child = content.remove(0);
                        parent.addContent(i, child);
                    }
                }
            }
        }

        Element components = editor.getChild("components");
        String root = components.getAttributeValue("root");

        for (int i = 0; i < components.getContentSize(); i++) {
            Content child = components.getContent(i);
            if (!(child instanceof Element)) {
                continue;
            }
            if (((Element) child).getName().equals("headline")) {
                continue;
            }
            if (!root.equals(((Element) child).getAttributeValue("id"))) {
                components.removeContent(i--);
            }
        }
    }

    /**
     * This map contains default attribute values to set for a given element name
     */
    private static HashMap<String, Properties> defaultAttributes = new HashMap<String, Properties>();

    static {
        defaultAttributes.put("cell", new Properties());
        defaultAttributes.get("cell").setProperty("row", "1");
        defaultAttributes.get("cell").setProperty("col", "1");
        defaultAttributes.get("cell").setProperty("class", "editorCell");
        defaultAttributes.put("headline", new Properties());
        defaultAttributes.get("headline").setProperty("class", "editorHeadline");
        defaultAttributes.put("repeater", new Properties());
        defaultAttributes.get("repeater").setProperty("class", "editorRepeater");
        defaultAttributes.get("repeater").setProperty("min", "1");
        defaultAttributes.get("repeater").setProperty("max", "100");
        defaultAttributes.put("panel", new Properties());
        defaultAttributes.get("panel").setProperty("class", "editorPanel");
        defaultAttributes.put("editor", new Properties());
        defaultAttributes.get("editor").setProperty("class", "editor");
        defaultAttributes.put("helpPopup", new Properties());
        defaultAttributes.get("helpPopup").setProperty("class", "editorButton");
        defaultAttributes.put("text", new Properties());
        defaultAttributes.get("text").setProperty("class", "editorText");
        defaultAttributes.put("textfield", new Properties());
        defaultAttributes.get("textfield").setProperty("class", "editorTextfield");
        defaultAttributes.put("textarea", new Properties());
        defaultAttributes.get("textarea").setProperty("class", "editorTextarea");
        defaultAttributes.put("file", new Properties());
        defaultAttributes.get("file").setProperty("class", "editorFile");
        defaultAttributes.put("password", new Properties());
        defaultAttributes.get("password").setProperty("class", "editorPassword");
        defaultAttributes.put("subselect", new Properties());
        defaultAttributes.get("subselect").setProperty("class", "editorButton");
        defaultAttributes.put("submitButton", new Properties());
        defaultAttributes.get("submitButton").setProperty("class", "editorButton");
        defaultAttributes.put("cancelButton", new Properties());
        defaultAttributes.get("cancelButton").setProperty("class", "editorButton");
        defaultAttributes.put("button", new Properties());
        defaultAttributes.get("button").setProperty("class", "editorButton");
        defaultAttributes.put("list", new Properties());
        defaultAttributes.get("list").setProperty("class", "editorList");
        defaultAttributes.put("checkbox", new Properties());
        defaultAttributes.get("checkbox").setProperty("class", "editorCheckbox");
    }

    /**
     * Sets default attribute values for the given element, if any
     */
    private void setDefaultAttributes(Element element) {
        Properties defaults = defaultAttributes.get(element.getName());
        if (defaults == null) {
            return;
        }
        for (Object element2 : defaults.keySet()) {
            String key = (String) element2;
            if (element.getAttribute(key) == null) {
                element.setAttribute(key, defaults.getProperty(key));
            }
        }
    }

    /**
     * Transforms @var attribute values that have a condition like
     * title[@type='main'] into escaped internal syntax
     * title__type__main
     */
    static void fixConditionedVariables(Element element) {
        String var = element.getAttributeValue("var", "");
        int beginOfPredicate = var.indexOf("[@");
        while (beginOfPredicate != -1) {
            String predicate = MCREditorSubmission.escapePredicate(var, beginOfPredicate);
            int endOfPredicate = var.indexOf("]", beginOfPredicate);
            var = var.substring(0, beginOfPredicate) + predicate + var.substring(endOfPredicate + 1);
            element.setAttribute("var", var);
            beginOfPredicate = var.indexOf("[@", endOfPredicate);
        }

        for (Element child : (List<Element>) (element.getChildren()))
            fixConditionedVariables(child);
    }
}