001 /**
002 * Copyright 2005-2013 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 */
016 package org.kuali.rice.edl.impl.service.impl;
017
018 import org.apache.log4j.Logger;
019 import org.kuali.rice.core.api.config.property.ConfigContext;
020 import org.kuali.rice.core.api.impex.ExportDataSet;
021 import org.kuali.rice.core.api.impex.xml.XmlIngestionException;
022 import org.kuali.rice.coreservice.api.style.StyleService;
023 import org.kuali.rice.core.api.util.xml.XmlException;
024 import org.kuali.rice.core.api.util.xml.XmlJotter;
025 import org.kuali.rice.edl.impl.EDLController;
026 import org.kuali.rice.edl.impl.EDLControllerFactory;
027 import org.kuali.rice.edl.impl.EDLGlobalConfig;
028 import org.kuali.rice.edl.impl.EDLGlobalConfigFactory;
029 import org.kuali.rice.edl.impl.EDLXmlUtils;
030 import org.kuali.rice.edl.impl.bo.EDocLiteAssociation;
031 import org.kuali.rice.edl.impl.bo.EDocLiteDefinition;
032 import org.kuali.rice.edl.impl.dao.EDocLiteDAO;
033 import org.kuali.rice.edl.impl.service.EDocLiteService;
034 import org.kuali.rice.edl.impl.xml.EDocLiteXmlParser;
035 import org.kuali.rice.edl.impl.xml.export.EDocLiteXmlExporter;
036 import org.kuali.rice.kew.api.WorkflowRuntimeException;
037 import org.kuali.rice.kew.exception.WorkflowServiceErrorException;
038 import org.kuali.rice.kew.exception.WorkflowServiceErrorImpl;
039 import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue;
040 import org.kuali.rice.kew.rule.bo.RuleAttribute;
041 import org.kuali.rice.kew.service.KEWServiceLocator;
042 import org.kuali.rice.kew.api.KewApiConstants;
043 import org.w3c.dom.Document;
044 import org.w3c.dom.Element;
045 import org.w3c.dom.Node;
046 import org.w3c.dom.NodeList;
047 import org.xml.sax.InputSource;
048
049 import javax.xml.parsers.DocumentBuilderFactory;
050 import javax.xml.transform.Templates;
051 import javax.xml.transform.TransformerConfigurationException;
052 import javax.xml.xpath.XPath;
053 import javax.xml.xpath.XPathConstants;
054 import javax.xml.xpath.XPathExpressionException;
055 import javax.xml.xpath.XPathFactory;
056 import java.io.InputStream;
057 import java.io.StringReader;
058 import java.util.ArrayList;
059 import java.util.Collection;
060 import java.util.Iterator;
061 import java.util.List;
062 import java.util.concurrent.atomic.AtomicReference;
063
064 /**
065 * DAO-based EDocLiteService implementation
066 *
067 * @author Kuali Rice Team (rice.collab@kuali.org)
068 */
069 public 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 }