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}