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 */
016package org.kuali.rice.kew.preferences;
017
018import static org.junit.Assert.assertFalse;
019import static org.junit.Assert.assertNotNull;
020import static org.junit.Assert.assertNull;
021import static org.junit.Assert.assertTrue;
022
023import java.io.StringReader;
024import java.io.StringWriter;
025import java.util.ArrayList;
026import java.util.Collection;
027import java.util.HashMap;
028import java.util.List;
029import java.util.Map;
030
031import junit.framework.Assert;
032import org.junit.Test;
033import org.kuali.rice.kew.api.KewApiServiceLocator;
034import org.kuali.rice.kew.api.preferences.Preferences;
035import org.kuali.rice.kew.api.preferences.PreferencesService;
036import org.kuali.rice.kew.service.KEWServiceLocator;
037import org.kuali.rice.kew.test.KEWTestCase;
038import org.kuali.rice.kew.useroptions.UserOptions;
039import org.kuali.rice.kew.useroptions.UserOptionsService;
040import org.kuali.rice.kim.api.identity.principal.Principal;
041import org.springframework.transaction.TransactionStatus;
042import org.springframework.transaction.support.TransactionCallback;
043import org.springframework.transaction.support.TransactionTemplate;
044
045import javax.xml.bind.JAXBContext;
046import javax.xml.bind.Marshaller;
047import javax.xml.bind.Unmarshaller;
048
049public class PreferencesServiceTest extends KEWTestCase {
050
051    /**
052     * Test that the preferences are saved by default when going through the preferences service.  This
053     * means that the preferences service will persist any user option that was not in the db when it went
054     * to fetch that preferences.
055     */
056        @Test public void testPreferencesDefaultSave() throws Exception {
057       //verify that user doesn't have any preferences in the db.
058
059       final UserOptionsService userOptionsService = KEWServiceLocator.getUserOptionsService();
060       Principal principal = KEWServiceLocator.getIdentityHelperService().getPrincipalByPrincipalName("rkirkend");
061       Collection userOptions = userOptionsService.findByWorkflowUser(principal.getPrincipalId());
062       assertTrue("UserOptions should be empty", userOptions.isEmpty());
063
064       PreferencesService preferencesService = KewApiServiceLocator.getPreferencesService();
065       Preferences preferences = preferencesService.getPreferences(principal.getPrincipalId());
066       assertTrue("Preferences should require a save.", preferences.isRequiresSave());
067
068       userOptions = userOptionsService.findByWorkflowUser(principal.getPrincipalId());
069       assertTrue("UserOptions should not empty", userOptions.isEmpty());
070
071       preferencesService.savePreferences(principal.getPrincipalId(), preferences);
072       userOptions = userOptionsService.findByWorkflowUser(principal.getPrincipalId());
073       assertTrue("UserOptions should not be empty", !userOptions.isEmpty());
074
075       preferences = preferencesService.getPreferences(principal.getPrincipalId());
076       assertFalse("Preferences should NOT require a save.", preferences.isRequiresSave());
077
078       // now delete one of the options
079       final UserOptions refreshRateOption = userOptionsService.findByOptionId("REFRESH_RATE", principal.getPrincipalId());
080       assertNotNull("REFRESH_RATE option should exist.", refreshRateOption);
081       TransactionTemplate template = new TransactionTemplate(KEWServiceLocator.getPlatformTransactionManager());
082       template.execute(new TransactionCallback() {
083           public Object doInTransaction(TransactionStatus status) {
084               userOptionsService.deleteUserOptions(refreshRateOption);
085               return null;
086           }
087       });
088       assertNull("REFRESH_RATE option should no longer exist.", userOptionsService.findByOptionId("REFRESH_RATE", principal.getPrincipalId()));
089
090       preferences = preferencesService.getPreferences(principal.getPrincipalId());
091       assertTrue("Preferences should now require a save again.", preferences.isRequiresSave());
092
093       // save refresh rate again
094       template.execute(new TransactionCallback() {
095           public Object doInTransaction(TransactionStatus status) {
096               userOptionsService.save(refreshRateOption);
097               return null;
098           }
099       });
100       preferences = preferencesService.getPreferences(principal.getPrincipalId());
101       assertFalse("Preferences should no longer require a save.", preferences.isRequiresSave());
102    }
103
104    @Test
105    public void testPreferencesMarshallingWithInvalidJson() {
106        final UserOptionsService userOptionsService = KEWServiceLocator.getUserOptionsService();
107        Principal principal = KEWServiceLocator.getIdentityHelperService().getPrincipalByPrincipalName("ewestfal");
108        Collection<UserOptions> userOptions = userOptionsService.findByWorkflowUser(principal.getPrincipalId());
109        assertTrue("UserOptions should be empty", userOptions.isEmpty());
110
111        PreferencesService preferencesService = KewApiServiceLocator.getPreferencesService();
112        Preferences preferences = preferencesService.getPreferences(principal.getPrincipalId());
113        assertTrue("Preferences should require a save.", preferences.isRequiresSave());
114        preferencesService.savePreferences(principal.getPrincipalId(), preferences);
115        userOptions = userOptionsService.findByWorkflowUser(principal.getPrincipalId());
116
117        UserOptions docSearchOrder = new UserOptions();
118        docSearchOrder.setOptionId("DocSearch.LastSearch.Order");
119        docSearchOrder.setWorkflowId(principal.getPrincipalId());
120        docSearchOrder.setOptionVal("DocSearch.LastSearch.Holding0");
121
122        UserOptions badJsonOption = new UserOptions();
123        badJsonOption.setOptionId("DocSearch.LastSearch.Holding0");
124        badJsonOption.setWorkflowId(principal.getPrincipalId());
125
126        //this be invalid
127        badJsonOption.setOptionVal("{isAdvancedSearch\":\"NO\",\"dateCreatedFrom\":1339168393063,\"documentStatuses\":[],\"documentStatusCategories\":[],\"documentAttributeValues\":{},\"additionalDocumentTypeNames\":[]}");
128
129        userOptionsService.save(docSearchOrder);
130        userOptionsService.save(badJsonOption);
131
132
133        userOptions = userOptionsService.findByWorkflowUser(principal.getPrincipalId());
134        assertTrue("UserOptions should not empty", !userOptions.isEmpty());
135
136        //UserOptions previousDocSearch0 = userOptionsService.findByOptionId("DocSearch.LastSearch.Holding0", principal.getPrincipalId());
137        preferences = preferencesService.getPreferences(principal.getPrincipalId());
138        Preferences.Builder pBuilder = Preferences.Builder.create(preferences);
139        Map<String, String> docTypeNotification = new HashMap<String, String>();
140
141        docTypeNotification.put("hello", "world");
142        docTypeNotification.put("does_this_thing_have_a", "bookstore example");
143        pBuilder.setDocumentTypeNotificationPreferences(docTypeNotification);
144
145        String marshaledXml = null;
146        try {
147            JAXBContext jaxbContext = JAXBContext.newInstance(Preferences.class);
148            Marshaller marshaller = jaxbContext.createMarshaller();
149
150            StringWriter stringWriter = new StringWriter();
151
152            marshaller.marshal(pBuilder.build(), stringWriter);
153
154            marshaledXml = stringWriter.toString();
155
156            Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
157            Preferences actual = (Preferences)unmarshaller.unmarshal(new StringReader(marshaledXml));
158            //Object expected = unmarshaller.unmarshal(new StringReader(XML))
159            Assert.assertEquals(pBuilder.build(), actual);
160
161        } catch (Throwable e) {
162            e.printStackTrace();
163        }
164
165
166    }
167
168
169        /**
170     * Tests default saving concurrently which can cause a race condition on startup
171     * that leads to constraint violations
172     */
173    @Test public void testPreferencesConcurrentDefaultSave() throws Throwable {
174       //verify that user doesn't have any preferences in the db.
175       final UserOptionsService userOptionsService = KEWServiceLocator.getUserOptionsService();
176       final Principal principal = KEWServiceLocator.getIdentityHelperService().getPrincipalByPrincipalName("rkirkend");
177       Collection userOptions = userOptionsService.findByWorkflowUser(principal.getPrincipalId());
178       assertTrue("UserOptions should be empty", userOptions.isEmpty());
179
180       final PreferencesService preferencesService = KewApiServiceLocator.getPreferencesService();
181       Runnable getPrefRunnable = new Runnable() {
182           public void run() {
183               Preferences preferences = preferencesService.getPreferences(principal.getPrincipalId());
184               assertTrue("Preferences should require a save.", preferences.isRequiresSave());
185               Collection updatedOptions = userOptionsService.findByWorkflowUser(principal.getPrincipalId());
186               assertTrue("UserOptions should be empty", updatedOptions.isEmpty());
187           }
188       };
189       final List<Throwable> errors = new ArrayList<Throwable>();
190       Thread.UncaughtExceptionHandler ueh = new Thread.UncaughtExceptionHandler() {
191           public void uncaughtException(Thread thread, Throwable error) {
192               errors.add(error);
193           }
194       };
195
196       // 3 threads should do
197       Thread t1 = new Thread(getPrefRunnable);
198       Thread t2 = new Thread(getPrefRunnable);
199       Thread t3 = new Thread(getPrefRunnable);
200       t1.setUncaughtExceptionHandler(ueh);
201       t2.setUncaughtExceptionHandler(ueh);
202       t3.setUncaughtExceptionHandler(ueh);
203       t1.start();
204       t2.start();
205       t3.start();
206       t1.join();
207       t2.join();
208       t3.join();
209
210       if (errors.size() > 0) {
211           throw errors.iterator().next();
212       }
213
214       Preferences preferences = preferencesService.getPreferences(principal.getPrincipalId());
215       assertTrue("Preferences should require a save.", preferences.isRequiresSave());
216       Collection updatedOptions = userOptionsService.findByWorkflowUser(principal.getPrincipalId());
217       assertTrue("UserOptions should be empty", updatedOptions.isEmpty());
218       preferencesService.savePreferences(principal.getPrincipalId(), preferences);
219       updatedOptions = userOptionsService.findByWorkflowUser(principal.getPrincipalId());
220       assertTrue("UserOptions should not be empty", !updatedOptions.isEmpty());
221       t1.stop();
222    }
223}