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.kew.doctype.service.impl;
017
018import org.apache.commons.collections.CollectionUtils;
019import org.jdom.Element;
020import org.kuali.rice.core.api.criteria.QueryByCriteria;
021import org.kuali.rice.core.api.criteria.QueryResults;
022import org.kuali.rice.core.api.impex.ExportDataSet;
023import org.kuali.rice.kew.doctype.bo.DocumentType;
024import org.kuali.rice.kew.doctype.dao.DocumentTypeDAO;
025import org.kuali.rice.kew.doctype.service.DocumentTypeService;
026import org.kuali.rice.kew.exception.WorkflowServiceErrorException;
027import org.kuali.rice.kew.exception.WorkflowServiceErrorImpl;
028import org.kuali.rice.kew.rule.RuleBaseValues;
029import org.kuali.rice.kew.xml.DocumentTypeXmlParser;
030import org.kuali.rice.kew.xml.export.DocumentTypeXmlExporter;
031import org.kuali.rice.krad.data.DataObjectService;
032import org.kuali.rice.krad.data.PersistenceOption;
033import org.kuali.rice.krad.util.KRADUtils;
034import org.springframework.beans.factory.annotation.Required;
035
036import java.io.InputStream;
037import java.util.ArrayList;
038import java.util.Collection;
039import java.util.Iterator;
040import java.util.List;
041
042import static org.kuali.rice.core.api.criteria.PredicateFactory.*;
043
044/**
045 * The standard implementation of the DocumentTypeService.
046 *
047 * @author Kuali Rice Team (rice.collab@kuali.org)
048 * This class does not support KEW REMOTE mode.
049 * KULRICE-7770 added an expicit check for this class in GlobalResourceDelegatingSpringCreator.java
050 */
051public class DocumentTypeServiceImpl implements DocumentTypeService {
052
053    private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(DocumentTypeServiceImpl.class);
054    protected static final String XML_FILE_PARSE_ERROR = "general.error.parsexml";
055
056    private DocumentTypeDAO documentTypeDAO;
057    private DataObjectService dataObjectService;
058
059    public Collection<DocumentType> find(DocumentType documentType, String docTypeParentName, boolean climbHierarchy) {
060       DocumentType docTypeParent = this.findByName(docTypeParentName);
061       return getDocumentTypeDAO().find(documentType, docTypeParent, climbHierarchy);
062    }
063
064    public DocumentType findById(String documentTypeId) {
065        if (documentTypeId == null) {
066                return null;
067        }
068        return getDataObjectService().find(DocumentType.class,documentTypeId);
069    }
070
071    public DocumentType findByDocumentId(String documentId) {
072        if (documentId == null) {
073                return null;
074        }
075
076        return documentTypeDAO.findDocumentTypeByDocumentId(documentId);
077    }
078
079    public DocumentType findByName(String name) {
080        return this.findByName(name, true);
081    }
082
083    public DocumentType findByNameCaseInsensitive(String name) {
084        return this.findByName(name, false);
085    }
086
087    /**
088     * 
089     * This method seaches for a DocumentType by document name.
090     * 
091     * @param name
092     * @param caseSensitive
093     * @return
094     */
095    private DocumentType findByName(String name, boolean caseSensitive) {
096        if (name == null) {
097                return null;
098        }
099        return documentTypeDAO.findByName(name, caseSensitive);
100    }
101
102    public DocumentType versionAndSave(DocumentType documentType) {
103        // at this point this save is designed to version the document type by creating an entire new record if this is going to be an update and
104        // not a create just throw and exception to be on the safe side
105        if (documentType.getDocumentTypeId() != null && documentType.getVersionNumber() != null) {
106            throw new RuntimeException("DocumentType configured for update and not versioning which we support");
107        }
108
109        // grab the old document. Don't Use Cached Version!
110        DocumentType oldDocumentType = findByName(documentType.getName());
111        // reset the children on the oldDocumentType
112        //oldDocumentType.resetChildren();
113        String existingDocTypeId = null;
114        if (oldDocumentType != null) {
115            existingDocTypeId = oldDocumentType.getDocumentTypeId();
116            // set version number on the new doc type using the max version from the database
117            Integer maxVersionNumber = documentTypeDAO.getMaxVersionNumber(documentType.getName());
118            documentType.setVersion((maxVersionNumber != null) ? new Integer(maxVersionNumber.intValue() + 1) : new Integer(0));
119            oldDocumentType.setCurrentInd(Boolean.FALSE);
120            if ( LOG.isInfoEnabled() ) {
121                LOG.info("Saving old document type Id " + oldDocumentType.getDocumentTypeId() + " name '" + oldDocumentType.getName() + "' (current = " + oldDocumentType.getCurrentInd() + ")");
122            }
123            oldDocumentType = save(oldDocumentType);
124        }
125        // check to see that no current documents exist in database
126        if (!CollectionUtils.isEmpty(documentTypeDAO.findAllCurrentByName(documentType.getName()))) {
127            String errorMsg = "Found invalid 'current' document with name '" + documentType.getName() + "'.  None should exist.";
128            LOG.error(errorMsg);
129            throw new RuntimeException(errorMsg);
130        }
131        // set up the previous current doc type on the new doc type
132        documentType.setPreviousVersionId(existingDocTypeId);
133        documentType.setCurrentInd(Boolean.TRUE);
134        documentType = save(documentType);
135        if ( LOG.isInfoEnabled() ) {
136            LOG.info("Saved current document type Id " + documentType.getDocumentTypeId() + " name '" + documentType.getName() + "' (current = " + documentType.getCurrentInd() + ")");
137        }
138        //attach the children to this new parent.  cloning the children would probably be a better way to go here...
139        if (existingDocTypeId != null) {
140            // documentType.getPreviousVersion() should not be null at this point
141            for (Iterator iterator = getChildDocumentTypes(existingDocTypeId).iterator(); iterator.hasNext();) {
142//                      for (Iterator iterator = oldDocumentType.getChildrenDocTypes().iterator(); iterator.hasNext();) {
143                DocumentType child = (DocumentType) iterator.next();
144                child.setDocTypeParentId(documentType.getDocumentTypeId());
145                save(child);
146                if ( LOG.isInfoEnabled() ) {
147                    LOG.info("Saved child document type Id " + child.getDocumentTypeId() + " name '" + child.getName() + "' (parent = " + child.getDocTypeParentId() + ", current = " + child.getCurrentInd() + ")");
148                }
149            }
150        }
151        // initiate a save of this document type's parent document type, this will force a
152        // version check which should reveal (via an optimistic lock exception) whether or
153        // not there is a concurrent transaction
154        // which has modified the parent (and therefore made it non-current)
155        // be sure to get the parent doc type directly from the db and not from the cache
156        if (documentType.getDocTypeParentId() != null) {
157            DocumentType parent = getDataObjectService().find(DocumentType.class,documentType.getDocTypeParentId());
158            save(parent);
159            if ( LOG.isInfoEnabled() ) {
160                LOG.info("Saved parent document type Id " + parent.getDocumentTypeId() + " name '" + parent.getName() + "' (current = " + parent.getCurrentInd() + ")");
161            }
162        }
163        return documentType;
164    }
165
166    public String findParentNameByName(String documentTypeName) {
167        return getDocumentTypeDAO().findParentNameByName(documentTypeName);
168    }
169
170    public DocumentType save(DocumentType documentType) {
171        // any time that we save a document type, let's grab an optimistic lock on it's parent document type, that's
172        // because perhaps another transaction created a new version of the parent doc type, if we don't optimistic
173        // lock and fail this transaction, then we will end up linking to a non-current parent version!
174        if (documentType.getParentId() != null) {
175            getDocumentTypeDAO().incrementOptimisticLock(documentType.getParentId());
176        }
177        return getDataObjectService().save(documentType, PersistenceOption.FLUSH);
178    }
179
180    public DocumentTypeDAO getDocumentTypeDAO() {
181        return documentTypeDAO;
182    }
183
184    public void setDocumentTypeDAO(DocumentTypeDAO documentTypeDAO) {
185        this.documentTypeDAO = documentTypeDAO;
186    }
187
188    public synchronized List findAllCurrentRootDocuments() {
189        QueryByCriteria.Builder builder = QueryByCriteria.Builder.create();
190        builder.setPredicates(
191                isNull("docTypeParentId"),
192                equal("currentInd", Boolean.TRUE)
193        );
194        QueryResults<DocumentType> results = getDataObjectService().findMatching(DocumentType.class, builder.build());
195        return results.getResults();
196    }
197
198    public List findAllCurrent() {
199        QueryByCriteria.Builder builder = QueryByCriteria.Builder.create();
200        builder.setPredicates(
201                equal("currentInd", Boolean.TRUE)
202        );
203        QueryResults<DocumentType> results = getDataObjectService().findMatching(DocumentType.class, builder.build());
204        return results.getResults();
205    }
206
207    public List<DocumentType> findPreviousInstances(String documentTypeName) {
208        QueryByCriteria.Builder builder = QueryByCriteria.Builder.create();
209        builder.setPredicates(
210                equal("name",documentTypeName),
211                equal("currentInd", Boolean.FALSE)
212        );
213        QueryResults<DocumentType> results = getDataObjectService().findMatching(DocumentType.class, builder.build());
214        return results.getResults();
215    }
216
217    public DocumentType findRootDocumentType(DocumentType docType) {
218        if (docType.getParentDocType() != null) {
219            return findRootDocumentType(docType.getParentDocType());
220        } else {
221            return docType;
222        }
223    }
224
225    public void loadXml(InputStream inputStream, String principalId) {
226        DocumentTypeXmlParser parser = new DocumentTypeXmlParser();
227        try {
228            parser.parseDocumentTypes(inputStream);
229        } catch (Exception e) {
230            WorkflowServiceErrorException wsee = new WorkflowServiceErrorException("Error parsing documentType XML file", new WorkflowServiceErrorImpl("Error parsing documentType XML file", XML_FILE_PARSE_ERROR));
231            wsee.initCause(e);
232            throw wsee;
233        }
234    }
235
236    public Element export(ExportDataSet dataSet) {
237        DocumentTypeXmlExporter exporter = new DocumentTypeXmlExporter();
238        return exporter.export(dataSet);
239    }
240    
241    @Override
242        public boolean supportPrettyPrint() {
243                return true;
244        }
245
246    public List getChildDocumentTypes(String documentTypeId) {
247        List childDocumentTypes = new ArrayList();
248        List childIds = getDocumentTypeDAO().getChildDocumentTypeIds(documentTypeId);
249        for (Iterator iter = childIds.iterator(); iter.hasNext();) {
250                        String childDocumentTypeId = (String) iter.next();
251                        childDocumentTypes.add(findById(childDocumentTypeId));
252                }
253        return childDocumentTypes;
254    }
255
256
257    public DataObjectService getDataObjectService() {
258        return dataObjectService;
259    }
260
261    @Required
262    public void setDataObjectService(DataObjectService dataObjectService) {
263        this.dataObjectService = dataObjectService;
264    }
265
266}