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.edl.impl.service.impl; 017 018import org.apache.logging.log4j.Logger; 019import org.apache.logging.log4j.LogManager; 020import org.kuali.rice.core.api.config.property.ConfigContext; 021import org.kuali.rice.core.api.impex.ExportDataSet; 022import org.kuali.rice.core.api.impex.xml.XmlIngestionException; 023import org.kuali.rice.coreservice.api.style.StyleService; 024import org.kuali.rice.core.api.util.xml.XmlException; 025import org.kuali.rice.core.api.util.xml.XmlJotter; 026import org.kuali.rice.edl.impl.EDLController; 027import org.kuali.rice.edl.impl.EDLControllerFactory; 028import org.kuali.rice.edl.impl.EDLGlobalConfig; 029import org.kuali.rice.edl.impl.EDLGlobalConfigFactory; 030import org.kuali.rice.edl.impl.EDLXmlUtils; 031import org.kuali.rice.edl.impl.bo.EDocLiteAssociation; 032import org.kuali.rice.edl.impl.bo.EDocLiteDefinition; 033import org.kuali.rice.edl.impl.dao.EDocLiteDAO; 034import org.kuali.rice.edl.impl.service.EDocLiteService; 035import org.kuali.rice.edl.impl.xml.EDocLiteXmlParser; 036import org.kuali.rice.edl.impl.xml.export.EDocLiteXmlExporter; 037import org.kuali.rice.kew.api.WorkflowRuntimeException; 038import org.kuali.rice.kew.exception.WorkflowServiceErrorException; 039import org.kuali.rice.kew.exception.WorkflowServiceErrorImpl; 040import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue; 041import org.kuali.rice.kew.rule.bo.RuleAttribute; 042import org.kuali.rice.kew.service.KEWServiceLocator; 043import org.kuali.rice.kew.api.KewApiConstants; 044import org.w3c.dom.Document; 045import org.w3c.dom.Element; 046import org.w3c.dom.Node; 047import org.w3c.dom.NodeList; 048import org.xml.sax.InputSource; 049 050import javax.xml.parsers.DocumentBuilderFactory; 051import javax.xml.transform.Templates; 052import javax.xml.transform.TransformerConfigurationException; 053import javax.xml.xpath.XPath; 054import javax.xml.xpath.XPathConstants; 055import javax.xml.xpath.XPathExpressionException; 056import javax.xml.xpath.XPathFactory; 057import java.io.InputStream; 058import java.io.StringReader; 059import java.util.ArrayList; 060import java.util.Collection; 061import java.util.Iterator; 062import java.util.List; 063import java.util.concurrent.atomic.AtomicReference; 064 065/** 066 * DAO-based EDocLiteService implementation 067 * 068 * @author Kuali Rice Team (rice.collab@kuali.org) 069 */ 070public class EDocLiteServiceImpl implements EDocLiteService { 071 private static final Logger LOG = LogManager.getLogger(EDocLiteServiceImpl.class); 072 073 private final AtomicReference<EDLGlobalConfig> edlGlobalConfig = new AtomicReference<EDLGlobalConfig>(null); 074 /** 075 * The Spring-wired DAO bean 076 */ 077 private EDocLiteDAO dao; 078 /** 079 * Spring wired StyleService bean 080 */ 081 private StyleService styleService; 082 083 // ---- Spring DAO setters 084 085 public void setEDocLiteDAO(EDocLiteDAO dao) { 086 this.dao = dao; 087 } 088 089 public EDLController getEDLControllerUsingEdlName(String edlName) { 090 EDocLiteAssociation edlAssociation = this.dao.getEDocLiteAssociation(edlName); 091 if (edlAssociation == null) { 092 throw new WorkflowRuntimeException("No document association active for EDL: " + edlName); 093 } 094 initEDLGlobalConfig(); 095 return EDLControllerFactory.createEDLController(edlAssociation, edlGlobalConfig.get()); 096 } 097 098 public EDLController getEDLControllerUsingDocumentId(String documentId) { 099 DocumentRouteHeaderValue document = KEWServiceLocator.getRouteHeaderService().getRouteHeader(documentId); 100 String edlName = document.getAppDocId();//components working with workflow docs will need to know this, perhaps through a document utils. 101 if (edlName == null) { 102 edlName = document.getDocumentType().getName(); 103 } 104 EDocLiteAssociation edlAssociation = this.dao.getEDocLiteAssociation(edlName); 105 if (edlAssociation == null) { 106 throw new WorkflowRuntimeException("No document association active for EDL: " + edlName); 107 } 108 initEDLGlobalConfig(); 109 return EDLControllerFactory.createEDLController(edlAssociation, edlGlobalConfig.get(), document); 110 } 111 112 @Override 113 public void initEDLGlobalConfig() { 114 edlGlobalConfig.compareAndSet(null, getEDLGlobalConfig()); 115 } 116 117 private EDLGlobalConfig getEDLGlobalConfig() { 118 try { 119 return EDLGlobalConfigFactory.createEDLGlobalConfig(ConfigContext.getCurrentContextConfig().getEDLConfigLocation()); 120 } catch (Exception e) { 121 throw new WorkflowRuntimeException(e); 122 } 123 } 124 125 public Document getDefinitionXml(EDocLiteAssociation edlAssociation) { 126 try { 127 Document def = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new InputSource( 128 new StringReader(getEDocLiteDefinition(edlAssociation.getDefinition()).getXmlContent()))); 129 return def; 130 } catch (Exception e) { 131 throw new WorkflowRuntimeException("Caught exception parsing EDL definition " + edlAssociation.getDefinition(), e); 132 } 133 } 134 135 private static XmlIngestionException generateException(String error, Throwable cause) { 136 return new XmlIngestionException(error, cause); 137 } 138 139 private static XmlIngestionException generateMissingAttribException(String element, String attrib) { 140 return generateException("EDocLite '" + element + "' element must contain a '" + attrib + "' attribute", null); 141 } 142 143 private static XmlIngestionException generateMissingChildException(String element, String child) { 144 return generateException("EDocLite '" + element + "' element must contain a '" + child + "' child element", null); 145 } 146 147 private static XmlIngestionException generateSerializationException(String element, XmlException cause) { 148 return generateException("Error serializing EDocLite '" + element + "' element", cause); 149 } 150 151 /** 152 * Parses an arbitrary XML stream 153 * 154 * @param stream 155 * stream containing XML doc content 156 * @return parsed Document object 157 */ 158 private static Document parse(InputStream stream) { 159 try { 160 return DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(stream); 161 } catch (Exception e) { 162 WorkflowServiceErrorException wsee = new WorkflowServiceErrorException("Error parsing EDocLite XML file", new WorkflowServiceErrorImpl("Error parsing XML file.", KewApiConstants.XML_FILE_PARSE_ERROR)); 163 wsee.initCause(e); 164 throw wsee; 165 } 166 } 167 168 /** 169 * Parses an EDocLiteAssocation 170 * 171 * @param e 172 * element to parse 173 * @return an EDocLiteAssocation 174 */ 175 private static EDocLiteAssociation parseEDocLiteAssociation(Element e) { 176 String docType = EDLXmlUtils.getChildElementTextValue(e, "docType"); 177 if (docType == null) { 178 throw generateMissingChildException("association", "docType"); 179 } 180 EDocLiteAssociation assoc = new EDocLiteAssociation(); 181 assoc.setEdlName(docType); 182 assoc.setDefinition(EDLXmlUtils.getChildElementTextValue(e, "definition")); 183 assoc.setStyle(EDLXmlUtils.getChildElementTextValue(e, "style")); 184 assoc.setActiveInd(Boolean.valueOf(EDLXmlUtils.getChildElementTextValue(e, "active"))); 185 return assoc; 186 } 187 188 /** 189 * Parses an EDocLiteDefinition 190 * 191 * @param e 192 * element to parse 193 * @return an EDocLiteDefinition 194 */ 195 private static EDocLiteDefinition parseEDocLiteDefinition(Element e) { 196 EDocLiteDefinition def = new EDocLiteDefinition(); 197 String name = e.getAttribute("name"); 198 if (name == null || name.length() == 0) { 199 throw generateMissingAttribException(EDLXmlUtils.EDL_E, "name"); 200 } 201 def.setName(name); 202 203 // do some validation to ensure that any attributes referenced actually exist 204 // blow up if there is a problem 205 206 XPath xpath = XPathFactory.newInstance().newXPath(); 207 NodeList fields; 208 try { 209 fields = (NodeList) xpath.evaluate("fieldDef", e, XPathConstants.NODESET); 210 } catch (XPathExpressionException xpee) { 211 throw new RuntimeException("Invalid EDocLiteDefinition", xpee); 212 } 213 214 if (fields != null) { 215 Collection invalidAttributes = new ArrayList(5); 216 for (int i = 0; i < fields.getLength(); i++) { 217 Node node = (Node) fields.item(i); 218 // they should all be Element... 219 if (node instanceof Element) { 220 Element field = (Element) node; 221 // rely on XML validation to ensure this is present 222 String fieldName = field.getAttribute("name"); 223 String attribute = field.getAttribute("attributeName"); 224 if (attribute != null && attribute.length() > 0) { 225 RuleAttribute ruleAttrib = KEWServiceLocator.getRuleAttributeService().findByName(attribute); 226 if (ruleAttrib == null) { 227 LOG.error("Invalid attribute referenced in EDocLite definition: " + attribute); 228 invalidAttributes.add("Attribute '" + attribute + "' referenced in field '" + fieldName + "' not found"); 229 } 230 } 231 } 232 } 233 if (invalidAttributes.size() > 0) { 234 LOG.error("Invalid attributes referenced in EDocLite definition"); 235 StringBuffer message = new StringBuffer("EDocLite definition contains references to non-existent attributes;\n"); 236 Iterator it = invalidAttributes.iterator(); 237 while (it.hasNext()) { 238 message.append(it.next()); 239 message.append("\n"); 240 } 241 throw new RuntimeException(message.toString()); 242 } 243 } 244 245 try { 246 def.setXmlContent(XmlJotter.jotNode(e, true)); 247 } catch (XmlException te) { 248 throw generateSerializationException(EDLXmlUtils.EDL_E, te); 249 } 250 return def; 251 } 252 253 // ---- helper methods 254 255 public void saveEDocLiteDefinition(EDocLiteDefinition data) { 256 EDocLiteDefinition existingData = getEDocLiteDefinition(data.getName()); 257 if (existingData != null) { 258 existingData.setActiveInd(Boolean.FALSE); 259 dao.saveEDocLiteDefinition(existingData); 260 } 261 // if not specified (from xml), mark it as active 262 if (data.getActiveInd() == null) { 263 data.setActiveInd(Boolean.TRUE); 264 } 265 dao.saveEDocLiteDefinition(data); 266 } 267 268 public void saveEDocLiteAssociation(EDocLiteAssociation assoc) { 269 EDocLiteAssociation existingData = getEDocLiteAssociation(assoc.getEdlName()); 270 if (existingData != null) { 271 existingData.setActiveInd(Boolean.FALSE); 272 dao.saveEDocLiteAssociation(existingData); 273 } 274 // if not specified (from xml), mark it as active 275 if (assoc.getActiveInd() == null) { 276 assoc.setActiveInd(Boolean.TRUE); 277 } 278 dao.saveEDocLiteAssociation(assoc); 279 } 280 281 // ---- EDocLiteService interface implementation 282 283 public void saveEDocLiteDefinition(InputStream xml) { 284 // convert xml to EDocLiteDefinition 285 EDocLiteDefinition data = parseEDocLiteDefinition(parse(xml).getDocumentElement()); 286 saveEDocLiteDefinition(data); 287 } 288 289 public void saveEDocLiteAssociation(InputStream xml) { 290 // convert xml to EDocLiteAssociation 291 EDocLiteAssociation assoc = parseEDocLiteAssociation(parse(xml).getDocumentElement()); 292 saveEDocLiteAssociation(assoc); 293 } 294 295 public EDocLiteDefinition getEDocLiteDefinition(String definitionName) { 296 return dao.getEDocLiteDefinition(definitionName); 297 } 298 299 public EDocLiteAssociation getEDocLiteAssociation(String docTypeName) { 300 return dao.getEDocLiteAssociation(docTypeName); 301 } 302 303 public List getEDocLiteDefinitions() { 304 return dao.getEDocLiteDefinitions(); 305 } 306 307 public List getEDocLiteAssociations() { 308 return dao.getEDocLiteAssociations(); 309 } 310 311 public Templates getStyleAsTranslet(String name) throws TransformerConfigurationException { 312 if (name == null || "null".equals(name)) { //"name".equals(name) - from a null value in the lookupable 313 name = "Default"; 314 } 315 316 return styleService.getStyleAsTranslet(name); 317 } 318 319 public List search(EDocLiteAssociation edocLite) { 320 return this.dao.search(edocLite); 321 } 322 323 public EDocLiteAssociation getEDocLiteAssociation(Long associationId) { 324 return dao.getEDocLiteAssociation(associationId); 325 } 326 327 // ---- XmlLoader interface implementation 328 329 public void loadXml(InputStream inputStream, String principalId) { 330 EDocLiteXmlParser.loadXml(inputStream, principalId); 331 } 332 333 // ---- XmlExporter interface implementation 334 public org.jdom2.Element export(ExportDataSet dataSet) { 335 return new EDocLiteXmlExporter().export(dataSet); 336 } 337 338 @Override 339 public boolean supportPrettyPrint() { 340 return false; 341 } 342 343 public void setStyleService(StyleService styleService) { 344 this.styleService = styleService; 345 } 346 347}