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.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}