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