Java tutorial
/* * Copyright (c) 2008-2016 Haulmont. * * 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 com.haulmont.cuba.gui.xml; import com.haulmont.bali.util.Dom4j; import com.haulmont.bali.util.ParamsMap; import com.haulmont.cuba.core.global.AppBeans; import com.haulmont.cuba.core.global.DevelopmentException; import com.haulmont.cuba.core.global.Resources; import com.haulmont.cuba.gui.xml.layout.LayoutLoader; import com.haulmont.cuba.gui.xml.layout.ScreenXmlParser; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.dom4j.*; import java.io.InputStream; import java.io.StringWriter; import java.util.*; /** * Provides inheritance of screen XML descriptors. * */ public class XmlInheritanceProcessor { private static final Logger log = LoggerFactory.getLogger(XmlInheritanceProcessor.class); private Document document; private Namespace extNs; private Map<String, Object> params; private List<ElementTargetLocator> targetLocators = new ArrayList<>(); private Resources resources = AppBeans.get(Resources.NAME); private ScreenXmlParser screenXmlParser = AppBeans.get(ScreenXmlParser.NAME); public XmlInheritanceProcessor(Document document, Map<String, Object> params) { this.document = document; this.params = params; extNs = document.getRootElement().getNamespaceForPrefix("ext"); targetLocators.add(new ViewPropertyElementTargetLocator()); targetLocators.add(new ViewElementTargetLocator()); targetLocators.add(new ButtonElementTargetLocator()); targetLocators.add(new CommonElementTargetLocator()); } public Element getResultRoot() { Element result; Element root = document.getRootElement(); String ancestorTemplate = root.attributeValue("extends"); if (!StringUtils.isBlank(ancestorTemplate)) { InputStream ancestorStream = resources.getResourceAsStream(ancestorTemplate); if (ancestorStream == null) { ancestorStream = getClass().getResourceAsStream(ancestorTemplate); if (ancestorStream == null) { throw new DevelopmentException("Template is not found", "Ancestor's template path", ancestorTemplate); } } Document ancestorDocument; try { ancestorDocument = screenXmlParser.parseDescriptor(ancestorStream); } finally { IOUtils.closeQuietly(ancestorStream); } XmlInheritanceProcessor processor = new XmlInheritanceProcessor(ancestorDocument, params); result = processor.getResultRoot(); process(result, root); if (log.isTraceEnabled()) { StringWriter writer = new StringWriter(); Dom4j.writeDocument(result.getDocument(), true, writer); log.trace("Resulting template:\n" + writer.toString()); } } else { result = root; } return result; } private void process(Element resultElem, Element extElem) { // set text if (!StringUtils.isBlank(extElem.getText())) resultElem.setText(extElem.getText()); // add all attributes from extension for (Attribute attribute : Dom4j.attributes(extElem)) { if (resultElem == document.getRootElement() && attribute.getName().equals("extends")) { // ignore "extends" in root element continue; } resultElem.addAttribute(attribute.getName(), attribute.getValue()); } String idx = extElem.attributeValue(new QName("index", extNs)); if (resultElem != document.getRootElement() && StringUtils.isNotBlank(idx)) { int index = Integer.parseInt(idx); Element parent = resultElem.getParent(); if (index < 0 || index > parent.elements().size()) { String message = String.format( "Incorrect extension XML for screen. Could not move existing element %s to position %s", resultElem.getName(), index); throw new DevelopmentException(message, ParamsMap.of("element", resultElem.getName(), "index", index)); } parent.remove(resultElem); //noinspection unchecked parent.elements().add(index, resultElem); } // add and process elements Set<Element> justAdded = new HashSet<>(); for (Element element : Dom4j.elements(extElem)) { // look for suitable locator ElementTargetLocator locator = null; for (ElementTargetLocator l : targetLocators) { if (l.suitableFor(element)) { locator = l; break; } } if (locator != null) { Element target = locator.locate(resultElem, element); // process target or a new element if target not found if (target != null) { process(target, element); } else { addNewElement(resultElem, element, justAdded); } } else { // if no suitable locator found, look for a single element with the same name List<Element> list = Dom4j.elements(resultElem, element.getName()); if (list.size() == 1 && !justAdded.contains(list.get(0))) { process(list.get(0), element); } else { addNewElement(resultElem, element, justAdded); } } } } private void addNewElement(Element resultElem, Element element, Set<Element> justAdded) { String idx = element.attributeValue(new QName("index", extNs)); Element newElement; if (StringUtils.isBlank(idx)) { newElement = resultElem.addElement(element.getName()); } else { newElement = DocumentHelper.createElement(element.getName()); @SuppressWarnings("unchecked") List<Element> elements = resultElem.elements(); int index = Integer.parseInt(idx); if (index < 0 || index > elements.size()) { String message = String.format( "Incorrect extension XML for screen. Could not paste new element %s to position %s", newElement.getName(), index); throw new DevelopmentException(message, ParamsMap.of("element", newElement.getName(), "index", index)); } elements.add(index, newElement); } justAdded.add(newElement); process(newElement, element); } private interface ElementTargetLocator { boolean suitableFor(Element extElem); Element locate(Element resultParentElem, Element extElem); } private static class CommonElementTargetLocator implements ElementTargetLocator { @Override public boolean suitableFor(Element extElem) { return !StringUtils.isBlank(extElem.attributeValue("id")); } @Override public Element locate(Element resultParentElem, Element extElem) { String id = extElem.attributeValue("id"); for (Element e : Dom4j.elements(resultParentElem)) { if (id.equals(e.attributeValue("id"))) { return e; } } return null; } } private static class ViewElementTargetLocator implements ElementTargetLocator { @Override public boolean suitableFor(Element extElem) { return "view".equals(extElem.getName()); } @Override public Element locate(Element resultParentElem, Element extElem) { String entity = extElem.attributeValue("entity"); String clazz = extElem.attributeValue("class"); String name = extElem.attributeValue("name"); for (Element e : Dom4j.elements(resultParentElem)) { if (name.equals(e.attributeValue("name")) && ((entity != null && entity.equals(e.attributeValue("entity"))) || (clazz != null && clazz.equals(e.attributeValue("class"))))) { return e; } } return null; } } private static class ViewPropertyElementTargetLocator implements ElementTargetLocator { @Override public boolean suitableFor(Element extElem) { return "property".equals(extElem.getName()); } @Override public Element locate(Element resultParentElem, Element extElem) { String name = extElem.attributeValue("name"); for (Element e : Dom4j.elements(resultParentElem)) { if (name.equals(e.attributeValue("name"))) { return e; } } return null; } } private static class ButtonElementTargetLocator implements ElementTargetLocator { @Override public boolean suitableFor(Element extElem) { return "button".equals(extElem.getName()) && extElem.attributeValue("id") == null && extElem.attributeValue("action") != null; } @Override public Element locate(Element resultParentElem, Element extElem) { String action = extElem.attributeValue("action"); for (Element e : Dom4j.elements(resultParentElem)) { if (action.equals(e.attributeValue("action"))) { return e; } } return null; } } }