001/**
002 * Copyright 2005-2015 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.kew.routeheader;
017
018import java.io.BufferedReader;
019import java.io.IOException;
020import java.io.ObjectInputStream;
021import java.io.Serializable;
022import java.io.StringReader;
023
024import javax.xml.parsers.DocumentBuilder;
025import javax.xml.parsers.DocumentBuilderFactory;
026import javax.xml.parsers.ParserConfigurationException;
027
028import org.kuali.rice.core.api.exception.RiceRuntimeException;
029import org.kuali.rice.kew.api.document.InvalidDocumentContentException;
030import org.kuali.rice.kew.engine.RouteContext;
031import org.kuali.rice.kew.api.KewApiConstants;
032import org.w3c.dom.Document;
033import org.w3c.dom.Element;
034import org.w3c.dom.Node;
035import org.w3c.dom.NodeList;
036import org.xml.sax.InputSource;
037import org.xml.sax.SAXException;
038
039
040/**
041 * Standard implementation of {@link DocumentContent} which nows hows to parse a
042 * String that it's constructed with into content with the application,
043 * attribute, and searchable content sections.
044 *
045 * @author Kuali Rice Team (rice.collab@kuali.org)
046 */
047public class StandardDocumentContent implements DocumentContent, Serializable {
048
049        private static final long serialVersionUID = -3189330007364191220L;
050        
051        private static final String LEGACY_FLEXDOC_ELEMENT = "flexdoc";
052
053        private String docContent;
054
055        private transient Document document;
056
057        private transient Element applicationContent;
058
059        private transient Element attributeContent;
060
061        private transient Element searchableContent;
062
063        private RouteContext routeContext;
064
065        public StandardDocumentContent(String docContent) {
066                this(docContent, null);
067        }
068
069        public StandardDocumentContent(String docContent, RouteContext routeContext) {
070                this.routeContext = routeContext;
071                initialize(docContent, routeContext);
072        }
073
074        private void initialize(String docContent, RouteContext routeContext) {
075                if (org.apache.commons.lang.StringUtils.isEmpty(docContent)) {
076                        this.docContent = "";
077                        this.document = null;
078                } else {
079                        try {
080                                this.docContent = docContent;
081                                this.document = parseDocContent(docContent);
082                                extractElements(this.document);
083                        } catch (IOException e) {
084                                throw new InvalidDocumentContentException("I/O Error when attempting to parse document content.", e);
085                        } catch (SAXException e) {
086                                throw new InvalidDocumentContentException("XML parse error when attempting to parse document content.", e);
087                        } catch (ParserConfigurationException e) {
088                                throw new RiceRuntimeException("XML parser configuration error when attempting to parse document content.", e);
089                        }
090                }
091        }
092
093        private Document parseDocContent(String docContent) throws IOException, SAXException, ParserConfigurationException {
094                DocumentBuilder documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
095                return documentBuilder.parse(new InputSource(new BufferedReader(new StringReader(docContent))));
096        }
097
098        private void extractElements(Document document) {
099                // this handles backward compatibility in document content
100                if (!document.getDocumentElement().getNodeName().equals(KewApiConstants.DOCUMENT_CONTENT_ELEMENT)) {
101                        // if the root element is the flexdoc element (pre Workflow 2.0)
102                        // then designate that as attribute content
103                        if (document.getDocumentElement().getNodeName().equals(LEGACY_FLEXDOC_ELEMENT)) {
104                                attributeContent = document.getDocumentElement();
105                        } else {
106                                applicationContent = document.getDocumentElement();
107                        }
108                } else {
109                        NodeList nodes = document.getDocumentElement().getChildNodes();
110                        for (int index = 0; index < nodes.getLength(); index++) {
111                                Node node = nodes.item(index);
112                                if (node.getNodeType() == Node.ELEMENT_NODE && node.getNodeName().equals(KewApiConstants.APPLICATION_CONTENT_ELEMENT)) {
113                                        int numChildElements = 0;
114                                        for (int childIndex = 0; childIndex < node.getChildNodes().getLength(); childIndex++) {
115                                                Node child = (Node) node.getChildNodes().item(childIndex);
116                                                if (child.getNodeType() == Node.ELEMENT_NODE) {
117                                                        applicationContent = (Element) child;
118                                                        numChildElements++;
119                                                }
120                                        }
121                                        // TODO can we have application content without a root node?
122                                        if (numChildElements > 1) {
123                                                applicationContent = (Element) node;
124                                        }
125                                } else if (node.getNodeType() == Node.ELEMENT_NODE && node.getNodeName().equals(KewApiConstants.ATTRIBUTE_CONTENT_ELEMENT)) {
126                                        attributeContent = (Element) node;
127                                } else if (node.getNodeType() == Node.ELEMENT_NODE && node.getNodeName().equals(KewApiConstants.SEARCHABLE_CONTENT_ELEMENT)) {
128                                        searchableContent = (Element) node;
129                                }
130                        }
131                }
132        }
133
134        public Element getApplicationContent() {
135                return applicationContent;
136        }
137
138        public Element getAttributeContent() {
139                return attributeContent;
140        }
141
142        public String getDocContent() {
143                return docContent;
144        }
145
146        public Document getDocument() {
147                return document;
148        }
149
150        public Element getSearchableContent() {
151                return searchableContent;
152        }
153
154        public RouteContext getRouteContext() {
155                return this.routeContext;
156        }
157
158        private void readObject(ObjectInputStream ais) throws IOException, ClassNotFoundException {
159                ais.defaultReadObject();
160                initialize(this.docContent, this.routeContext);
161        }
162
163}