View Javadoc

1   package org.kuali.student.enrollment.class2.courseofferingset.service.impl;
2   
3   import org.apache.commons.lang.ArrayUtils;
4   import org.apache.commons.lang.StringUtils;
5   import org.apache.log4j.Logger;
6   import org.kuali.rice.core.api.resourceloader.GlobalResourceLoader;
7   import org.kuali.student.enrollment.courseoffering.dto.ActivityOfferingInfo;
8   import org.kuali.student.enrollment.courseoffering.service.CourseOfferingService;
9   import org.kuali.student.enrollment.courseofferingset.dto.SocInfo;
10  import org.kuali.student.enrollment.courseofferingset.service.CourseOfferingSetService;
11  import org.kuali.student.r2.common.dto.ContextInfo;
12  import org.kuali.student.r2.common.dto.StatusInfo;
13  import org.kuali.student.r2.common.exceptions.*;
14  import org.kuali.student.r2.common.util.constants.CourseOfferingServiceConstants;
15  import org.kuali.student.r2.common.util.constants.CourseOfferingSetServiceConstants;
16  import org.kuali.student.r2.common.util.constants.LuiServiceConstants;
17  
18  import javax.xml.namespace.QName;
19  import java.util.Date;
20  import java.util.List;
21  
22  /**
23   *  SOC mass publishing event helper.
24   *
25   *  This code needs to move to services layer ... probably to CourseOfferingSetServiceBuisnessLogic.
26   *  Concurrency not addressed since it is just a stop-gap.
27   */
28  public class CourseOfferingSetPublishingHelper {
29      final static Logger LOG = Logger.getLogger(CourseOfferingSetPublishingHelper.class);
30  
31      private CourseOfferingService coService;
32      private CourseOfferingSetService socService;
33  
34      /**
35       * Kicks off SOC lifecycle mass publishing event. Runs asychronously by default.
36       *
37       * @param socId The ID of the SOC to publish.
38       * @param optionKeys List of options.
39       * @param context
40       * @return A StatusInfo on success. Otherwise, throws and exception.
41       */
42      public StatusInfo startMassPublishingEvent(String socId, List<String> optionKeys, ContextInfo context)
43              throws InvalidParameterException, MissingParameterException, DoesNotExistException, PermissionDeniedException, OperationFailedException {
44  
45          //  Validate the SOC. Should exist, state should be "publishing" and scheduling state "completed" .
46          SocInfo soc = getSocService().getSoc(socId, context);
47          if ( ! StringUtils.equals(soc.getStateKey(), CourseOfferingSetServiceConstants.PUBLISHING_SOC_STATE_KEY)) {
48              throw new OperationFailedException(String.format("SOC state [%s] was invalid for mass publishing.", soc.getStateKey()));
49          }
50  
51          if (! StringUtils.equals(soc.getSchedulingStateKey(), CourseOfferingSetServiceConstants.SOC_SCHEDULING_STATE_COMPLETED)) {
52              throw new OperationFailedException(String.format("SOC scheduling state [%s] was invalid for mass publishing.", soc.getStateKey()));
53          }
54  
55          //  Initialize the runner
56          final SocMassPublishingRunner runner = new SocMassPublishingRunner();
57          runner.setCoService(getCourseOfferingService());
58          runner.setSocService(getSocService());
59          runner.setSocId(socId);
60          runner.setContext(context);
61  
62          if (optionKeys.contains(CourseOfferingSetServiceConstants.RUN_SYNCHRONOUSLY_OPTION_KEY)) {
63              //  Run in the existing thread.
64              runner.run();
65          } else {
66              //  Run asynchronously after any transactions clear
67              KSThreadRunnerAfterTransactionSynchronization.runAfterTransactionCompletes(runner);
68          }
69  
70          StatusInfo statusInfo = new StatusInfo();
71          statusInfo.setSuccess(true);
72          statusInfo.setMessage("Success");
73  
74          return statusInfo;
75      }
76  
77      //  For unit testing
78      public void setCoService(CourseOfferingService coService) {
79          this.coService = coService;
80      }
81  
82      public void setSocService(CourseOfferingSetService socService) {
83          this.socService = socService;
84      }
85  
86      private CourseOfferingSetService getSocService() {
87          if (socService == null) {
88              socService = (CourseOfferingSetService) GlobalResourceLoader.getService(new QName(CourseOfferingSetServiceConstants.NAMESPACE,
89                  CourseOfferingSetServiceConstants.SERVICE_NAME_LOCAL_PART));
90          }
91          return socService;
92      }
93  
94      private CourseOfferingService getCourseOfferingService() {
95          if (coService == null) {
96              coService = (CourseOfferingService) GlobalResourceLoader.getService(new QName(CourseOfferingServiceConstants.NAMESPACE,
97                      CourseOfferingServiceConstants.SERVICE_NAME_LOCAL_PART));
98          }
99          return coService;
100     }
101 
102     /**
103      * Performs publishing state changes on COs, FOs, and AOs.
104      *
105      * !!! This code should set the SOC state to a sane value: "published" on success or back to "final edits" if there is a problem. !!!
106      */
107     public class SocMassPublishingRunner implements Runnable {
108         private ContextInfo context;
109         private CourseOfferingService coService;
110         private CourseOfferingSetService socService;
111 
112         private String socId;
113 
114         private final String[] aoSchedStatesForOfferedKeys = {
115                 LuiServiceConstants.LUI_AO_SCHEDULING_STATE_EXEMPT_KEY,
116                 LuiServiceConstants.LUI_AO_SCHEDULING_STATE_SCHEDULED_KEY
117         };
118         private final String aoOfferedKey = LuiServiceConstants.LUI_AO_STATE_OFFERED_KEY;
119         private final String aoApprovedKey = LuiServiceConstants.LUI_AO_STATE_APPROVED_KEY;
120         private final String foOfferedKey = LuiServiceConstants.LUI_FO_STATE_OFFERED_KEY;
121 
122         @Override
123         public void run() {
124             boolean success = true;
125             try {
126                 doMpe();
127             } catch(Exception e) {
128                 LOG.error("The Mass Publishing Event did not complete successfully.", e);
129                 success = false;
130             }
131 
132             if (! success) {
133                 LOG.warn(String.format("Changing SOC state back to [%s].", CourseOfferingSetServiceConstants.FINALEDITS_SOC_STATE_KEY));
134                 StatusInfo statusInfo = null;
135                 try {
136                     statusInfo = socService.updateSocState(socId, CourseOfferingSetServiceConstants.FINALEDITS_SOC_STATE_KEY, context);
137                 } catch (Exception e) {
138                     LOG.error(String.format("Unable to change SOC state back to [%s]. The SOC state will have to be manually updated to recover.",
139                         CourseOfferingSetServiceConstants.FINALEDITS_SOC_STATE_KEY));
140                 }
141                 if (statusInfo == null || ! statusInfo.getIsSuccess()) {
142                     throw new RuntimeException(String.format("State changed failed for SOC [%s]: %s", socId, statusInfo.getMessage()));
143                 }
144             }
145         }
146 
147         private void doMpe() throws Exception {
148             LOG.warn(String.format("Beginning Mass Publishing Event for SOC [%s].", socId));
149             context.setCurrentDate(new Date());
150             /*
151              * Get all of the COs within the SOC. Query the AOs for each CO and do state changes.
152              */
153             List<String> coIds = socService.getCourseOfferingIdsBySoc(socId, context);
154             for (String coId : coIds) {
155                 boolean hasAOStateChange = false;
156                 List<ActivityOfferingInfo> activityOfferings = coService.getActivityOfferingsByCourseOffering(coId, context);
157                 for (ActivityOfferingInfo ao : activityOfferings) {
158                     /*
159                      * All AOs with BOTH a state of Approved and a Scheduling state of Scheduled or Exempt will change to AO
160                      * state of Offered. The FO and CO for these AOs also changes state from Planned to Offered.
161                      */
162                     String aoState = ao.getStateKey();
163                     String aoSchedState = ao.getSchedulingStateKey();
164                     if (LOG.isDebugEnabled()) {
165                         LOG.debug(String.format("Inspecting CO [%s] AO [%s] in state %s and scheduling state [%s].", coId, ao.getId(), aoState, aoSchedState));
166                     }
167                     if (StringUtils.equals(aoState, aoApprovedKey) && ArrayUtils.contains(aoSchedStatesForOfferedKeys, aoSchedState)) {
168                         if (! hasAOStateChange) {
169                             hasAOStateChange = true;
170                         }
171                         StatusInfo statusInfo = coService.updateActivityOfferingState(ao.getId(), aoOfferedKey, context);
172                         if ( ! statusInfo.getIsSuccess()) {
173                             LOG.error(String.format("State change failed for AO [%s]: %s", ao.getId(), statusInfo.getMessage()));
174                         } else {
175                             if (LOG.isDebugEnabled()) {
176                                 LOG.debug(String.format("Updating AO [%s] state to [%s].", ao.getId(), aoState));
177                             }
178                         }
179                         //  Change the FO state to offered.
180                         statusInfo = coService.updateFormatOfferingState(ao.getFormatOfferingId(), foOfferedKey, context);
181                         if ( ! statusInfo.getIsSuccess()) {
182                             LOG.error(String.format("State change failed for FO [%s]: %s", ao.getFormatOfferingId(), statusInfo.getMessage()));
183                         }  else {
184                             if (LOG.isDebugEnabled()) {
185                                 LOG.debug(String.format("Updating FO [%s] state to [%s].", ao.getFormatOfferingId(), foOfferedKey));
186                             }
187                         }
188                     } else {
189                         if (LOG.isDebugEnabled()) {
190                             LOG.debug(String.format("CO [%s] AO [%s] doesn't need a state change.", coId, ao.getId()));
191                         }
192                     }
193                 }
194 
195                 // If an AO changed state then state change the CO.
196                 if (hasAOStateChange) {
197                     coService.updateCourseOfferingState(coId, LuiServiceConstants.LUI_CO_STATE_OFFERED_KEY, context);
198                     if (LOG.isDebugEnabled()) {
199                         LOG.debug(String.format("Updating CO [%s] state to [%s].", coId, LuiServiceConstants.LUI_CO_STATE_OFFERED_KEY));
200                     }
201                 }
202             }
203 
204             //  Set SOC scheduling state to "published".
205             LOG.warn(String.format("Updating SOC [%s] state to [%s].", socId, CourseOfferingSetServiceConstants.PUBLISHED_SOC_STATE_KEY));
206             context.setCurrentDate(new Date());
207             StatusInfo statusInfo = socService.updateSocState(socId, CourseOfferingSetServiceConstants.PUBLISHED_SOC_STATE_KEY, context);
208             if ( ! statusInfo.getIsSuccess()) {
209                 throw new RuntimeException(String.format("State changed failed for SOC [%s]: %s", socId, statusInfo.getMessage()));
210             }
211 
212             LOG.warn(String.format("Mass Publishing Event for SOC [%s] completed.", socId));
213         }
214 
215         public String getSocId() {
216             return socId;
217         }
218 
219         public void setSocId(String socId) {
220             this.socId = socId;
221         }
222 
223         public void setContext(ContextInfo context) {
224             this.context = context;
225         }
226 
227         public void setCoService(CourseOfferingService coService) {
228             this.coService = coService;
229         }
230 
231         public void setSocService(CourseOfferingSetService socService) {
232             this.socService = socService;
233         }
234     }
235 }