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    }