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.workgroup;
018:
019: import java.util.ArrayList;
020: import java.util.List;
021:
022: import org.apache.commons.lang.StringUtils;
023: import org.jdom.Element;
024: import org.kuali.workflow.workgroup.WorkgroupType;
025:
026: import edu.iu.uis.eden.EdenConstants;
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.WorkflowDocument;
031: import edu.iu.uis.eden.clientapp.vo.WorkflowAttributeDefinitionVO;
032: import edu.iu.uis.eden.clientapp.vo.WorkflowIdVO;
033: import edu.iu.uis.eden.exception.EdenUserNotFoundException;
034: import edu.iu.uis.eden.exception.WorkflowException;
035: import edu.iu.uis.eden.export.ExportDataSet;
036: import edu.iu.uis.eden.export.ExportFormat;
037: import edu.iu.uis.eden.routeheader.DocumentRouteHeaderValue;
038: import edu.iu.uis.eden.routeheader.Routable;
039: import edu.iu.uis.eden.routetemplate.RuleAttribute;
040: import edu.iu.uis.eden.user.Recipient;
041: import edu.iu.uis.eden.user.WorkflowUser;
042: import edu.iu.uis.eden.util.XmlHelper;
043: import edu.iu.uis.eden.web.session.UserSession;
044: import edu.iu.uis.eden.workgroup.dao.BaseWorkgroupDAO;
045:
046: /**
047: * A simple implementation of the WorkgroupRoutingService
048: *
049: * @author Eric Westfall
050: */
051: public class BaseWorkgroupRoutingService implements
052: WorkgroupRoutingService {
053:
054: private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger
055: .getLogger(BaseWorkgroupRoutingService.class);
056:
057: private static final String DEFAULT_DOCUMENT_TYPE = "EDENSERVICE-DOCS.WKGRPREQ";
058: private static String WORKGROUP_SEARCHABLE_ATTRIBUTE_NAME = "WorkgroupNameXMLSearchableAttribute";
059: private static final String ACTIVE_IND_BLANK = "workgroup.WorkgroupService.activeInd.empty";
060: private static final String NAME_BLANK = "workgroup.WorkgroupService.workgroupName.empty";
061: private static String WORKGROUP_LOCKED = "workgroup.WorkgroupService.workgroupInRoute";
062: private static String NO_MEMBERS = "workgroup.WorkgroupService.members.empty";
063: private static String NAME_EXISTS = "workgroup.WorkgroupService.workgroupName.exists";
064: private static String INVALID_TYPE = "workgroup.WorkgroupService.type.invalid";
065: private BaseWorkgroupDAO workgroupDAO;
066:
067: public String getDefaultDocumentTypeName() {
068: return DEFAULT_DOCUMENT_TYPE;
069: }
070:
071: public WorkflowDocument createWorkgroupDocument(
072: UserSession initiator, WorkgroupType workgroupType)
073: throws WorkflowException {
074: String documentType = DEFAULT_DOCUMENT_TYPE;
075: if (workgroupType != null) {
076: if (!StringUtils.isEmpty(workgroupType
077: .getDocumentTypeName())) {
078: documentType = workgroupType.getDocumentTypeName();
079: }
080: }
081: return new WorkflowDocument(new WorkflowIdVO(initiator
082: .getWorkflowUser().getWorkflowId()), documentType);
083: }
084:
085: public Workgroup findByDocumentId(Long documentId)
086: throws EdenUserNotFoundException {
087: BaseWorkgroup workgroup = getWorkgroupDAO().findByDocumentId(
088: documentId);
089: if (workgroup != null) {
090: workgroup.materializeMembers();
091: }
092: return workgroup;
093: }
094:
095: public void activateRoutedWorkgroup(Long documentId)
096: throws EdenUserNotFoundException {
097: LOG.debug("activating routed workgroup");
098: BaseWorkgroup newWorkgroup = (BaseWorkgroup) findByDocumentId(documentId);
099: BaseWorkgroup oldWorkgroup = (BaseWorkgroup) getWorkgroupService()
100: .getWorkgroup(newWorkgroup.getWorkflowGroupId());
101:
102: if (oldWorkgroup != null) {
103: oldWorkgroup.setCurrentInd(Boolean.FALSE);
104: getWorkgroupService().save(oldWorkgroup);
105: }
106:
107: newWorkgroup.setCurrentInd(Boolean.TRUE);
108: getWorkgroupService().save(newWorkgroup);
109: if (oldWorkgroup != null) {
110: // if there was an old workgroup then we need to update for member change
111: KEWServiceLocator.getActionListService()
112: .updateActionItemsForWorkgroupChange(oldWorkgroup,
113: newWorkgroup);
114: }
115: }
116:
117: public Long getLockingDocumentId(GroupId groupId)
118: throws WorkflowException {
119: Workgroup workgroup = getEnrouteWorkgroup(groupId);
120: if (workgroup == null) {
121: return null;
122: }
123: Routable routableWorkgroup = (Routable) workgroup;
124: boolean isCurrent = routableWorkgroup.getCurrentInd()
125: .booleanValue();
126: boolean isDead = true;
127: if (routableWorkgroup.getDocumentId() != null
128: && routableWorkgroup.getDocumentId().longValue() > 0) {
129: DocumentRouteHeaderValue routeHeader = KEWServiceLocator
130: .getRouteHeaderService().getRouteHeader(
131: routableWorkgroup.getDocumentId());
132: if (routeHeader == null) {
133: LOG
134: .warn("Could not locate locking document with id "
135: + routableWorkgroup.getDocumentId()
136: + ". This means that the original document "
137: + "id of this workgroup is no longer in the database which could be a data problem!");
138: } else {
139: isDead = routeHeader.isDisaproved()
140: || routeHeader.isCanceled();
141: }
142: } else if (routableWorkgroup.getDocumentId() == null
143: || routableWorkgroup.getDocumentId().longValue() == -1) {
144: isDead = false;
145: }
146: return (!isCurrent && !isDead ? routableWorkgroup
147: .getDocumentId() : null);
148: }
149:
150: public void route(Workgroup workgroup, UserSession user,
151: String annotation) throws WorkflowException {
152: BaseWorkgroup simpleWorkgroup = (BaseWorkgroup) workgroup;
153: materializeMembersForRouting(simpleWorkgroup);
154: validateWorkgroup(simpleWorkgroup);
155: if (simpleWorkgroup.getDocumentId() == null) {
156: throw new WorkflowException(
157: "Workgroup document does not contain a valid document id.");
158: }
159: WorkflowDocument document = getWorkflowDocumentForRouting(
160: simpleWorkgroup.getDocumentId(), workgroup, user
161: .getWorkflowUser());
162: saveWorkgroupForRouting(document, simpleWorkgroup);
163: generateXmlContent(document, simpleWorkgroup);
164: document.routeDocument(annotation);
165: }
166:
167: public void blanketApprove(Workgroup workgroup, UserSession user,
168: String annotation) throws WorkflowException {
169: BaseWorkgroup simpleWorkgroup = (BaseWorkgroup) workgroup;
170: materializeMembersForRouting(simpleWorkgroup);
171: validateWorkgroup(simpleWorkgroup);
172: if (simpleWorkgroup.getDocumentId() == null) {
173: throw new WorkflowException(
174: "Workgroup document does not contain a valid document id.");
175: }
176: WorkflowDocument document = getWorkflowDocumentForRouting(
177: simpleWorkgroup.getDocumentId(), workgroup, user
178: .getWorkflowUser());
179: saveWorkgroupForRouting(document, simpleWorkgroup);
180: generateXmlContent(document, simpleWorkgroup);
181: document.blanketApprove(annotation);
182: }
183:
184: protected void materializeMembersForRouting(BaseWorkgroup workgroup)
185: throws WorkflowException {
186: workgroup.getWorkgroupMembers().clear();
187: for (Recipient member : workgroup.getMembers()) {
188: BaseWorkgroupMember workgroupMember = new BaseWorkgroupMember();
189: workgroupMember.setWorkgroup(workgroup);
190: workgroupMember.setWorkgroupVersionNumber(workgroup
191: .getVersionNumber());
192: if (member instanceof WorkflowUser) {
193: WorkflowUser user = (WorkflowUser) member;
194: workgroupMember.setWorkflowId(user.getWorkflowId());
195: workgroupMember
196: .setMemberType(EdenConstants.ACTION_REQUEST_USER_RECIPIENT_CD);
197: } else if (member instanceof Workgroup) {
198: Workgroup nestedWorkgroup = (Workgroup) member;
199: // check for a cycle in the workgroup membership
200: if (nestedWorkgroup.hasMember(workgroup)) {
201: throw new WorkflowException(
202: "A cycle was detected in workgroup membership. Workgroup '"
203: + nestedWorkgroup.getGroupNameId()
204: .getNameId() + "' has '"
205: + workgroup.getGroupNameId()
206: + "' as a member");
207: }
208: workgroupMember.setWorkflowId(nestedWorkgroup
209: .getWorkflowGroupId().getGroupId().toString());
210: workgroupMember
211: .setMemberType(EdenConstants.ACTION_REQUEST_WORKGROUP_RECIPIENT_CD);
212: } else {
213: LOG
214: .error("Invalid recipient type found for workgroup member when materializing members for routing: "
215: + member.getClass().getName());
216: continue;
217: }
218: workgroup.getWorkgroupMembers().add(workgroupMember);
219: }
220: }
221:
222: protected void validateWorkgroup(BaseWorkgroup workgroup)
223: throws WorkflowException {
224: List errors = new ArrayList();
225: if (workgroup.getWorkflowGroupId() != null
226: && workgroup.getWorkflowGroupId().getGroupId()
227: .longValue() > 0) {
228: Long lockingDocumentId = getLockingDocumentId(workgroup
229: .getWorkflowGroupId());
230: // could have been routed in time user took to route this.
231: if (lockingDocumentId != null) {
232: errors
233: .add(new WorkflowServiceErrorImpl(
234: "Workgroup is currently in route, and locked for changes.",
235: WORKGROUP_LOCKED, "document "
236: + lockingDocumentId));
237: }
238: }
239: if (workgroup.getActiveInd() == null) {
240: errors.add(new WorkflowServiceErrorImpl(
241: "Workgroup active indicator is empty.",
242: ACTIVE_IND_BLANK));
243: }
244:
245: String workgroupName = (workgroup.getGroupNameId() == null ? null
246: : workgroup.getGroupNameId().getNameId());
247: if (StringUtils.isEmpty(workgroupName)) {
248: errors.add(new WorkflowServiceErrorImpl(
249: "Workgroup name is empty.", NAME_BLANK));
250: } else {
251: Workgroup existingWorkgroup = getWorkgroupService()
252: .getWorkgroup(workgroup.getGroupNameId());
253: if (existingWorkgroup != null
254: && existingWorkgroup.getActiveInd().booleanValue()) {
255: if (workgroup.getWorkgroupId() == null) {
256: errors.add(new WorkflowServiceErrorImpl(
257: "Workgroup name already in use.",
258: NAME_EXISTS));
259: } else if (existingWorkgroup.getWorkflowGroupId()
260: .getGroupId().intValue() != workgroup
261: .getWorkgroupId().intValue()) {
262: errors.add(new WorkflowServiceErrorImpl(
263: "Workgroup name already in use.",
264: NAME_EXISTS));
265: }
266: }
267: }
268:
269: if (!StringUtils.isBlank(workgroup.getWorkgroupType())) {
270: WorkgroupType workgroupType = KEWServiceLocator
271: .getWorkgroupTypeService().findByName(
272: workgroup.getWorkgroupType());
273: if (workgroupType == null) {
274: errors.add(new WorkflowServiceErrorImpl(
275: "Could not locate the workgroup type '"
276: + workgroup.getWorkgroupType()
277: + "' defined on the workgroup.",
278: INVALID_TYPE, workgroup.getWorkgroupType()));
279: }
280: }
281:
282: if (workgroup.getWorkgroupMembers().isEmpty()) {
283: errors.add(new WorkflowServiceErrorImpl(
284: "Workgroup does not have members.", NO_MEMBERS));
285: }
286:
287: LOG.debug("Exit validateWorkgroup(..) ");
288: if (!errors.isEmpty()) {
289: throw new WorkflowServiceErrorException(
290: "Workgroup Validation Error", errors);
291: }
292: }
293:
294: protected void generateXmlContent(WorkflowDocument document,
295: BaseWorkgroup workgroup) {
296: ExportDataSet dataSet = new ExportDataSet(ExportFormat.XML);
297: dataSet.getWorkgroups().add(workgroup);
298: Element element = KEWServiceLocator.getWorkgroupService()
299: .export(dataSet);
300: String xmlContent = XmlHelper.jotNode(element);
301: document.setApplicationContent(xmlContent);
302: }
303:
304: protected WorkflowDocument getWorkflowDocumentForRouting(
305: Long routeHeaderId, Workgroup workgroup, WorkflowUser user)
306: throws WorkflowException {
307: WorkflowDocument workflowDocument = new WorkflowDocument(
308: new WorkflowIdVO(user.getWorkflowId()), routeHeaderId);
309: workflowDocument.setTitle(getDocTitle(workgroup));
310: addSearchableAttributeDefinitions(workflowDocument, workgroup);
311: return workflowDocument;
312: }
313:
314: protected void saveWorkgroupForRouting(WorkflowDocument document,
315: BaseWorkgroup workgroup) throws WorkflowException {
316: Routable routableWorkgroup = (Routable) workgroup;
317: Integer versionNumber = new Integer(0);
318: if (workgroup.getWorkflowGroupId() == null
319: || workgroup.getWorkflowGroupId().getGroupId() == null
320: || workgroup.getWorkflowGroupId().getGroupId()
321: .longValue() <= 0) {
322: workgroup.setWorkflowGroupId(new WorkflowGroupId(document
323: .getRouteHeaderId()));
324: } else {
325: versionNumber = getNextVersionNumber(workgroup
326: .getWorkflowGroupId());
327: }
328: routableWorkgroup.setDocumentId(document.getRouteHeaderId());
329: routableWorkgroup.setCurrentInd(Boolean.FALSE);
330: workgroup.setVersionNumber(versionNumber);
331: KEWServiceLocator.getWorkgroupService().save(workgroup);
332: }
333:
334: protected Integer getNextVersionNumber(GroupId groupId)
335: throws WorkflowException {
336: BaseWorkgroup workgroup = (BaseWorkgroup) getEnrouteWorkgroup(groupId);
337: if (workgroup == null) {
338: return new Integer(0);
339: }
340: return new Integer(workgroup.getVersionNumber().intValue() + 1);
341: }
342:
343: /**
344: * Constructs the title for the Workgroup Document.
345: */
346: protected String getDocTitle(Workgroup workgroup) {
347: return ("Routing workgroup " + workgroup.getGroupNameId()
348: .getNameId());
349: }
350:
351: /**
352: * Adds the searchable attribute definitions to this document to allow for searching by Workgroup Name
353: */
354: protected void addSearchableAttributeDefinitions(
355: WorkflowDocument document, Workgroup workgroup) {
356: RuleAttribute searchableAttribute = KEWServiceLocator
357: .getRuleAttributeService().findByName(
358: WORKGROUP_SEARCHABLE_ATTRIBUTE_NAME);
359: if (searchableAttribute != null) {
360: WorkflowAttributeDefinitionVO xmldef = new WorkflowAttributeDefinitionVO(
361: WORKGROUP_SEARCHABLE_ATTRIBUTE_NAME);
362: xmldef.addProperty("wrkgrp_nm", workgroup.getGroupNameId()
363: .getNameId());
364: document.addSearchableDefinition(xmldef);
365: }
366: }
367:
368: /**
369: * Returns the currently enroute workgroup for the given id. Used to determine whether or not the Workgroup is
370: * locked for changes.
371: */
372: protected BaseWorkgroup getEnrouteWorkgroup(GroupId groupId)
373: throws WorkflowException {
374: BaseWorkgroup workgroup = null;
375: if (groupId instanceof WorkflowGroupId) {
376: workgroup = getWorkgroupDAO().findEnrouteWorkgroupById(
377: ((WorkflowGroupId) groupId).getGroupId());
378: } else if (groupId instanceof GroupNameId) {
379: workgroup = getWorkgroupDAO().findEnrouteWorkgroupByName(
380: ((GroupNameId) groupId).getNameId());
381: } else {
382: throw new WorkflowException("Invalid GroupId type: "
383: + groupId);
384: }
385: if (workgroup == null) {
386: LOG
387: .warn("Received null retrieving workgroup by "
388: + groupId);
389: } else {
390: workgroup.materializeMembers();
391: }
392: return workgroup;
393: }
394:
395: protected WorkgroupService getWorkgroupService() {
396: return KEWServiceLocator.getWorkgroupService();
397: }
398:
399: protected BaseWorkgroupDAO getWorkgroupDAO() {
400: return workgroupDAO;
401: }
402:
403: public void setWorkgroupDAO(BaseWorkgroupDAO workgroupDAO) {
404: this.workgroupDAO = workgroupDAO;
405: }
406:
407: }
|