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}