001package org.kuali.ole.olekrad.service;
002
003import org.apache.commons.lang.StringUtils;
004import org.apache.commons.lang.time.StopWatch;
005import org.kuali.rice.core.api.CoreApiServiceLocator;
006import org.kuali.rice.core.api.exception.RiceRuntimeException;
007import org.kuali.rice.core.api.util.RiceKeyConstants;
008import org.kuali.rice.kew.api.KewApiConstants;
009import org.kuali.rice.kew.api.KewApiServiceLocator;
010import org.kuali.rice.kew.api.WorkflowDocument;
011import org.kuali.rice.kew.api.WorkflowDocumentFactory;
012import org.kuali.rice.kew.api.action.ActionRequestType;
013import org.kuali.rice.kew.api.action.ActionType;
014import org.kuali.rice.kew.api.document.node.RouteNodeInstance;
015import org.kuali.rice.kew.api.exception.WorkflowException;
016import org.kuali.rice.kim.api.group.Group;
017import org.kuali.rice.kim.api.identity.Person;
018import org.kuali.rice.kim.api.identity.principal.Principal;
019import org.kuali.rice.kim.api.services.KimApiServiceLocator;
020import org.kuali.rice.krad.bo.AdHocRoutePerson;
021import org.kuali.rice.krad.bo.AdHocRouteRecipient;
022import org.kuali.rice.krad.bo.AdHocRouteWorkgroup;
023import org.kuali.rice.krad.exception.UnknownDocumentIdException;
024import org.kuali.rice.krad.service.KRADServiceLocator;
025import org.kuali.rice.krad.util.GlobalVariables;
026import org.kuali.rice.krad.workflow.service.impl.WorkflowDocumentServiceImpl;
027import org.springframework.transaction.annotation.Transactional;
028
029import java.text.MessageFormat;
030import java.util.ArrayList;
031import java.util.HashSet;
032import java.util.List;
033import java.util.Set;
034
035/**
036 * Created with IntelliJ IDEA.
037 * User: sheiksalahudeenm
038 * Date: 12/16/13
039 * Time: 4:08 PM
040 * To change this template use File | Settings | File Templates.
041 */
042public class OLEWorkflowDocumentServiceImpl extends WorkflowDocumentServiceImpl {
043
044    private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(OLEWorkflowDocumentServiceImpl.class);
045
046    @Override
047    public boolean workflowDocumentExists(String documentId) {
048        boolean exists = false;
049
050        if (StringUtils.isBlank(documentId)) {
051            throw new IllegalArgumentException("invalid (blank) documentId");
052        }
053
054        exists = KewApiServiceLocator.getWorkflowDocumentService().doesDocumentExist(documentId);
055
056        return exists;
057    }
058
059    @Override
060    public WorkflowDocument createWorkflowDocument(String documentTypeName, Person person) {
061        String watchName = "createWorkflowDocument";
062        StopWatch watch = new StopWatch();
063        watch.start();
064        if (LOG.isDebugEnabled()) {
065            LOG.debug(watchName + ": started");
066        }
067        if (StringUtils.isBlank(documentTypeName)) {
068            throw new IllegalArgumentException("invalid (blank) documentTypeName");
069        }
070        if (person == null) {
071            throw new IllegalArgumentException("invalid (null) person");
072        }
073
074        if (StringUtils.isBlank(person.getPrincipalName())) {
075            throw new IllegalArgumentException("invalid (empty) PrincipalName");
076        }
077
078        if (LOG.isDebugEnabled()) {
079            LOG.debug("creating workflowDoc(" + documentTypeName + "," + person.getPrincipalName() + ")");
080        }
081
082        WorkflowDocument document = WorkflowDocumentFactory.createDocument(person.getPrincipalId(), documentTypeName);
083        watch.stop();
084        if (LOG.isDebugEnabled()) {
085            LOG.debug(watchName + ": " + watch.toString());
086        }
087
088        return document;
089    }
090
091    @Override
092    public WorkflowDocument loadWorkflowDocument(String documentId, Person user) {
093        if (documentId == null) {
094            throw new IllegalArgumentException("invalid (null) documentHeaderId");
095        }
096        if (user == null) {
097            throw new IllegalArgumentException("invalid (null) workflowUser");
098        }
099        else if (StringUtils.isEmpty(user.getPrincipalName())) {
100            throw new IllegalArgumentException("invalid (empty) workflowUser");
101        }
102
103        if (LOG.isDebugEnabled()) {
104            LOG.debug("retrieving document(" + documentId + "," + user.getPrincipalName() + ")");
105        }
106
107        try {
108            return WorkflowDocumentFactory.loadDocument(user.getPrincipalId(), documentId);
109        } catch (IllegalArgumentException e) {
110            // TODO do we really need to do this or just let the IllegalArgument propogate?
111            throw new UnknownDocumentIdException("unable to locate document with documentHeaderId '" + documentId + "'");
112        }
113    }
114
115    @Override
116    public void acknowledge(WorkflowDocument workflowDocument, String annotation, List<AdHocRouteRecipient> adHocRecipients) throws WorkflowException {
117        if (LOG.isDebugEnabled()) {
118            LOG.debug("acknowleding document(" + workflowDocument.getDocumentId() + ",'" + annotation + "')");
119        }
120
121        handleAdHocRouteRequests(workflowDocument, annotation, filterAdHocRecipients(adHocRecipients, new String[] { KewApiConstants.ACTION_REQUEST_ACKNOWLEDGE_REQ, KewApiConstants.ACTION_REQUEST_FYI_REQ }));
122        workflowDocument.acknowledge(annotation);
123    }
124
125    @Override
126    public void approve(WorkflowDocument workflowDocument, String annotation, List<AdHocRouteRecipient> adHocRecipients) throws WorkflowException {
127        if (LOG.isDebugEnabled()) {
128            LOG.debug("approving document(" + workflowDocument.getDocumentId() + ",'" + annotation + "')");
129        }
130
131        handleAdHocRouteRequests(workflowDocument, annotation, filterAdHocRecipients(adHocRecipients, new String[] { KewApiConstants.ACTION_REQUEST_ACKNOWLEDGE_REQ, KewApiConstants.ACTION_REQUEST_FYI_REQ, KewApiConstants.ACTION_REQUEST_APPROVE_REQ }));
132        workflowDocument.approve(annotation);
133    }
134
135
136    @Override
137    public void superUserApprove(WorkflowDocument workflowDocument, String annotation) throws WorkflowException {
138        if ( LOG.isInfoEnabled() ) {
139            LOG.info("super user approve document(" + workflowDocument.getDocumentId() + ",'" + annotation + "')");
140        }
141        workflowDocument.superUserBlanketApprove(annotation);
142    }
143
144    @Override
145    public void superUserCancel(WorkflowDocument workflowDocument, String annotation) throws WorkflowException {
146        LOG.info("super user cancel document(" + workflowDocument.getDocumentId() + ",'" + annotation + "')");
147        workflowDocument.superUserCancel(annotation);
148    }
149
150    @Override
151    public void superUserDisapprove(WorkflowDocument workflowDocument, String annotation) throws WorkflowException {
152        if ( LOG.isInfoEnabled() ) {
153            LOG.info("super user disapprove document(" + workflowDocument.getDocumentId() + ",'" + annotation + "')");
154        }
155        workflowDocument.superUserDisapprove(annotation);
156    }
157
158    @Override
159    public void blanketApprove(WorkflowDocument workflowDocument, String annotation, List<AdHocRouteRecipient> adHocRecipients) throws WorkflowException {
160        if (LOG.isDebugEnabled()) {
161            LOG.debug("blanket approving document(" + workflowDocument.getDocumentId() + ",'" + annotation + "')");
162        }
163
164        handleAdHocRouteRequests(workflowDocument, annotation, filterAdHocRecipients(adHocRecipients, new String[] { KewApiConstants.ACTION_REQUEST_ACKNOWLEDGE_REQ, KewApiConstants.ACTION_REQUEST_FYI_REQ }));
165        workflowDocument.blanketApprove(annotation);
166    }
167
168    @Override
169    public void cancel(WorkflowDocument workflowDocument, String annotation) throws WorkflowException {
170        if (LOG.isDebugEnabled()) {
171            LOG.debug("canceling document(" + workflowDocument.getDocumentId() + ",'" + annotation + "')");
172        }
173
174        workflowDocument.cancel(annotation);
175    }
176
177    @Override
178    public void recall(WorkflowDocument workflowDocument, String annotation, boolean cancel) throws WorkflowException {
179        if (LOG.isDebugEnabled()) {
180            LOG.debug("recalling document(" + workflowDocument.getDocumentId() + ",'" + annotation + "', '" + cancel + "')");
181        }
182
183        workflowDocument.recall(annotation, cancel);
184    }
185
186    @Override
187    public void clearFyi(WorkflowDocument workflowDocument, List<AdHocRouteRecipient> adHocRecipients) throws WorkflowException {
188        if (LOG.isDebugEnabled()) {
189            LOG.debug("clearing FYI for document(" + workflowDocument.getDocumentId() + ")");
190        }
191
192        handleAdHocRouteRequests(workflowDocument, "", filterAdHocRecipients(adHocRecipients, new String[] { KewApiConstants.ACTION_REQUEST_FYI_REQ }));
193        workflowDocument.fyi();
194    }
195
196    @Override
197    public void sendWorkflowNotification(WorkflowDocument workflowDocument, String annotation, List<AdHocRouteRecipient> adHocRecipients) throws WorkflowException {
198        sendWorkflowNotification(workflowDocument, annotation, adHocRecipients, null);
199    }
200
201    @Override
202    public void sendWorkflowNotification(WorkflowDocument workflowDocument, String annotation, List<AdHocRouteRecipient> adHocRecipients, String notificationLabel) throws WorkflowException {
203        if (LOG.isDebugEnabled()) {
204            LOG.debug("sending FYI for document(" + workflowDocument.getDocumentId() + ")");
205        }
206
207        handleAdHocRouteRequests(workflowDocument, annotation, adHocRecipients, notificationLabel);
208    }
209
210    @Override
211    public void disapprove(WorkflowDocument workflowDocument, String annotation) throws WorkflowException {
212        if (LOG.isDebugEnabled()) {
213            LOG.debug("disapproving document(" + workflowDocument.getDocumentId() + ",'" + annotation + "')");
214        }
215
216        workflowDocument.disapprove(annotation);
217    }
218
219    @Override
220    public void route(WorkflowDocument workflowDocument, String annotation, List<AdHocRouteRecipient> adHocRecipients) throws WorkflowException {
221        if (LOG.isDebugEnabled()) {
222            LOG.debug("routing document(" + workflowDocument.getDocumentId() + ",'" + annotation + "')");
223        }
224
225        handleAdHocRouteRequests(workflowDocument, annotation, filterAdHocRecipients(adHocRecipients, new String[] { KewApiConstants.ACTION_REQUEST_ACKNOWLEDGE_REQ, KewApiConstants.ACTION_REQUEST_FYI_REQ, KewApiConstants.ACTION_REQUEST_APPROVE_REQ, KewApiConstants.ACTION_REQUEST_COMPLETE_REQ }));
226        workflowDocument.route(annotation);
227    }
228
229    @Override
230    public void save(WorkflowDocument workflowDocument, String annotation) throws WorkflowException {
231        if (workflowDocument.isValidAction(ActionType.SAVE)) {
232            if (LOG.isDebugEnabled()) {
233                LOG.debug("saving document(" + workflowDocument.getDocumentId() + ",'" + annotation + "')");
234            }
235
236            workflowDocument.saveDocument(annotation);
237        }
238        else {
239            this.saveRoutingData(workflowDocument);
240        }
241    }
242
243    @Override
244    public void saveRoutingData(WorkflowDocument workflowDocument) throws WorkflowException {
245        if (LOG.isDebugEnabled()) {
246            LOG.debug("saving document(" + workflowDocument.getDocumentId() + ")");
247        }
248
249        workflowDocument.saveDocumentData();
250    }
251
252    @Override
253    public String getCurrentRouteLevelName(WorkflowDocument workflowDocument) throws WorkflowException {
254        if (LOG.isDebugEnabled()) {
255            LOG.debug("getting current route level name for document(" + workflowDocument.getDocumentId());
256        }
257//        return KEWServiceLocator.getRouteHeaderService().getRouteHeader(workflowDocument.getDocumentId()).getCurrentRouteLevelName();
258        WorkflowDocument freshCopyWorkflowDoc = loadWorkflowDocument(workflowDocument.getDocumentId(), GlobalVariables.getUserSession().getPerson());
259        return getCurrentRouteNodeNames(freshCopyWorkflowDoc);
260    }
261
262
263
264    @Override
265    public String getCurrentRouteNodeNames(WorkflowDocument workflowDocument) {
266        Set<String> nodeNames = workflowDocument.getNodeNames();
267        if (nodeNames.isEmpty()) {
268            return "";
269        }
270        StringBuilder builder = new StringBuilder();
271        for (String nodeName : nodeNames) {
272            builder.append(nodeName).append(", ");
273        }
274        builder.setLength(builder.length() - 2);
275        return builder.toString();
276    }
277
278    private void handleAdHocRouteRequests(WorkflowDocument workflowDocument, String annotation, List<AdHocRouteRecipient> adHocRecipients) throws WorkflowException {
279        handleAdHocRouteRequests(workflowDocument, annotation, adHocRecipients, null);
280    }
281
282
283    private void handleAdHocRouteRequests(WorkflowDocument workflowDocument, String annotation, List<AdHocRouteRecipient> adHocRecipients, String notificationLabel) throws WorkflowException {
284
285        if (adHocRecipients != null && adHocRecipients.size() > 0) {
286            String currentNode = null;
287            Set<String> currentNodes = workflowDocument.getNodeNames();
288            if (currentNodes.isEmpty()) {
289                List<RouteNodeInstance> nodes = KewApiServiceLocator.getWorkflowDocumentService().getTerminalRouteNodeInstances(
290                        workflowDocument.getDocumentId());
291                currentNodes = new HashSet<String>();
292                for (RouteNodeInstance node : nodes) {
293                    currentNodes.add(node.getName());
294                }
295            }
296            // for now just pick a node and go with it...
297            currentNode = currentNodes.iterator().next();
298
299            List<AdHocRoutePerson> adHocRoutePersons = new ArrayList<AdHocRoutePerson>();
300            List<AdHocRouteWorkgroup> adHocRouteWorkgroups = new ArrayList<AdHocRouteWorkgroup>();
301
302            for (AdHocRouteRecipient recipient : adHocRecipients) {
303                if (StringUtils.isNotEmpty(recipient.getId())) {
304                    String newAnnotation = annotation;
305                    if ( StringUtils.isBlank( annotation ) ) {
306                        try {
307                            String message = CoreApiServiceLocator.getKualiConfigurationService().getPropertyValueAsString(
308                                    RiceKeyConstants.MESSAGE_ADHOC_ANNOTATION);
309                            newAnnotation = MessageFormat.format(message, GlobalVariables.getUserSession().getPrincipalName());
310                        } catch ( Exception ex ) {
311                            LOG.warn("Unable to set annotation", ex );
312                        }
313                    }
314                    if (AdHocRouteRecipient.PERSON_TYPE.equals(recipient.getType())) {
315                        // TODO make the 1 a constant
316                        Principal principal = KimApiServiceLocator.getIdentityService().getPrincipalByPrincipalName(recipient.getName());
317                        if (principal == null) {
318                            throw new RiceRuntimeException("Could not locate principal with name '" + recipient.getId() + "'");
319                        }
320                        workflowDocument.adHocToPrincipal(ActionRequestType.fromCode(recipient.getActionRequested()), currentNode, newAnnotation, principal.getPrincipalId(), "", true, notificationLabel);
321                        AdHocRoutePerson personRecipient  = (AdHocRoutePerson)recipient;
322                        adHocRoutePersons.add(personRecipient);
323                    }
324                    else {
325                        Group group = KimApiServiceLocator.getGroupService().getGroup(recipient.getId());
326                        if (group == null) {
327                            throw new RiceRuntimeException("Could not locate group with id '" + recipient.getId() + "'");
328                        }
329                        workflowDocument.adHocToGroup(ActionRequestType.fromCode(recipient.getActionRequested()), currentNode, newAnnotation, group.getId() , "", true, notificationLabel);
330                        AdHocRouteWorkgroup groupRecipient  = (AdHocRouteWorkgroup)recipient;
331                        adHocRouteWorkgroups.add(groupRecipient);
332                    }
333                }
334            }
335            KRADServiceLocator.getBusinessObjectService().delete(adHocRoutePersons);
336            KRADServiceLocator.getBusinessObjectService().delete(adHocRouteWorkgroups);
337        }
338    }
339
340    /**
341     * Convenience method to filter out any ad hoc recipients that should not be allowed given the action requested of the user that
342     * is taking action on the document
343     *
344     * @param adHocRecipients
345     */
346    private List<AdHocRouteRecipient> filterAdHocRecipients(List<AdHocRouteRecipient> adHocRecipients, String[] validTypes) {
347        // now filter out any but ack or fyi from the ad hoc list
348        List<AdHocRouteRecipient> realAdHocRecipients = new ArrayList<AdHocRouteRecipient>();
349        if (adHocRecipients != null) {
350            for (AdHocRouteRecipient proposedRecipient : adHocRecipients) {
351                if (StringUtils.isNotBlank(proposedRecipient.getActionRequested())) {
352                    for (int i = 0; i < validTypes.length; i++) {
353                        if (validTypes[i].equals(proposedRecipient.getActionRequested())) {
354                            realAdHocRecipients.add(proposedRecipient);
355                        }
356                    }
357                }
358            }
359        }
360        return realAdHocRecipients;
361    }
362
363    /**
364     * Completes workflow document
365     *
366     * @see org.kuali.rice.krad.workflow.service.WorkflowDocumentService#complete(org.kuali.rice.kew.api.WorkflowDocument, String, java.util.List)
367     */
368    public void complete(WorkflowDocument workflowDocument, String annotation, List adHocRecipients) throws WorkflowException {
369        if (LOG.isDebugEnabled()) {
370            LOG.debug("routing flexDoc(" + workflowDocument.getDocumentId() + ",'" + annotation + "')");
371        }
372        handleAdHocRouteRequests(workflowDocument, annotation, filterAdHocRecipients(adHocRecipients, new String[] { KewApiConstants.ACTION_REQUEST_COMPLETE_REQ,KewApiConstants.ACTION_REQUEST_ACKNOWLEDGE_REQ, KewApiConstants.ACTION_REQUEST_FYI_REQ, KewApiConstants.ACTION_REQUEST_APPROVE_REQ }));
373        workflowDocument.complete(annotation);
374    }
375}