View Javadoc

1   /**
2    * Copyright 2005-2012 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.kns.service.impl;
17  
18  import org.apache.commons.collections.map.LRUMap;
19  import org.apache.commons.lang.StringUtils;
20  import org.apache.log4j.Logger;
21  import org.kuali.rice.core.api.CoreApiServiceLocator;
22  import org.kuali.rice.core.api.encryption.EncryptionService;
23  import org.kuali.rice.kew.api.KewApiConstants;
24  import org.kuali.rice.kew.api.WorkflowDocument;
25  import org.kuali.rice.kns.document.authorization.DocumentAuthorizerBase;
26  import org.kuali.rice.kns.service.SessionDocumentService;
27  import org.kuali.rice.kns.web.struts.form.KualiDocumentFormBase;
28  import org.kuali.rice.krad.UserSession;
29  import org.kuali.rice.krad.bo.SessionDocument;
30  import org.kuali.rice.krad.dao.SessionDocumentDao;
31  import org.kuali.rice.krad.datadictionary.DocumentEntry;
32  import org.kuali.rice.krad.service.BusinessObjectService;
33  import org.kuali.rice.krad.service.DataDictionaryService;
34  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
35  import org.kuali.rice.krad.util.KRADConstants;
36  import org.springframework.beans.factory.InitializingBean;
37  import org.springframework.transaction.annotation.Transactional;
38  
39  import java.io.ByteArrayInputStream;
40  import java.io.ByteArrayOutputStream;
41  import java.io.ObjectInputStream;
42  import java.io.ObjectOutputStream;
43  import java.sql.Timestamp;
44  import java.util.Collections;
45  import java.util.HashMap;
46  import java.util.Map;
47  import java.util.concurrent.ConcurrentHashMap;
48  
49  /**
50   * Implementation of <code>SessionDocumentService</code> that persists the document form
51   * contents to the underlying database
52   *
53   * @author Kuali Rice Team (rice.collab@kuali.org)
54   */
55  @Deprecated
56  @Transactional
57  public class SessionDocumentServiceImpl implements SessionDocumentService, InitializingBean {
58      private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(SessionDocumentServiceImpl.class);
59  
60      protected static final String IP_ADDRESS = "ipAddress";
61      protected static final String PRINCIPAL_ID = "principalId";
62      protected static final String DOCUMENT_NUMBER = "documentNumber";
63      protected static final String SESSION_ID = "sessionId";
64  
65      private Map<String, CachedObject> cachedObjects;
66      private EncryptionService encryptionService;
67      private int maxCacheSize;
68  
69      private BusinessObjectService businessObjectService;
70      private DataDictionaryService dataDictionaryService;
71      private SessionDocumentDao sessionDocumentDao;
72  
73      private static class CachedObject {
74          private UserSession userSession;
75          private String formKey;
76  
77          CachedObject(UserSession userSession, String formKey) {
78              this.userSession = userSession;
79              this.formKey = formKey;
80          }
81  
82          @Override
83          public String toString() {
84              return "CachedObject: principalId=" + userSession.getPrincipalId() + " / objectWithFormKey=" +
85                      userSession.retrieveObject(formKey);
86          }
87  
88          public UserSession getUserSession() {
89              return this.userSession;
90          }
91  
92          public String getFormKey() {
93              return this.formKey;
94          }
95      }
96  
97      /**
98       * Override LRUMap removeEntity method
99       *
100      *
101      */
102     private static class KualiLRUMap extends LRUMap {
103 
104         /** Serialization version */
105         private static final long serialVersionUID = 1L;
106 
107         private KualiLRUMap() {
108             super();
109         }
110 
111         private KualiLRUMap(int maxSize) {
112             super(maxSize);
113         }
114 
115         @Override
116         protected void removeEntry(HashEntry entry, int hashIndex, HashEntry previous) {
117 
118             // It is for session document cache enhancement.
119             // To control the size of cache. When the LRUMap reach the maxsize.
120             // It will remove session document entries from the in-memory user
121             // session objects.
122             try {
123                 CachedObject cachedObject
124                         = (CachedObject)this.entryValue(entry);
125                 cachedObject.getUserSession().removeObject(cachedObject.getFormKey());
126             } catch (Exception ex) {
127                 Logger.getLogger(getClass()).warn( "Problem purging old entry from the user session when removing from the map: ", ex);
128             }
129 
130             super.removeEntry(entry, hashIndex, previous);
131         }
132 
133     }
134 
135     @Override
136     @SuppressWarnings("unchecked")
137     public void afterPropertiesSet() throws Exception {
138         cachedObjects = Collections.synchronizedMap(new KualiLRUMap(maxCacheSize));
139     }
140 
141 
142     @Override
143     public KualiDocumentFormBase getDocumentForm(String documentNumber, String docFormKey, UserSession userSession,
144             String ipAddress) {
145         KualiDocumentFormBase documentForm = null;
146 
147         LOG.debug("getDocumentForm KualiDocumentFormBase from db");
148         try {
149             // re-create the KualiDocumentFormBase object
150             documentForm = (KualiDocumentFormBase) retrieveDocumentForm(userSession, userSession.getKualiSessionId(),
151                     documentNumber, ipAddress);
152 
153             //re-store workFlowDocument into session
154             if (!(StringUtils.equals((String)userSession.retrieveObject(DocumentAuthorizerBase.USER_SESSION_METHOD_TO_CALL_OBJECT_KEY),
155                     KRADConstants.TableRenderConstants.SORT_METHOD) ||
156                   StringUtils.equals((String)userSession.retrieveObject(DocumentAuthorizerBase.USER_SESSION_METHOD_TO_CALL_OBJECT_KEY),
157                     KRADConstants.PARAM_MAINTENANCE_VIEW_MODE_INQUIRY))) {
158                         WorkflowDocument workflowDocument =
159                             documentForm.getDocument().getDocumentHeader().getWorkflowDocument();
160                         addDocumentToUserSession(userSession, workflowDocument);
161             }
162         } catch (Exception e) {
163             LOG.error("getDocumentForm failed for SessId/DocNum/PrinId/IP:" + userSession.getKualiSessionId() + "/" +
164                     documentNumber + "/" + userSession.getPrincipalId() + "/" + ipAddress, e);
165         }
166 
167         return documentForm;
168     }
169 
170     protected Object retrieveDocumentForm(UserSession userSession, String sessionId, String documentNumber,
171             String ipAddress) throws Exception {
172         HashMap<String, String> primaryKeys = new HashMap<String, String>(4);
173         primaryKeys.put(SESSION_ID, sessionId);
174         if (documentNumber != null) {
175             primaryKeys.put(DOCUMENT_NUMBER, documentNumber);
176         }
177         primaryKeys.put(PRINCIPAL_ID, userSession.getPrincipalId());
178         primaryKeys.put(IP_ADDRESS, ipAddress);
179 
180         SessionDocument sessionDoc = getBusinessObjectService().findByPrimaryKey(SessionDocument.class, primaryKeys);
181         if (sessionDoc != null) {
182             byte[] formAsBytes = sessionDoc.getSerializedDocumentForm();
183             if (sessionDoc.isEncrypted()) {
184                 formAsBytes = getEncryptionService().decryptBytes(formAsBytes);
185             }
186             ByteArrayInputStream baip = new ByteArrayInputStream(formAsBytes);
187             ObjectInputStream ois = new ObjectInputStream(baip);
188 
189             return ois.readObject();
190         }
191 
192         return null;
193     }
194 
195     @Override
196     public WorkflowDocument getDocumentFromSession(UserSession userSession, String docId) {
197         synchronized (userSession) {
198         @SuppressWarnings("unchecked") Map<String, WorkflowDocument> workflowDocMap =
199                 (Map<String, WorkflowDocument>) userSession
200                         .retrieveObject(KewApiConstants.WORKFLOW_DOCUMENT_MAP_ATTR_NAME);
201 
202             if (workflowDocMap == null) {
203                 workflowDocMap = new ConcurrentHashMap<String, WorkflowDocument> ();
204                 userSession.addObject(KewApiConstants.WORKFLOW_DOCUMENT_MAP_ATTR_NAME, workflowDocMap);
205                 return null;
206             }
207             return workflowDocMap.get(docId);
208         }
209     }
210 
211     /**
212      * @see org.kuali.rice.krad.service.SessionDocumentService#addDocumentToUserSession(org.kuali.rice.krad.UserSession,
213      *      org.kuali.rice.krad.workflow.service.KualiWorkflowDocument)
214      */
215     @Override
216     public void addDocumentToUserSession(UserSession userSession, WorkflowDocument document) {
217         synchronized (userSession) {
218             @SuppressWarnings("unchecked") Map<String, WorkflowDocument> workflowDocMap =
219                 (Map<String, WorkflowDocument>) userSession
220                         .retrieveObject(KewApiConstants.WORKFLOW_DOCUMENT_MAP_ATTR_NAME);
221             if (workflowDocMap == null) {
222                 workflowDocMap = new ConcurrentHashMap<String, WorkflowDocument> ();
223             }
224             if(document != null && document.getDocumentId() != null) {
225                 workflowDocMap.put(document.getDocumentId(), document);
226             }
227             userSession.addObject(KewApiConstants.WORKFLOW_DOCUMENT_MAP_ATTR_NAME, workflowDocMap);
228         }
229     }
230 
231     /**
232      * @see org.kuali.rice.krad.service.SessionDocumentService#purgeDocumentForm(String
233      *      documentNumber, String docFormKey, UserSession userSession)
234      */
235     @Override
236     public void purgeDocumentForm(String documentNumber, String docFormKey, UserSession userSession, String ipAddress) {
237         synchronized (userSession) {
238 
239             LOG.debug("purge document form from session");
240             userSession.removeObject(docFormKey);
241             try {
242                 LOG.debug("purge document form from database");
243                 HashMap<String, String> primaryKeys = new HashMap<String, String>(4);
244                 primaryKeys.put(SESSION_ID, userSession.getKualiSessionId());
245                 primaryKeys.put(DOCUMENT_NUMBER, documentNumber);
246                 primaryKeys.put(PRINCIPAL_ID, userSession.getPrincipalId());
247                 primaryKeys.put(IP_ADDRESS, ipAddress);
248                 getBusinessObjectService().deleteMatching(SessionDocument.class, primaryKeys);
249             } catch (Exception e) {
250                 LOG.error("purgeDocumentForm failed for SessId/DocNum/PrinId/IP:" + userSession.getKualiSessionId() +
251                         "/" + documentNumber + "/" + userSession.getPrincipalId() + "/" + ipAddress, e);
252             }
253         }
254     }
255 
256     @Override
257     public void setDocumentForm(KualiDocumentFormBase form, UserSession userSession, String ipAddress) {
258         synchronized (userSession) {
259             //formKey was set in KualiDocumentActionBase execute method
260             String formKey = form.getFormKey();
261             String key = userSession.getKualiSessionId() + "-" + formKey;
262             cachedObjects.put(key, new CachedObject(userSession, formKey));
263 
264             String documentNumber = form.getDocument().getDocumentNumber();
265 
266             if (StringUtils.isNotBlank(documentNumber)) {
267                 persistDocumentForm(form, userSession, ipAddress, userSession.getKualiSessionId(), documentNumber);
268             } else {
269                 LOG.warn("documentNumber is null on form's document: " + form);
270             }
271         }
272     }
273 
274     protected void persistDocumentForm(Object form, UserSession userSession, String ipAddress, String sessionId,
275             String documentNumber) {
276         try {
277             LOG.debug("set Document Form into database");
278             Timestamp currentTime = new Timestamp(System.currentTimeMillis());
279             ByteArrayOutputStream baos = new ByteArrayOutputStream();
280             ObjectOutputStream oos = new ObjectOutputStream(baos);
281             oos.writeObject(form);
282             // serialize the KualiDocumentFormBase object into a byte array
283             byte[] formAsBytes = baos.toByteArray();
284             boolean encryptContent = false;
285 
286             if ((form instanceof KualiDocumentFormBase) && ((KualiDocumentFormBase) form).getDocTypeName() != null) {
287                 DocumentEntry documentEntry = getDataDictionaryService().getDataDictionary()
288                         .getDocumentEntry(((KualiDocumentFormBase) form).getDocTypeName());
289                 if (documentEntry != null) {
290                     encryptContent = documentEntry.isEncryptDocumentDataInPersistentSessionStorage();
291                 }
292             }
293             if (encryptContent) {
294                 formAsBytes = getEncryptionService().encryptBytes(formAsBytes);
295             }
296 
297             // check if a record is already there in the database
298             // this may only happen under jMeter testing, but there is no way to be sure
299             HashMap<String, String> primaryKeys = new HashMap<String, String>(4);
300             primaryKeys.put(SESSION_ID, sessionId);
301             primaryKeys.put(DOCUMENT_NUMBER, documentNumber);
302             primaryKeys.put(PRINCIPAL_ID, userSession.getPrincipalId());
303             primaryKeys.put(IP_ADDRESS, ipAddress);
304 
305             SessionDocument sessionDocument =
306                     getBusinessObjectService().findByPrimaryKey(SessionDocument.class, primaryKeys);
307             if (sessionDocument == null) {
308                 sessionDocument = new SessionDocument();
309                 sessionDocument.setSessionId(sessionId);
310                 sessionDocument.setDocumentNumber(documentNumber);
311                 sessionDocument.setPrincipalId(userSession.getPrincipalId());
312                 sessionDocument.setIpAddress(ipAddress);
313             }
314             sessionDocument.setSerializedDocumentForm(formAsBytes);
315             sessionDocument.setEncrypted(encryptContent);
316             sessionDocument.setLastUpdatedDate(currentTime);
317 
318             businessObjectService.save(sessionDocument);
319         } catch (Exception e) {
320             final String className = form != null ? form.getClass().getName() : "null";
321             LOG.error("setDocumentForm failed for SessId/DocNum/PrinId/IP/class:" + userSession.getKualiSessionId() +
322                     "/" + documentNumber + "/" + userSession.getPrincipalId() + "/" + ipAddress + "/" + className, e);
323         }
324     }
325 
326     /**
327      * @see org.kuali.rice.krad.service.SessionDocumentService#purgeAllSessionDocuments(java.sql.Timestamp)
328      */
329     @Override
330     public void purgeAllSessionDocuments(Timestamp expirationDate) {
331         sessionDocumentDao.purgeAllSessionDocuments(expirationDate);
332     }
333 
334     protected SessionDocumentDao getSessionDocumentDao() {
335         return this.sessionDocumentDao;
336     }
337 
338     public void setSessionDocumentDao(SessionDocumentDao sessionDocumentDao) {
339         this.sessionDocumentDao = sessionDocumentDao;
340     }
341 
342     protected BusinessObjectService getBusinessObjectService() {
343         return this.businessObjectService;
344     }
345 
346     public void setBusinessObjectService(BusinessObjectService businessObjectService) {
347         this.businessObjectService = businessObjectService;
348     }
349 
350     public int getMaxCacheSize() {
351         return maxCacheSize;
352     }
353 
354     public void setMaxCacheSize(int maxCacheSize) {
355         this.maxCacheSize = maxCacheSize;
356     }
357 
358     protected EncryptionService getEncryptionService() {
359         if (encryptionService == null) {
360             encryptionService = CoreApiServiceLocator.getEncryptionService();
361         }
362         return encryptionService;
363     }
364 
365     protected DataDictionaryService getDataDictionaryService() {
366         if (dataDictionaryService == null) {
367             dataDictionaryService = KRADServiceLocatorWeb.getDataDictionaryService();
368         }
369         return dataDictionaryService;
370     }
371 }