View Javadoc

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