001/**
002 * Copyright 2005-2017 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.web;
017
018import org.apache.struts.action.ActionForm;
019import org.apache.struts.action.ActionForward;
020import org.apache.struts.action.ActionMapping;
021import org.kuali.rice.core.api.config.property.RuntimeConfig;
022import org.kuali.rice.core.api.resourceloader.GlobalResourceLoader;
023import org.kuali.rice.core.api.util.RiceConstants;
024import org.kuali.rice.kew.doctype.service.DocumentTypeService;
025import org.kuali.rice.kew.impl.stuck.StuckDocument;
026import org.kuali.rice.kew.impl.stuck.StuckDocumentFixAttempt;
027import org.kuali.rice.kew.impl.stuck.StuckDocumentIncident;
028import org.kuali.rice.kew.impl.stuck.StuckDocumentNotificationJob;
029import org.kuali.rice.kew.impl.stuck.StuckDocumentService;
030import org.kuali.rice.kew.service.KEWServiceLocator;
031import org.kuali.rice.kim.api.role.RoleService;
032import org.kuali.rice.kim.api.services.KimApiServiceLocator;
033import org.kuali.rice.kns.web.struts.action.KualiAction;
034import org.kuali.rice.krad.exception.AuthorizationException;
035import org.kuali.rice.krad.util.GlobalVariables;
036
037import javax.servlet.http.HttpServletRequest;
038import javax.servlet.http.HttpServletResponse;
039import java.time.format.DateTimeFormatter;
040import java.time.format.FormatStyle;
041import java.util.Collections;
042import java.util.HashMap;
043import java.util.List;
044import java.util.stream.Collectors;
045
046public class StuckDocumentsAction extends KualiAction {
047
048    private static final String NOTIFICATION_ENABLED = "stuckDocumentsNotificationEnabledParam";
049    private static final String NOTIFICATION_CRON_EXPRESSION = "stuckDocumentsNotificationCronExpressionParam";
050    private static final String NOTIFICATION_FROM = "stuckDocumentsNotificationFromParam";
051    private static final String NOTIFICATION_TO = "stuckDocumentsNotificationToParam";
052    private static final String NOTIFICATION_SUBJECT = "stuckDocumentsNotificationSubjectParam";
053
054    private static final String AUTOFIX_ENABLED = "stuckDocumentsAutofixEnabledParam";
055    private static final String AUTOFIX_CRON_EXPRESSION = "stuckDocumentsAutofixCronExpressionParam";
056    private static final String AUTOFIX_QUIET_PERIOD = "stuckDocumentsAutofixQuietPeriodParam";
057    private static final String AUTOFIX_MAX_ATTEMPTS = "stuckDocumentsAutofixMaxAttemptsParam";
058    private static final String AUTOFIX_NOTIFICATION_ENABLED = "stuckDocumentsAutofixNotificationEnabledParam";
059    private static final String AUTOFIX_NOTIFICATION_SUBJECT = "stuckDocumentsAutofixNotificationSubjectParam";
060
061    private static final int MAX_INCIDENTS = 1000;
062
063    /**
064     * To avoid having to go through the pain of setting up a KIM permission for "Use Screen" for this utility screen,
065     * we'll hardcode this screen to the "KR-SYS Technical Administrator" role. Without doing this, the screen is open
066     * to all users until that permission is setup which could be considered a security issue.
067     */
068    protected void checkAuthorization(ActionForm form, String methodToCall) throws AuthorizationException
069    {
070        boolean authorized = false;
071        String principalId = GlobalVariables.getUserSession().getPrincipalId();
072        RoleService roleService = KimApiServiceLocator.getRoleService();
073        String roleId = roleService.getRoleIdByNamespaceCodeAndName("KR-SYS", "Technical Administrator");
074        if (roleId != null) {
075            authorized = roleService.principalHasRole(principalId, Collections.singletonList(roleId),
076                    new HashMap<String, String>(), true);
077        }
078
079        if (!authorized) {
080            throw new AuthorizationException(GlobalVariables.getUserSession().getPerson().getPrincipalName(),
081                    methodToCall,
082                    this.getClass().getSimpleName());
083        }
084    }
085
086    @Override
087    protected ActionForward defaultDispatch(ActionMapping mapping, ActionForm actionForm, HttpServletRequest request, HttpServletResponse response) throws Exception {
088        StuckDocumentsForm form = (StuckDocumentsForm) actionForm;
089
090        form.setNotificationEnabled(getNotificationEnabled().getValue());
091        form.setNotificationCronExpression(getNotificationCronExpression().getValue());
092        form.setNotificationFrom(getNotificationFrom().getValue());
093        form.setNotificationTo(getNotificationTo().getValue());
094        form.setNotificationSubject(getNotificationSubject().getValue());
095
096        form.setAutofixEnabled(getAutofixEnabled().getValue());
097        form.setAutofixCronExpression(getAutofixCronExpression().getValue());
098        form.setAutofixQuietPeriod(getAutofixQuietPeriod().getValue());
099        form.setAutofixMaxAttempts(getAutofixMaxAttempts().getValue());
100        form.setAutofixNotificationEnabled(getAutofixNotificationEnabled().getValue());
101        form.setAutofixNotificationSubject(getAutofixNotificationSubject().getValue());
102
103        return super.defaultDispatch(mapping, form, request, response);
104    }
105
106    public ActionForward updateConfig(ActionMapping mapping, ActionForm actionForm, HttpServletRequest request, HttpServletResponse response) throws Exception {
107        StuckDocumentsForm form = (StuckDocumentsForm)actionForm;
108
109        getNotificationEnabled().setValue(form.getNotificationEnabled());
110        getNotificationCronExpression().setValue(form.getNotificationCronExpression());
111        getNotificationFrom().setValue(form.getNotificationFrom());
112        getNotificationTo().setValue(form.getNotificationTo());
113        getNotificationSubject().setValue(form.getNotificationSubject());
114
115        getAutofixEnabled().setValue(form.getAutofixEnabled());
116        getAutofixCronExpression().setValue(form.getAutofixCronExpression());
117        getAutofixQuietPeriod().setValue(form.getAutofixQuietPeriod());
118        getAutofixMaxAttempts().setValue(form.getAutofixMaxAttempts());
119        getAutofixNotificationEnabled().setValue(form.getAutofixNotificationEnabled());
120        getAutofixNotificationSubject().setValue(form.getAutofixNotificationSubject());
121
122        return mapping.findForward(RiceConstants.MAPPING_BASIC);
123    }
124
125    public ActionForward runStuckNotificationNow(ActionMapping mapping, ActionForm actionForm, HttpServletRequest request, HttpServletResponse response) throws Exception {
126        // make sure we update any config first
127        updateConfig(mapping, actionForm, request, response);
128        // a little hacky, we are depending on that fact that this job doesn't use the JobExecutionContext
129        new StuckDocumentNotificationJob().execute(null);
130        return mapping.findForward(RiceConstants.MAPPING_BASIC);
131    }
132
133    public ActionForward report(ActionMapping mapping, ActionForm actionForm, HttpServletRequest request, HttpServletResponse response) throws Exception {
134        List<StuckDocument> stuckDocuments = getStuckDocumentService().findAllStuckDocuments();
135        request.setAttribute("stuckDocuments", stuckDocuments);
136        return mapping.findForward("report");
137    }
138
139    public ActionForward autofixReport(ActionMapping mapping, ActionForm actionForm, HttpServletRequest request, HttpServletResponse response) throws Exception {
140        StuckDocumentService stuckDocumentService = getStuckDocumentService();
141        StuckDocumentsForm form = (StuckDocumentsForm)actionForm;
142        StuckDocumentsForm.Status selectedStatus = form.getSelectedStatus();
143        List<StuckDocumentIncident> incidents;
144        if (selectedStatus == null || selectedStatus.getValue().equals("All")) {
145            incidents = stuckDocumentService.findAllIncidents(MAX_INCIDENTS);
146        } else {
147            incidents = stuckDocumentService.findIncidentsByStatus(MAX_INCIDENTS, StuckDocumentIncident.Status.valueOf(selectedStatus.getValue()));
148        }
149        List<IncidentHistory> history = incidents.stream().map(incident -> {
150            List<StuckDocumentFixAttempt> attempts = stuckDocumentService.findAllFixAttempts(incident.getStuckDocumentIncidentId());
151            String documentTypeLabel = getDocumentTypeService().findByDocumentId(incident.getDocumentId()).getLabel();
152            return new IncidentHistory(incident, attempts, documentTypeLabel);
153        }).collect(Collectors.toList());
154        request.setAttribute("history", history);
155        return mapping.findForward("autofixReport");
156    }
157
158    private RuntimeConfig getNotificationEnabled() {
159        return GlobalResourceLoader.getService(NOTIFICATION_ENABLED);
160    }
161
162    private RuntimeConfig getNotificationCronExpression() {
163        return GlobalResourceLoader.getService(NOTIFICATION_CRON_EXPRESSION);
164    }
165
166    private RuntimeConfig getNotificationFrom() {
167        return GlobalResourceLoader.getService(NOTIFICATION_FROM);
168    }
169
170    private RuntimeConfig getNotificationTo() {
171        return GlobalResourceLoader.getService(NOTIFICATION_TO);
172    }
173
174    private RuntimeConfig getNotificationSubject() {
175        return GlobalResourceLoader.getService(NOTIFICATION_SUBJECT);
176    }
177
178    private RuntimeConfig getAutofixEnabled() {
179        return GlobalResourceLoader.getService(AUTOFIX_ENABLED);
180    }
181
182    private RuntimeConfig getAutofixCronExpression() {
183        return GlobalResourceLoader.getService(AUTOFIX_CRON_EXPRESSION);
184    }
185
186    private RuntimeConfig getAutofixQuietPeriod() {
187        return GlobalResourceLoader.getService(AUTOFIX_QUIET_PERIOD);
188    }
189
190    private RuntimeConfig getAutofixMaxAttempts() {
191        return GlobalResourceLoader.getService(AUTOFIX_MAX_ATTEMPTS);
192    }
193
194    private RuntimeConfig getAutofixNotificationEnabled() {
195        return GlobalResourceLoader.getService(AUTOFIX_NOTIFICATION_ENABLED);
196    }
197
198    private RuntimeConfig getAutofixNotificationSubject() {
199        return GlobalResourceLoader.getService(AUTOFIX_NOTIFICATION_SUBJECT);
200    }
201
202    private StuckDocumentService getStuckDocumentService() {
203        return KEWServiceLocator.getStuckDocumentService();
204    }
205
206    private DocumentTypeService getDocumentTypeService() {
207        return KEWServiceLocator.getDocumentTypeService();
208    }
209
210    public static class IncidentHistory {
211
212        private final StuckDocumentIncident incident;
213        private final List<StuckDocumentFixAttempt> attempts;
214        private final String documentTypeLabel;
215
216        IncidentHistory(StuckDocumentIncident incident, List<StuckDocumentFixAttempt> attempts, String documentTypeLabel) {
217            this.incident = incident;
218            this.attempts = attempts;
219            this.documentTypeLabel = documentTypeLabel;
220        }
221
222        public String getDocumentId() {
223            return incident.getDocumentId();
224        }
225
226        public String getStartDate() {
227            return incident.getStartDate().toString();
228        }
229
230        public String getEndDate() {
231            if (incident.getEndDate() == null) {
232                return "";
233            }
234            return incident.getEndDate().toString();
235        }
236
237        public String getStatus() {
238            return incident.getStatus().name();
239        }
240
241        public String getFixAttempts() {
242            return attempts.stream().
243                    map(attempt -> attempt.getTimestamp().toString()).
244                    collect(Collectors.joining(", "));
245        }
246
247        public String getDocumentTypeLabel() {
248            return documentTypeLabel;
249        }
250    }
251
252}