001 /** 002 * Copyright 2005-2013 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 */ 016 package org.kuali.rice.kew.preferences.service.impl; 017 018 import java.util.ArrayList; 019 import java.util.Collection; 020 import java.util.HashMap; 021 import java.util.Map; 022 import java.util.Map.Entry; 023 024 import org.apache.commons.lang.StringUtils; 025 import org.kuali.rice.core.api.config.property.ConfigContext; 026 import org.kuali.rice.core.api.config.property.ConfigurationService; 027 import org.kuali.rice.kew.api.KewApiConstants; 028 import org.kuali.rice.kew.api.preferences.Preferences; 029 import org.kuali.rice.kew.api.preferences.PreferencesService; 030 import org.kuali.rice.kew.exception.WorkflowServiceErrorException; 031 import org.kuali.rice.kew.exception.WorkflowServiceErrorImpl; 032 import org.kuali.rice.kew.service.KEWServiceLocator; 033 import org.kuali.rice.kew.useroptions.UserOptions; 034 import org.kuali.rice.kew.useroptions.UserOptionsService; 035 import org.kuali.rice.krad.service.KRADServiceLocator; 036 037 038 /** 039 * An implementation of the {@link PreferencesService}. 040 * 041 * @author Kuali Rice Team (rice.collab@kuali.org) 042 */ 043 public class PreferencesServiceImpl implements PreferencesService { 044 045 private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(PreferencesServiceImpl.class); 046 047 private static Map<String, String> USER_OPTION_KEY_DEFAULT_MAP; 048 049 static { 050 USER_OPTION_KEY_DEFAULT_MAP = new HashMap<String, String>(); 051 USER_OPTION_KEY_DEFAULT_MAP.put(Preferences.KEYS.COLOR_APPROVED, "userOptions.default.color"); 052 USER_OPTION_KEY_DEFAULT_MAP.put(Preferences.KEYS.COLOR_CANCELED, "userOptions.default.color"); 053 USER_OPTION_KEY_DEFAULT_MAP.put(Preferences.KEYS.COLOR_DISAPPROVE_CANCEL, "userOptions.default.color"); 054 USER_OPTION_KEY_DEFAULT_MAP.put(Preferences.KEYS.COLOR_DISAPPROVED, "userOptions.default.color"); 055 USER_OPTION_KEY_DEFAULT_MAP.put(Preferences.KEYS.COLOR_ENROUTE, "userOptions.default.color"); 056 USER_OPTION_KEY_DEFAULT_MAP.put(Preferences.KEYS.COLOR_EXCEPTION, "userOptions.default.color"); 057 USER_OPTION_KEY_DEFAULT_MAP.put(Preferences.KEYS.COLOR_FINAL, "userOptions.default.color"); 058 USER_OPTION_KEY_DEFAULT_MAP.put(Preferences.KEYS.COLOR_INITIATED, "userOptions.default.color"); 059 USER_OPTION_KEY_DEFAULT_MAP.put(Preferences.KEYS.COLOR_PROCESSED, "userOptions.default.color"); 060 USER_OPTION_KEY_DEFAULT_MAP.put(Preferences.KEYS.COLOR_SAVED, "userOptions.default.color"); 061 USER_OPTION_KEY_DEFAULT_MAP.put(Preferences.KEYS.EMAIL_NOTIFICATION, "userOptions.default.email"); 062 USER_OPTION_KEY_DEFAULT_MAP.put(Preferences.KEYS.NOTIFY_PRIMARY_DELEGATION, "userOptions.default.notifyPrimary"); 063 USER_OPTION_KEY_DEFAULT_MAP.put(Preferences.KEYS.NOTIFY_SECONDARY_DELEGATION, "userOptions.default.notifySecondary"); 064 USER_OPTION_KEY_DEFAULT_MAP.put(Preferences.KEYS.OPEN_NEW_WINDOW, "userOptions.default.openNewWindow"); 065 USER_OPTION_KEY_DEFAULT_MAP.put(Preferences.KEYS.PAGE_SIZE, "userOptions.default.actionListSize"); 066 USER_OPTION_KEY_DEFAULT_MAP.put(Preferences.KEYS.REFRESH_RATE, "userOptions.default.refreshRate"); 067 USER_OPTION_KEY_DEFAULT_MAP.put(Preferences.KEYS.SHOW_ACTION_REQUESTED, "userOptions.default.showActionRequired"); 068 USER_OPTION_KEY_DEFAULT_MAP.put(Preferences.KEYS.SHOW_DATE_CREATED, "userOptions.default.showDateCreated"); 069 USER_OPTION_KEY_DEFAULT_MAP.put(Preferences.KEYS.SHOW_DOC_TYPE, "userOptions.default.showDocumentType"); 070 USER_OPTION_KEY_DEFAULT_MAP.put(Preferences.KEYS.SHOW_DOCUMENT_STATUS, "userOptions.default.showDocumentStatus"); 071 USER_OPTION_KEY_DEFAULT_MAP.put(Preferences.KEYS.SHOW_INITIATOR, "showInitiator"); 072 USER_OPTION_KEY_DEFAULT_MAP.put(Preferences.KEYS.SHOW_DELEGATOR, "userOptions.default.showDelegator"); 073 USER_OPTION_KEY_DEFAULT_MAP.put(Preferences.KEYS.SHOW_DOC_TITLE, "userOptions.default.showTitle"); 074 USER_OPTION_KEY_DEFAULT_MAP.put(Preferences.KEYS.SHOW_GROUP_REQUEST, "userOptions.default.showWorkgroupRequest"); 075 USER_OPTION_KEY_DEFAULT_MAP.put(Preferences.KEYS.SHOW_CLEAR_FYI, "userOptions.default.showClearFYI"); 076 USER_OPTION_KEY_DEFAULT_MAP.put(Preferences.KEYS.DELEGATOR_FILTER, "userOptions.default.delegatorFilterOnActionList"); 077 USER_OPTION_KEY_DEFAULT_MAP.put(Preferences.KEYS.PRIMARY_DELEGATE_FILTER, "userOptions.default.primaryDelegatorFilterOnActionList"); 078 USER_OPTION_KEY_DEFAULT_MAP.put(Preferences.KEYS.SHOW_DATE_APPROVED, "userOptions.default.showLastApprovedDate"); 079 USER_OPTION_KEY_DEFAULT_MAP.put(Preferences.KEYS.SHOW_CURRENT_NODE, "userOptions.default.showCurrentNode"); 080 USER_OPTION_KEY_DEFAULT_MAP.put(Preferences.KEYS.USE_OUT_BOX, KewApiConstants.USER_OPTIONS_DEFAULT_USE_OUTBOX_PARAM); 081 USER_OPTION_KEY_DEFAULT_MAP.put(Preferences.KEYS.NOTIFY_ACKNOWLEDGE, "userOptions.default.notifyAcknowledge"); 082 USER_OPTION_KEY_DEFAULT_MAP.put(Preferences.KEYS.NOTIFY_APPROVE, "userOptions.default.notifyApprove"); 083 USER_OPTION_KEY_DEFAULT_MAP.put(Preferences.KEYS.NOTIFY_COMPLETE, "userOptions.default.notifyComplete"); 084 USER_OPTION_KEY_DEFAULT_MAP.put(Preferences.KEYS.NOTIFY_FYI, "userOptions.default.notifyFYI"); 085 } 086 087 088 public Preferences getPreferences(String principalId) { 089 if ( LOG.isDebugEnabled() ) { 090 LOG.debug("start preferences fetch user " + principalId); 091 } 092 Collection<UserOptions> options = getUserOptionService().findByWorkflowUser(principalId); 093 Map<String,UserOptions> optionMap = new HashMap<String, UserOptions>(); 094 Map<String,String> optionValueMap = new HashMap<String, String>(); 095 Map<String, String> documentTypeNotificationPreferences = new HashMap<String, String>(); 096 for ( UserOptions option : options ) { 097 if(option.getOptionId().endsWith(KewApiConstants.DOCUMENT_TYPE_NOTIFICATION_PREFERENCE_SUFFIX)) { 098 String preferenceName = option.getOptionId(); 099 preferenceName = StringUtils.substringBeforeLast(preferenceName, KewApiConstants.DOCUMENT_TYPE_NOTIFICATION_PREFERENCE_SUFFIX); 100 documentTypeNotificationPreferences.put(preferenceName, option.getOptionVal()); 101 } else { 102 optionMap.put(option.getOptionId(), option); 103 } 104 } 105 106 ConfigurationService kcs = KRADServiceLocator.getKualiConfigurationService(); 107 108 boolean isSaveRequired = false; 109 110 for (Map.Entry<String, String> entry : USER_OPTION_KEY_DEFAULT_MAP.entrySet()) { 111 String optionKey = entry.getKey(); 112 String defaultValue = kcs.getPropertyValueAsString(entry.getValue()); 113 if (LOG.isDebugEnabled()) { 114 LOG.debug("start fetch option " + optionKey + " user " + principalId); 115 } 116 117 UserOptions option = optionMap.get(optionKey); 118 if (option == null) { 119 if (LOG.isDebugEnabled()) { 120 LOG.debug("User option '" 121 + optionKey 122 + "' on user " 123 + principalId 124 + " has no stored value. Preferences will require save."); 125 } 126 option = new UserOptions(); 127 option.setWorkflowId(principalId); 128 option.setOptionId(optionKey); 129 option.setOptionVal(defaultValue); 130 optionMap.put(optionKey, option); // just in case referenced a second time 131 132 if (!isSaveRequired) { 133 if (optionKey.equals(Preferences.KEYS.USE_OUT_BOX) && !ConfigContext.getCurrentContextConfig().getOutBoxOn()) { 134 // don't mark as needing save 135 } else { 136 isSaveRequired = true; 137 } 138 } 139 } 140 if (LOG.isDebugEnabled()) { 141 LOG.debug("End fetch option " + optionKey + " user " + principalId); 142 } 143 144 optionValueMap.put(optionKey, option.getOptionVal()); 145 } 146 147 // TODO: JLR - I'm not sure why this isSaveRequired logic is necessary -- couldn't we do something like the following? 148 // if (isSaveRequired) 149 // getUserOptionService().save(principalId, optionValueMap); 150 151 return Preferences.Builder.create(optionValueMap, documentTypeNotificationPreferences, isSaveRequired).build(); 152 } 153 154 public void savePreferences(String principalId, Preferences preferences) { 155 // NOTE: this previously displayed the principalName. Now it's just the id 156 if ( LOG.isDebugEnabled() ) { 157 LOG.debug("saving preferences user " + principalId); 158 } 159 160 validate(preferences); 161 Map<String,String> optionsMap = new HashMap<String,String>(50); 162 163 optionsMap.put(Preferences.KEYS.COLOR_DISAPPROVE_CANCEL, preferences.getColorDisapproveCancel()); 164 optionsMap.put(Preferences.KEYS.COLOR_DISAPPROVED, preferences.getColorDisapproved()); 165 optionsMap.put(Preferences.KEYS.COLOR_APPROVED, preferences.getColorApproved()); 166 optionsMap.put(Preferences.KEYS.COLOR_CANCELED, preferences.getColorCanceled()); 167 optionsMap.put(Preferences.KEYS.COLOR_SAVED, preferences.getColorSaved()); 168 optionsMap.put(Preferences.KEYS.COLOR_ENROUTE, preferences.getColorEnroute()); 169 optionsMap.put(Preferences.KEYS.COLOR_PROCESSED, preferences.getColorProcessed()); 170 optionsMap.put(Preferences.KEYS.COLOR_INITIATED, preferences.getColorInitiated()); 171 optionsMap.put(Preferences.KEYS.COLOR_FINAL, preferences.getColorFinal()); 172 optionsMap.put(Preferences.KEYS.COLOR_EXCEPTION, preferences.getColorException()); 173 optionsMap.put(Preferences.KEYS.REFRESH_RATE, preferences.getRefreshRate().trim()); 174 optionsMap.put(Preferences.KEYS.OPEN_NEW_WINDOW, preferences.getOpenNewWindow()); 175 optionsMap.put(Preferences.KEYS.SHOW_DOC_TYPE, preferences.getShowDocType()); 176 optionsMap.put(Preferences.KEYS.SHOW_DOC_TITLE, preferences.getShowDocTitle()); 177 optionsMap.put(Preferences.KEYS.SHOW_ACTION_REQUESTED, preferences.getShowActionRequested()); 178 optionsMap.put(Preferences.KEYS.SHOW_INITIATOR, preferences.getShowInitiator()); 179 optionsMap.put(Preferences.KEYS.SHOW_DELEGATOR, preferences.getShowDelegator()); 180 optionsMap.put(Preferences.KEYS.SHOW_DATE_CREATED, preferences.getShowDateCreated()); 181 optionsMap.put(Preferences.KEYS.SHOW_DOCUMENT_STATUS, preferences.getShowDocumentStatus()); 182 optionsMap.put(Preferences.KEYS.SHOW_APP_DOC_STATUS, preferences.getShowAppDocStatus()); 183 optionsMap.put(Preferences.KEYS.SHOW_GROUP_REQUEST, preferences.getShowWorkgroupRequest()); 184 optionsMap.put(Preferences.KEYS.SHOW_CLEAR_FYI, preferences.getShowClearFyi()); 185 optionsMap.put(Preferences.KEYS.PAGE_SIZE, preferences.getPageSize().trim()); 186 optionsMap.put(Preferences.KEYS.EMAIL_NOTIFICATION, preferences.getEmailNotification()); 187 optionsMap.put(Preferences.KEYS.NOTIFY_PRIMARY_DELEGATION, preferences.getNotifyPrimaryDelegation()); 188 optionsMap.put(Preferences.KEYS.NOTIFY_SECONDARY_DELEGATION, preferences.getNotifySecondaryDelegation()); 189 optionsMap.put(Preferences.KEYS.DELEGATOR_FILTER, preferences.getDelegatorFilter()); 190 optionsMap.put(Preferences.KEYS.PRIMARY_DELEGATE_FILTER, preferences.getPrimaryDelegateFilter()); 191 optionsMap.put(Preferences.KEYS.SHOW_DATE_APPROVED, preferences.getShowDateApproved()); 192 optionsMap.put(Preferences.KEYS.SHOW_CURRENT_NODE, preferences.getShowCurrentNode()); 193 optionsMap.put(Preferences.KEYS.NOTIFY_ACKNOWLEDGE, preferences.getNotifyAcknowledge()); 194 optionsMap.put(Preferences.KEYS.NOTIFY_APPROVE, preferences.getNotifyApprove()); 195 optionsMap.put(Preferences.KEYS.NOTIFY_COMPLETE, preferences.getNotifyComplete()); 196 optionsMap.put(Preferences.KEYS.NOTIFY_FYI, preferences.getNotifyFYI()); 197 if (ConfigContext.getCurrentContextConfig().getOutBoxOn()) { 198 optionsMap.put(Preferences.KEYS.USE_OUT_BOX, preferences.getUseOutbox()); 199 } 200 for(Entry<String, String> documentTypePreference : preferences.getDocumentTypeNotificationPreferences().entrySet()) { 201 optionsMap.put(documentTypePreference.getKey() + KewApiConstants.DOCUMENT_TYPE_NOTIFICATION_PREFERENCE_SUFFIX, documentTypePreference.getValue()); 202 } 203 getUserOptionService().save(principalId, optionsMap); 204 205 // Find which document type notification preferences have been deleted 206 // and remove them from the database 207 Preferences storedPreferences = this.getPreferences(principalId); 208 for(Entry<String, String> storedEntry : storedPreferences.getDocumentTypeNotificationPreferences().entrySet()) { 209 if(preferences.getDocumentTypeNotificationPreference(storedEntry.getKey()) == null) { 210 getUserOptionService().deleteUserOptions(getUserOptionService().findByOptionId(storedEntry.getKey() + KewApiConstants.DOCUMENT_TYPE_NOTIFICATION_PREFERENCE_SUFFIX, principalId)); 211 } 212 } 213 if ( LOG.isDebugEnabled() ) { 214 LOG.debug("saved preferences user " + principalId); 215 } 216 } 217 218 private void validate(Preferences preferences) { 219 LOG.debug("validating preferences"); 220 221 Collection errors = new ArrayList(); 222 try { 223 new Integer(preferences.getRefreshRate().trim()); 224 } catch (NumberFormatException e) { 225 errors.add(new WorkflowServiceErrorImpl("ActionList Refresh Rate must be in whole " + 226 "minutes", Preferences.KEYS.ERR_KEY_REFRESH_RATE_WHOLE_NUM)); 227 } catch (NullPointerException e1) { 228 errors.add(new WorkflowServiceErrorImpl("ActionList Refresh Rate must be in whole " + 229 "minutes", Preferences.KEYS.ERR_KEY_REFRESH_RATE_WHOLE_NUM)); 230 } 231 232 try { 233 if(new Integer(preferences.getPageSize().trim()) == 0){ 234 errors.add(new WorkflowServiceErrorImpl("ActionList Page Size must be non-zero ", 235 Preferences.KEYS.ERR_KEY_ACTION_LIST_PAGE_SIZE_WHOLE_NUM)); 236 } 237 } catch (NumberFormatException e) { 238 errors.add(new WorkflowServiceErrorImpl("ActionList Page Size must be in whole " + 239 "minutes", Preferences.KEYS.ERR_KEY_ACTION_LIST_PAGE_SIZE_WHOLE_NUM)); 240 } catch (NullPointerException e1) { 241 errors.add(new WorkflowServiceErrorImpl("ActionList Page Size must be in whole " + 242 "minutes", Preferences.KEYS.ERR_KEY_ACTION_LIST_PAGE_SIZE_WHOLE_NUM)); 243 } 244 245 LOG.debug("end validating preferences"); 246 if (! errors.isEmpty()) { 247 throw new WorkflowServiceErrorException("Preference Validation Error", errors); 248 } 249 } 250 251 public UserOptionsService getUserOptionService() { 252 return (UserOptionsService) KEWServiceLocator.getService( 253 KEWServiceLocator.USER_OPTIONS_SRV); 254 } 255 256 private final class UserOptionsWrapper { 257 258 private final UserOptions userOptions; 259 private final boolean isSaveRequired; 260 261 public UserOptionsWrapper(UserOptions userOptions, boolean isSaveRequired) { 262 this.userOptions = userOptions; 263 this.isSaveRequired = isSaveRequired; 264 } 265 266 public UserOptions getUserOptions() { 267 return userOptions; 268 } 269 270 public boolean isSaveRequired() { 271 return isSaveRequired; 272 } 273 } 274 } 275 276