001: /*
002: * Copyright 2005-2007 The Kuali Foundation.
003: *
004: *
005: * Licensed under the Educational Community License, Version 1.0 (the "License");
006: * you may not use this file except in compliance with the License.
007: * You may obtain a copy of the License at
008: *
009: * http://www.opensource.org/licenses/ecl1.php
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package edu.iu.uis.eden.doctype;
018:
019: import java.io.InputStream;
020: import java.util.ArrayList;
021: import java.util.Collection;
022: import java.util.Iterator;
023: import java.util.List;
024:
025: import org.jdom.Element;
026:
027: import edu.iu.uis.eden.KEWServiceLocator;
028: import edu.iu.uis.eden.WorkflowServiceErrorException;
029: import edu.iu.uis.eden.WorkflowServiceErrorImpl;
030: import edu.iu.uis.eden.clientapp.vo.DocumentTypeVO;
031: import edu.iu.uis.eden.doctype.dao.DocumentTypeDAO;
032: import edu.iu.uis.eden.export.ExportDataSet;
033: import edu.iu.uis.eden.routetemplate.RuleAttribute;
034: import edu.iu.uis.eden.server.BeanConverter;
035: import edu.iu.uis.eden.user.WorkflowUser;
036: import edu.iu.uis.eden.xml.DocumentTypeXmlParser;
037: import edu.iu.uis.eden.xml.export.DocumentTypeXmlExporter;
038:
039: /**
040: * The standard implementation of the DocumentTypeService.
041: *
042: * @author rkirkend
043: */
044: public class DocumentTypeServiceImpl implements DocumentTypeService {
045:
046: private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger
047: .getLogger(DocumentTypeServiceImpl.class);
048: private static final String XML_FILE_PARSE_ERROR = "general.error.parsexml";
049:
050: public static final String DOCUMENT_TYPE_ID_CACHE_GROUP = "DocumentTypeId";
051: public static final String DOCUMENT_TYPE_NAME_CACHE_GROUP = "DocumentTypeName";
052:
053: public static final String DOCUMENT_TYPE_ID_CACHE_PREFIX = DOCUMENT_TYPE_ID_CACHE_GROUP
054: + ":";
055: public static final String DOCUMENT_TYPE_NAME_CACHE_PREFIX = DOCUMENT_TYPE_NAME_CACHE_GROUP
056: + ":";
057: public static final String CURRENT_ROOTS_IN_CACHE_KEY = "DocumentType:CurrentRootsInCache";
058:
059: private DocumentTypeDAO documentTypeDAO;
060:
061: public Collection find(DocumentType documentType,
062: String docTypeParentName, boolean climbHierarchy) {
063: DocumentType docTypeParent = this .findByName(docTypeParentName);
064: Collection documentTypes = getDocumentTypeDAO().find(
065: documentType, docTypeParent, climbHierarchy);
066: //since we're here put them in the cache
067: for (Iterator iter = documentTypes.iterator(); iter.hasNext();) {
068: insertIntoCache((DocumentType) iter.next());
069: }
070: return documentTypes;
071: }
072:
073: public DocumentType findById(Long documentTypeId) {
074: if (documentTypeId == null) {
075: return null;
076: }
077: DocumentType documentType = fetchFromCacheById(documentTypeId);
078: if (documentType == null) {
079: documentType = getDocumentTypeDAO().findByDocId(
080: documentTypeId);
081: insertIntoCache(documentType);
082: }
083: return documentType;
084: }
085:
086: public DocumentType findByDocumentId(Long documentId) {
087: if (documentId == null) {
088: return null;
089: }
090: Long documentTypeId = getDocumentTypeDAO()
091: .findDocumentTypeIdByDocumentId(documentId);
092: return findById(documentTypeId);
093: }
094:
095: public DocumentType findByName(String name) {
096: if (name == null) {
097: return null;
098: }
099: DocumentType documentType = fetchFromCacheByName(name);
100: if (documentType == null) {
101: documentType = getDocumentTypeDAO().findByName(name);
102: insertIntoCache(documentType);
103: }
104: return documentType;
105: }
106:
107: /**
108: * Fetches the DocumentType from the cache with the given document type name. If there is no entry in the cache for the given
109: * document type name, null is returned.
110: */
111: protected DocumentType fetchFromCacheByName(String documentTypeName) {
112: return (DocumentType) KEWServiceLocator.getCacheAdministrator()
113: .getFromCache(getNameCacheKey(documentTypeName));
114: }
115:
116: /**
117: * Fetches the DocumentType from the cache with the given document type id. If there is no entry in the cache for the given
118: * document type id, null is returned.
119: */
120: protected DocumentType fetchFromCacheById(Long documentTypeId) {
121: return (DocumentType) KEWServiceLocator.getCacheAdministrator()
122: .getFromCache(getIdCacheKey(documentTypeId));
123: }
124:
125: /**
126: * Returns the cache key for the given document type ID.
127: */
128: protected String getIdCacheKey(Long documentTypeId) {
129: return DOCUMENT_TYPE_ID_CACHE_PREFIX
130: + documentTypeId.toString();
131: }
132:
133: /**
134: * Returns the cache key for the given document type name.
135: */
136: protected String getNameCacheKey(String documentTypeName) {
137: return DOCUMENT_TYPE_NAME_CACHE_PREFIX + documentTypeName;
138: }
139:
140: /**
141: * Inserts the given DocumentType into the name and id caches. If the DocumentType is already in the cache,
142: * these entries should be overwritten.
143: *
144: * <p>If the given DocumentType does not represent the current version of the DocumentType then it
145: * should not be inserted into the name cache. This is because different versions of DocumentTypes have
146: * different IDs but they all have the same name. We want only the most recent version of the DocumentType
147: * to be cached by name.
148: */
149: protected void insertIntoCache(DocumentType documentType) {
150: if (documentType == null) {
151: return;
152: }
153: //don't cache by name if this isn't the current version
154: if (documentType.getCurrentInd().booleanValue()) {
155: KEWServiceLocator.getCacheAdministrator().putInCache(
156: getNameCacheKey(documentType.getName()),
157: documentType, DOCUMENT_TYPE_NAME_CACHE_GROUP);
158: }
159:
160: KEWServiceLocator.getCacheAdministrator().putInCache(
161: getIdCacheKey(documentType.getDocumentTypeId()),
162: documentType, DOCUMENT_TYPE_ID_CACHE_GROUP);
163: }
164:
165: /**
166: * Flushes all DocumentTypes from the cache.
167: */
168: protected void flushCache() {
169: // invalidate locally because if we're doing an upload of a document hierarchy we can't wait the 5 secs for this nodes cache
170: //to be accurate-the data going in the db depends on it being accurate now. This means the cache will be cleared multiple times
171: //over during an upload and the subsequent notification to this node.
172: LOG.info("clearing DocumentType cache because of local update");
173: KEWServiceLocator.getCacheAdministrator().flushGroup(
174: DOCUMENT_TYPE_ID_CACHE_GROUP);
175: KEWServiceLocator.getCacheAdministrator().flushGroup(
176: DOCUMENT_TYPE_NAME_CACHE_GROUP);
177: KEWServiceLocator.getCacheAdministrator().flushEntry(
178: CURRENT_ROOTS_IN_CACHE_KEY);
179: }
180:
181: public void clearCacheForAttributeUpdate(RuleAttribute ruleAttribute) {
182: List documentTypeAttributes = this .documentTypeDAO
183: .findDocumentTypeAttributes(ruleAttribute);
184: if (documentTypeAttributes.size() != 0) {
185: flushCache();
186: }
187: }
188:
189: public void versionAndSave(DocumentType documentType) {
190: try {
191: // 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
192: // not a create just throw and exception to be on the safe side
193: if (documentType.getDocumentTypeId() != null
194: && documentType.getLockVerNbr() != null) {
195: throw new RuntimeException(
196: "DocumentType configured for update and not versioning which we support");
197: }
198:
199: // flush the old document type from the cache so we get a
200: DocumentType oldDocumentType = findByName(documentType
201: .getName());
202: // reset the children on the oldDocumentType
203: //oldDocumentType.resetChildren();
204: Long existingDocTypeId = (oldDocumentType == null ? null
205: : oldDocumentType.getDocumentTypeId());
206: if (oldDocumentType != null
207: && existingDocTypeId.longValue() > 0) {
208: documentType.setPreviousVersionId(existingDocTypeId);
209: //TODO this used to be a query that I can't see being necessary if all our fetch code is working...
210: //delete if you're looking at this message in 2.4 release cycle
211: documentType.setVersion(new Integer(oldDocumentType
212: .getVersion().intValue() + 1));
213: oldDocumentType.setCurrentInd(Boolean.FALSE);
214: LOG.debug("Saving old document type Id "
215: + oldDocumentType.getDocumentTypeId()
216: + " name " + oldDocumentType.getName());
217: save(oldDocumentType, false);
218: }
219: documentType.setCurrentInd(Boolean.TRUE);
220:
221: save(documentType, false);
222: //attach the children to this new parent. cloning the children would probably be a better way to go here...
223: if (documentType.getPreviousVersion() != null) {
224: for (Iterator iterator = oldDocumentType
225: .getChildrenDocTypes().iterator(); iterator
226: .hasNext();) {
227: DocumentType child = (DocumentType) iterator.next();
228: child.setDocTypeParentId(documentType
229: .getDocumentTypeId());
230: save(child, false);
231: }
232: }
233: // initiate a save of this document type's parent document type, this will force a
234: // version check which should reveal (via an optimistic lock exception) whether or
235: // not there is a concurrent transaction
236: // which has modified the parent (and therefore made it non-current)
237: // be sure to get the parent doc type directly from the db and not from the cache
238: if (documentType.getDocTypeParentId() != null) {
239: DocumentType parent = getDocumentTypeDAO().findByDocId(
240: documentType.getDocTypeParentId());
241: save(parent, false);
242: }
243:
244: // finally, flush the cache and notify the rule cache of the DocumentType change
245: flushCache();
246: KEWServiceLocator.getRuleService()
247: .notifyCacheOfDocumentTypeChange(documentType);
248: } finally {
249: // the double flush here is necessary because of a series of events which occur inside of
250: // notifyCacheOfDocumentTypeChange, see the documentation inside that service method for
251: // more information on the problem. Esentially, the method ends up invoking methods on
252: // this service which re-cache document types, however the document types that get
253: // re-cached are ones pulled from the OJB cache that don't have the proper children
254: // on them
255: //
256: // also we flush in the finally block because if an exception is thrown then it's still possible
257: // the the "oldDocumentType" which was fetched from the cache has had it's dbLockVerNbr incremented
258: flushCache();
259: }
260: }
261:
262: protected void save(DocumentType documentType, boolean flushCache) {
263: getDocumentTypeDAO().save(documentType);
264: if (flushCache) {
265: // always clear the entire cache
266: flushCache();
267: KEWServiceLocator.getRuleService()
268: .notifyCacheOfDocumentTypeChange(documentType);
269: flushCache();
270: }
271: }
272:
273: public void save(DocumentType documentType) {
274: save(documentType, true);
275: }
276:
277: public DocumentTypeDAO getDocumentTypeDAO() {
278: return documentTypeDAO;
279: }
280:
281: public void setDocumentTypeDAO(DocumentTypeDAO documentTypeDAO) {
282: this .documentTypeDAO = documentTypeDAO;
283: }
284:
285: public DocumentTypeVO getDocumentTypeVO(Long documentTypeId) {
286: DocumentType docType = findById(documentTypeId);
287: return BeanConverter.convertDocumentType(docType);
288: }
289:
290: public DocumentTypeVO getDocumentTypeVO(String documentTypeName) {
291: DocumentType documentType = findByName(documentTypeName);
292: return BeanConverter.convertDocumentType(documentType);
293: }
294:
295: public synchronized List findAllCurrentRootDocuments() {
296: List currentRootsInCache = (List) KEWServiceLocator
297: .getCacheAdministrator().getFromCache(
298: CURRENT_ROOTS_IN_CACHE_KEY);
299: //we can do this because we whack the entire cache when a new document type comes into the picture.
300: if (currentRootsInCache == null) {
301: currentRootsInCache = getDocumentTypeDAO()
302: .findAllCurrentRootDocuments();
303: KEWServiceLocator.getCacheAdministrator().putInCache(
304: CURRENT_ROOTS_IN_CACHE_KEY, currentRootsInCache);
305: }
306: return currentRootsInCache;
307: }
308:
309: public List findAllCurrent() {
310: return getDocumentTypeDAO().findAllCurrent();
311: }
312:
313: public DocumentType findRootDocumentType(DocumentType docType) {
314: if (docType.getParentDocType() != null) {
315: return findRootDocumentType(docType.getParentDocType());
316: } else {
317: return docType;
318: }
319: }
320:
321: public void loadXml(InputStream inputStream, WorkflowUser user) {
322: DocumentTypeXmlParser parser = new DocumentTypeXmlParser();
323: try {
324: parser.parseDocumentTypes(inputStream);
325: } catch (Exception e) {
326: WorkflowServiceErrorException wsee = new WorkflowServiceErrorException(
327: "Error parsing documentType XML file",
328: new WorkflowServiceErrorImpl(
329: "Error parsing documentType XML file",
330: XML_FILE_PARSE_ERROR));
331: wsee.initCause(e);
332: throw wsee;
333: }
334: }
335:
336: public Element export(ExportDataSet dataSet) {
337: DocumentTypeXmlExporter exporter = new DocumentTypeXmlExporter();
338: return exporter.export(dataSet);
339: }
340:
341: public List getChildDocumentTypes(DocumentType documentType) {
342: List childDocumentTypes = new ArrayList();
343: List childIds = getDocumentTypeDAO().getChildDocumentTypeIds(
344: documentType.getDocumentTypeId());
345: for (Iterator iter = childIds.iterator(); iter.hasNext();) {
346: Long documentTypeId = (Long) iter.next();
347: childDocumentTypes.add(findById(documentTypeId));
348: }
349: return childDocumentTypes;
350: }
351:
352: }
|