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