001/**
002 * Copyright 2005-2016 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.rice.edl.impl.xml;
017
018import org.apache.log4j.Logger;
019import org.kuali.rice.core.api.impex.xml.XmlIngestionException;
020import org.kuali.rice.coreservice.api.style.Style;
021import org.kuali.rice.coreservice.api.style.StyleService;
022import org.kuali.rice.core.api.util.xml.XmlException;
023import org.kuali.rice.core.api.util.xml.XmlJotter;
024import org.kuali.rice.coreservice.api.CoreServiceApiServiceLocator;
025import org.kuali.rice.edl.impl.EDLXmlUtils;
026import org.kuali.rice.edl.impl.bo.EDocLiteAssociation;
027import org.kuali.rice.edl.impl.bo.EDocLiteDefinition;
028import org.kuali.rice.edl.impl.service.EDocLiteService;
029import org.kuali.rice.edl.impl.service.EdlServiceLocator;
030import org.kuali.rice.kew.rule.bo.RuleAttribute;
031import org.kuali.rice.kew.service.KEWServiceLocator;
032import org.w3c.dom.Document;
033import org.w3c.dom.Element;
034import org.w3c.dom.Node;
035import org.w3c.dom.NodeList;
036
037import javax.xml.parsers.DocumentBuilder;
038import javax.xml.xpath.XPath;
039import javax.xml.xpath.XPathConstants;
040import javax.xml.xpath.XPathExpressionException;
041import javax.xml.xpath.XPathFactory;
042import java.io.InputStream;
043import java.util.ArrayList;
044import java.util.Collection;
045import java.util.Iterator;
046
047
048/**
049 * An XML parser which parses EDocLite definitions.
050 *
051 * @author Kuali Rice Team (rice.collab@kuali.org)
052 */
053public class EDocLiteXmlParser {
054
055        private static final Logger LOG = Logger.getLogger(EDocLiteXmlParser.class);
056
057    public static void loadXml(InputStream inputStream, String principalId) {
058        DocumentBuilder db = EDLXmlUtils.getDocumentBuilder();
059        XPath xpath = XPathFactory.newInstance().newXPath();
060        Document doc;
061        // parse and save EDocLiteDefinition, EDocLiteStyle, or EDocLiteAssociation xml from to-be-determined XML format
062        //try {
063        try {
064            doc = db.parse(inputStream);
065        } catch (Exception e) {
066            throw generateException("Error parsing EDocLite XML file", e);
067        }
068            /*try {
069                LOG.info(XmlHelper.writeNode(doc.getFirstChild(), true));
070            } catch (TransformerException e) {
071                LOG.warn("Error displaying document");
072            }*/
073
074            NodeList edls;
075            try {
076                edls = (NodeList) xpath.evaluate("//edoclite", doc.getFirstChild(), XPathConstants.NODESET);
077            } catch (XPathExpressionException e) {
078                throw generateException("Error evaluating XPath expression", e);
079            }
080
081            for (int i = 0; i < edls.getLength(); i++) {
082                Node edl = edls.item(i);
083                NodeList children = edl.getChildNodes();
084                for (int j = 0; j < children.getLength(); j++) {
085                    Node node = children.item(j);
086                    /*try {
087                        LOG.info(XmlHelper.writeNode(node, true));
088                    } catch (TransformerException te) {
089                        LOG.warn("Error displaying node");
090                    }*/
091                    if (node.getNodeType() == Node.ELEMENT_NODE) {
092                        Element e = (Element) node;
093                        if ("style".equals(node.getNodeName())) {
094                            LOG.debug("Digesting EDocLiteStyle: " + e.getAttribute("name"));
095                            Style style = parseStyle(e);
096                            getStyleService().saveStyle(style);
097                        } else if ("edl".equals(node.getNodeName())) {
098                            LOG.debug("Digesting EDocLiteDefinition: " + e.getAttribute("name"));
099                            EDocLiteDefinition def = parseEDocLiteDefinition(e);
100                            getEDLService().saveEDocLiteDefinition(def);
101                        } else if ("association".equals(node.getNodeName())) {
102                            LOG.debug("Digesting EDocLiteAssociation: " + e.getAttribute("name"));
103                            EDocLiteAssociation assoc = parseEDocLiteAssociation(e);
104                            getEDLService().saveEDocLiteAssociation(assoc);
105                        } else {
106                            // LOG.debug("Unrecognized element '" + node.getNodeName() + "' in EDocLite XML doc");
107                        }
108                    }
109                }
110            }
111        //} catch (Exception e) {
112        //    throw generateException("Error parsing EDocLite XML file", e);
113        //}
114    }
115
116    private static XmlIngestionException generateException(String error, Throwable cause) {
117        throw new XmlIngestionException(error, cause);
118    }
119
120    /**
121     * Parses an EDocLiteAssocation
122     *
123     * @param e
124     *            element to parse
125     * @return an EDocLiteAssocation
126     */
127    private static EDocLiteAssociation parseEDocLiteAssociation(Element e) {
128        String docType = EDLXmlUtils.getChildElementTextValue(e, "docType");
129        if (docType == null) {
130            throw generateMissingChildException("association", "docType");
131        }
132        EDocLiteAssociation assoc = new EDocLiteAssociation();
133        assoc.setEdlName(docType);
134        assoc.setDefinition(EDLXmlUtils.getChildElementTextValue(e, "definition"));
135        assoc.setStyle(EDLXmlUtils.getChildElementTextValue(e, "style"));
136        assoc.setActiveInd(Boolean.valueOf(EDLXmlUtils.getChildElementTextValue(e, "active")));
137        return assoc;
138    }
139
140    /**
141     * Parses an EDocLiteStyle
142     *
143     * @param e
144     *            element to parse
145     * @return an EDocLiteStyle
146     */
147    private static Style parseStyle(Element e) {
148        String name = e.getAttribute("name");
149        if (name == null || name.length() == 0) {
150            throw generateMissingAttribException("style", "name");
151        }
152        Style.Builder styleBuilder = Style.Builder.create(name);
153        Element stylesheet = null;
154        NodeList children = e.getChildNodes();
155        for (int i = 0; i < children.getLength(); i++) {
156            Node child = children.item(i);
157            /*
158             * LOG.debug("NodeName: " + child.getNodeName()); LOG.debug("LocalName: " + child.getLocalName()); LOG.debug("Prefix: " + child.getPrefix()); LOG.debug("NS URI: " + child.getNamespaceURI());
159             */
160            if (child.getNodeType() == Node.ELEMENT_NODE && "xsl:stylesheet".equals(child.getNodeName())) {
161                stylesheet = (Element) child;
162                break;
163            }
164        }
165        if (stylesheet == null) {
166            throw generateMissingChildException("style", "xsl:stylesheet");
167        }
168        try {
169            styleBuilder.setXmlContent(XmlJotter.jotNode(stylesheet, true));
170        } catch (XmlException te) {
171            throw generateSerializationException("style", te);
172        }
173        return styleBuilder.build();
174    }
175
176    /**
177     * Parses an EDocLiteDefinition
178     *
179     * @param e
180     *            element to parse
181     * @return an EDocLiteDefinition
182     */
183    private static EDocLiteDefinition parseEDocLiteDefinition(Element e) {
184        EDocLiteDefinition def = new EDocLiteDefinition();
185        String name = e.getAttribute("name");
186        if (name == null || name.length() == 0) {
187            throw generateMissingAttribException(EDLXmlUtils.EDL_E, "name");
188        }
189        def.setName(name);
190
191        // do some validation to ensure that any attributes referenced actually exist
192        // blow up if there is a problem
193
194        XPath xpath = XPathFactory.newInstance().newXPath();
195        NodeList fields;
196        try {
197            fields = (NodeList) xpath.evaluate("fieldDef", e, XPathConstants.NODESET);
198        } catch (XPathExpressionException xpee) {
199            throw new XmlIngestionException("Invalid EDocLiteDefinition", xpee);
200        }
201
202        if (fields != null) {
203            Collection invalidAttributes = new ArrayList(5);
204            for (int i = 0; i < fields.getLength(); i++) {
205                Node node = (Node) fields.item(i);
206                // they should all be Element...
207                if (node instanceof Element) {
208                    Element field = (Element) node;
209                    // rely on XML validation to ensure this is present
210                    String fieldName = field.getAttribute("name");
211                    String attribute = field.getAttribute("attributeName");
212                    if (attribute != null && attribute.length() > 0) {
213                        RuleAttribute ruleAttrib = KEWServiceLocator.getRuleAttributeService().findByName(attribute);
214                        if (ruleAttrib == null) {
215                            LOG.error("Invalid attribute referenced in EDocLite definition: " + attribute);
216                            invalidAttributes.add("Attribute '" + attribute + "' referenced in field '" + fieldName + "' not found");
217                        }
218                    }
219                }
220            }
221            if (invalidAttributes.size() > 0) {
222                LOG.error("Invalid attributes referenced in EDocLite definition");
223                StringBuffer message = new StringBuffer("EDocLite definition contains references to non-existent attributes;\n");
224                Iterator it = invalidAttributes.iterator();
225                while (it.hasNext()) {
226                    message.append(it.next());
227                    message.append("\n");
228                }
229                throw new XmlIngestionException(message.toString());
230            }
231        }
232
233        try {
234            def.setXmlContent(XmlJotter.jotNode(e, true));
235        } catch (XmlException te) {
236            throw generateSerializationException(EDLXmlUtils.EDL_E, te);
237        }
238        return def;
239    }
240
241    private static XmlIngestionException generateMissingAttribException(String element, String attrib) {
242        return generateException("EDocLite '" + element + "' element must contain a '" + attrib + "' attribute", null);
243    }
244
245    private static XmlIngestionException generateMissingChildException(String element, String child) {
246        return generateException("EDocLite '" + element + "' element must contain a '" + child + "' child element", null);
247    }
248
249    private static XmlIngestionException generateSerializationException(String element, XmlException cause) {
250        return generateException("Error serializing EDocLite '" + element + "' element", cause);
251    }
252
253    private static EDocLiteService getEDLService() {
254        return EdlServiceLocator.getEDocLiteService();
255    }
256    
257    private static StyleService getStyleService() {
258        return CoreServiceApiServiceLocator.getStyleService();
259    }
260}