001 /** 002 * Copyright 2005-2014 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.kns.service.impl; 017 018 import org.apache.commons.collections.map.LRUMap; 019 import org.apache.commons.lang.StringUtils; 020 import org.apache.log4j.Logger; 021 import org.kuali.rice.core.api.CoreApiServiceLocator; 022 import org.kuali.rice.core.api.encryption.EncryptionService; 023 import org.kuali.rice.kew.api.WorkflowDocument; 024 import org.kuali.rice.kns.document.authorization.DocumentAuthorizerBase; 025 import org.kuali.rice.kns.service.SessionDocumentService; 026 import org.kuali.rice.kns.web.struts.form.KualiDocumentFormBase; 027 import org.kuali.rice.krad.UserSession; 028 import org.kuali.rice.krad.UserSessionUtils; 029 import org.kuali.rice.krad.bo.SessionDocument; 030 import org.kuali.rice.krad.dao.SessionDocumentDao; 031 import org.kuali.rice.krad.datadictionary.DocumentEntry; 032 import org.kuali.rice.krad.service.BusinessObjectService; 033 import org.kuali.rice.krad.service.DataDictionaryService; 034 import org.kuali.rice.krad.service.KRADServiceLocatorWeb; 035 import org.kuali.rice.krad.util.KRADConstants; 036 import org.springframework.beans.factory.InitializingBean; 037 import org.springframework.transaction.annotation.Transactional; 038 039 import java.io.ByteArrayInputStream; 040 import java.io.ByteArrayOutputStream; 041 import java.io.ObjectInputStream; 042 import java.io.ObjectOutputStream; 043 import java.sql.Timestamp; 044 import java.util.Collections; 045 import java.util.HashMap; 046 import java.util.Map; 047 import java.util.concurrent.ConcurrentHashMap; 048 049 /** 050 * Implementation of <code>SessionDocumentService</code> that persists the document form 051 * contents to the underlying database 052 * 053 * @author Kuali Rice Team (rice.collab@kuali.org) 054 */ 055 @Deprecated 056 @Transactional 057 public class SessionDocumentServiceImpl implements SessionDocumentService, InitializingBean { 058 private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(SessionDocumentServiceImpl.class); 059 060 protected static final String IP_ADDRESS = "ipAddress"; 061 protected static final String PRINCIPAL_ID = "principalId"; 062 protected static final String DOCUMENT_NUMBER = "documentNumber"; 063 protected static final String SESSION_ID = "sessionId"; 064 065 private Map<String, CachedObject> cachedObjects; 066 private EncryptionService encryptionService; 067 private int maxCacheSize; 068 069 private BusinessObjectService businessObjectService; 070 private DataDictionaryService dataDictionaryService; 071 private SessionDocumentDao sessionDocumentDao; 072 073 private static class CachedObject { 074 private UserSession userSession; 075 private String formKey; 076 077 CachedObject(UserSession userSession, String formKey) { 078 this.userSession = userSession; 079 this.formKey = formKey; 080 } 081 082 @Override 083 public String toString() { 084 return "CachedObject: principalId=" + userSession.getPrincipalId() + " / objectWithFormKey=" + 085 userSession.retrieveObject(formKey); 086 } 087 088 public UserSession getUserSession() { 089 return this.userSession; 090 } 091 092 public String getFormKey() { 093 return this.formKey; 094 } 095 } 096 097 /** 098 * Override LRUMap removeEntity method 099 * 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 UserSessionUtils.addWorkflowDocument(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 return UserSessionUtils.getWorkflowDocument(userSession, docId); 198 } 199 200 /** 201 * @see org.kuali.rice.krad.service.SessionDocumentService#addDocumentToUserSession(org.kuali.rice.krad.UserSession, 202 * org.kuali.rice.kew.api.WorkflowDocument) 203 */ 204 @Override 205 public void addDocumentToUserSession(UserSession userSession, WorkflowDocument document) { 206 UserSessionUtils.addWorkflowDocument(userSession, document); 207 } 208 209 /** 210 * @see org.kuali.rice.krad.service.SessionDocumentService#purgeDocumentForm(String 211 * documentNumber, String docFormKey, UserSession userSession) 212 */ 213 @Override 214 public void purgeDocumentForm(String documentNumber, String docFormKey, UserSession userSession, String ipAddress) { 215 synchronized (userSession) { 216 217 LOG.debug("purge document form from session"); 218 userSession.removeObject(docFormKey); 219 try { 220 LOG.debug("purge document form from database"); 221 HashMap<String, String> primaryKeys = new HashMap<String, String>(4); 222 primaryKeys.put(SESSION_ID, userSession.getKualiSessionId()); 223 primaryKeys.put(DOCUMENT_NUMBER, documentNumber); 224 primaryKeys.put(PRINCIPAL_ID, userSession.getPrincipalId()); 225 primaryKeys.put(IP_ADDRESS, ipAddress); 226 getBusinessObjectService().deleteMatching(SessionDocument.class, primaryKeys); 227 } catch (Exception e) { 228 LOG.error("purgeDocumentForm failed for SessId/DocNum/PrinId/IP:" + userSession.getKualiSessionId() + 229 "/" + documentNumber + "/" + userSession.getPrincipalId() + "/" + ipAddress, e); 230 } 231 } 232 } 233 234 @Override 235 public void setDocumentForm(KualiDocumentFormBase form, UserSession userSession, String ipAddress) { 236 synchronized (userSession) { 237 //formKey was set in KualiDocumentActionBase execute method 238 String formKey = form.getFormKey(); 239 String key = userSession.getKualiSessionId() + "-" + formKey; 240 cachedObjects.put(key, new CachedObject(userSession, formKey)); 241 242 String documentNumber = form.getDocument().getDocumentNumber(); 243 244 if (StringUtils.isNotBlank(documentNumber)) { 245 persistDocumentForm(form, userSession, ipAddress, userSession.getKualiSessionId(), documentNumber); 246 } else { 247 LOG.warn("documentNumber is null on form's document: " + form); 248 } 249 } 250 } 251 252 protected void persistDocumentForm(Object form, UserSession userSession, String ipAddress, String sessionId, 253 String documentNumber) { 254 try { 255 LOG.debug("set Document Form into database"); 256 Timestamp currentTime = new Timestamp(System.currentTimeMillis()); 257 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 258 ObjectOutputStream oos = new ObjectOutputStream(baos); 259 oos.writeObject(form); 260 // serialize the KualiDocumentFormBase object into a byte array 261 byte[] formAsBytes = baos.toByteArray(); 262 boolean encryptContent = false; 263 264 if ((form instanceof KualiDocumentFormBase) && ((KualiDocumentFormBase) form).getDocTypeName() != null) { 265 DocumentEntry documentEntry = getDataDictionaryService().getDataDictionary() 266 .getDocumentEntry(((KualiDocumentFormBase) form).getDocTypeName()); 267 if (documentEntry != null) { 268 encryptContent = documentEntry.isEncryptDocumentDataInPersistentSessionStorage(); 269 } 270 } 271 if (encryptContent) { 272 formAsBytes = getEncryptionService().encryptBytes(formAsBytes); 273 } 274 275 // check if a record is already there in the database 276 // this may only happen under jMeter testing, but there is no way to be sure 277 HashMap<String, String> primaryKeys = new HashMap<String, String>(4); 278 primaryKeys.put(SESSION_ID, sessionId); 279 primaryKeys.put(DOCUMENT_NUMBER, documentNumber); 280 primaryKeys.put(PRINCIPAL_ID, userSession.getPrincipalId()); 281 primaryKeys.put(IP_ADDRESS, ipAddress); 282 283 SessionDocument sessionDocument = 284 getBusinessObjectService().findByPrimaryKey(SessionDocument.class, primaryKeys); 285 if (sessionDocument == null) { 286 sessionDocument = new SessionDocument(); 287 sessionDocument.setSessionId(sessionId); 288 sessionDocument.setDocumentNumber(documentNumber); 289 sessionDocument.setPrincipalId(userSession.getPrincipalId()); 290 sessionDocument.setIpAddress(ipAddress); 291 } 292 sessionDocument.setSerializedDocumentForm(formAsBytes); 293 sessionDocument.setEncrypted(encryptContent); 294 sessionDocument.setLastUpdatedDate(currentTime); 295 296 businessObjectService.save(sessionDocument); 297 } catch (Exception e) { 298 final String className = form != null ? form.getClass().getName() : "null"; 299 LOG.error("setDocumentForm failed for SessId/DocNum/PrinId/IP/class:" + userSession.getKualiSessionId() + 300 "/" + documentNumber + "/" + userSession.getPrincipalId() + "/" + ipAddress + "/" + className, e); 301 } 302 } 303 304 /** 305 * @see org.kuali.rice.krad.service.SessionDocumentService#purgeAllSessionDocuments(java.sql.Timestamp) 306 */ 307 @Override 308 public void purgeAllSessionDocuments(Timestamp expirationDate) { 309 sessionDocumentDao.purgeAllSessionDocuments(expirationDate); 310 } 311 312 protected SessionDocumentDao getSessionDocumentDao() { 313 return this.sessionDocumentDao; 314 } 315 316 public void setSessionDocumentDao(SessionDocumentDao sessionDocumentDao) { 317 this.sessionDocumentDao = sessionDocumentDao; 318 } 319 320 protected BusinessObjectService getBusinessObjectService() { 321 return this.businessObjectService; 322 } 323 324 public void setBusinessObjectService(BusinessObjectService businessObjectService) { 325 this.businessObjectService = businessObjectService; 326 } 327 328 public int getMaxCacheSize() { 329 return maxCacheSize; 330 } 331 332 public void setMaxCacheSize(int maxCacheSize) { 333 this.maxCacheSize = maxCacheSize; 334 } 335 336 protected EncryptionService getEncryptionService() { 337 if (encryptionService == null) { 338 encryptionService = CoreApiServiceLocator.getEncryptionService(); 339 } 340 return encryptionService; 341 } 342 343 protected DataDictionaryService getDataDictionaryService() { 344 if (dataDictionaryService == null) { 345 dataDictionaryService = KRADServiceLocatorWeb.getDataDictionaryService(); 346 } 347 return dataDictionaryService; 348 } 349 }