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