001/** 002 * Copyright 2005-2016 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.kns.service.impl; 017 018import org.apache.commons.collections.map.LRUMap; 019import org.apache.commons.lang.StringUtils; 020import org.apache.log4j.Logger; 021import org.kuali.rice.core.api.CoreApiServiceLocator; 022import org.kuali.rice.core.api.encryption.EncryptionService; 023import org.kuali.rice.kew.api.WorkflowDocument; 024import org.kuali.rice.kns.document.authorization.DocumentAuthorizerBase; 025import org.kuali.rice.kns.service.SessionDocumentService; 026import org.kuali.rice.kns.web.struts.form.KualiDocumentFormBase; 027import org.kuali.rice.krad.UserSession; 028import org.kuali.rice.krad.UserSessionUtils; 029import org.kuali.rice.krad.bo.SessionDocument; 030import org.kuali.rice.krad.dao.SessionDocumentDao; 031import org.kuali.rice.krad.datadictionary.DocumentEntry; 032import org.kuali.rice.krad.service.BusinessObjectService; 033import org.kuali.rice.krad.service.DataDictionaryService; 034import org.kuali.rice.krad.service.KRADServiceLocatorWeb; 035import org.kuali.rice.krad.util.KRADConstants; 036import org.springframework.beans.factory.InitializingBean; 037import org.springframework.transaction.annotation.Transactional; 038 039import java.io.ByteArrayInputStream; 040import java.io.ByteArrayOutputStream; 041import java.io.ObjectInputStream; 042import java.io.ObjectOutputStream; 043import java.sql.Timestamp; 044import java.util.Collections; 045import java.util.HashMap; 046import java.util.Map; 047 048/** 049 * Implementation of <code>SessionDocumentService</code> that persists the document form 050 * contents to the underlying database 051 * 052 * @author Kuali Rice Team (rice.collab@kuali.org) 053 */ 054@Deprecated 055@Transactional 056public class SessionDocumentServiceImpl implements SessionDocumentService, InitializingBean { 057 private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(SessionDocumentServiceImpl.class); 058 059 protected static final String IP_ADDRESS = "ipAddress"; 060 protected static final String PRINCIPAL_ID = "principalId"; 061 protected static final String DOCUMENT_NUMBER = "documentNumber"; 062 protected static final String SESSION_ID = "sessionId"; 063 064 private Map<String, CachedObject> cachedObjects; 065 private EncryptionService encryptionService; 066 private int maxCacheSize; 067 068 private BusinessObjectService businessObjectService; 069 private DataDictionaryService dataDictionaryService; 070 private SessionDocumentDao sessionDocumentDao; 071 072 private static class CachedObject { 073 private UserSession userSession; 074 private String formKey; 075 076 CachedObject(UserSession userSession, String formKey) { 077 this.userSession = userSession; 078 this.formKey = formKey; 079 } 080 081 @Override 082 public String toString() { 083 return "CachedObject: principalId=" + userSession.getPrincipalId() + " / objectWithFormKey=" + 084 userSession.retrieveObject(formKey); 085 } 086 087 public UserSession getUserSession() { 088 return this.userSession; 089 } 090 091 public String getFormKey() { 092 return this.formKey; 093 } 094 } 095 096 /** 097 * Override LRUMap removeEntity method 098 * 099 * 100 */ 101 private static class KualiLRUMap extends LRUMap { 102 103 /** Serialization version */ 104 private static final long serialVersionUID = 1L; 105 106 private KualiLRUMap() { 107 super(); 108 } 109 110 private KualiLRUMap(int maxSize) { 111 super(maxSize); 112 } 113 114 @Override 115 protected void removeEntry(HashEntry entry, int hashIndex, HashEntry previous) { 116 117 // It is for session document cache enhancement. 118 // To control the size of cache. When the LRUMap reach the maxsize. 119 // It will remove session document entries from the in-memory user 120 // session objects. 121 try { 122 CachedObject cachedObject 123 = (CachedObject)this.entryValue(entry); 124 cachedObject.getUserSession().removeObject(cachedObject.getFormKey()); 125 } catch (Exception ex) { 126 Logger.getLogger(getClass()).warn( "Problem purging old entry from the user session when removing from the map: ", ex); 127 } 128 129 super.removeEntry(entry, hashIndex, previous); 130 } 131 132 } 133 134 @Override 135 @SuppressWarnings("unchecked") 136 public void afterPropertiesSet() throws Exception { 137 cachedObjects = Collections.synchronizedMap(new KualiLRUMap(maxCacheSize)); 138 } 139 140 141 @Override 142 public KualiDocumentFormBase getDocumentForm(String documentNumber, String docFormKey, UserSession userSession, 143 String ipAddress) { 144 KualiDocumentFormBase documentForm = null; 145 146 LOG.debug("getDocumentForm KualiDocumentFormBase from db"); 147 try { 148 // re-create the KualiDocumentFormBase object 149 documentForm = (KualiDocumentFormBase) retrieveDocumentForm(userSession, userSession.getKualiSessionId(), 150 documentNumber, ipAddress); 151 152 //re-store workFlowDocument into session 153 if (!(StringUtils.equals((String)userSession.retrieveObject(DocumentAuthorizerBase.USER_SESSION_METHOD_TO_CALL_OBJECT_KEY), 154 KRADConstants.TableRenderConstants.SORT_METHOD) || 155 StringUtils.equals((String)userSession.retrieveObject(DocumentAuthorizerBase.USER_SESSION_METHOD_TO_CALL_OBJECT_KEY), 156 KRADConstants.PARAM_MAINTENANCE_VIEW_MODE_INQUIRY))) { 157 WorkflowDocument workflowDocument = 158 documentForm.getDocument().getDocumentHeader().getWorkflowDocument(); 159 UserSessionUtils.addWorkflowDocument(userSession, workflowDocument); 160 } 161 } catch (Exception e) { 162 LOG.error("getDocumentForm failed for SessId/DocNum/PrinId/IP:" + userSession.getKualiSessionId() + "/" + 163 documentNumber + "/" + userSession.getPrincipalId() + "/" + ipAddress, e); 164 } 165 166 return documentForm; 167 } 168 169 protected Object retrieveDocumentForm(UserSession userSession, String sessionId, String documentNumber, 170 String ipAddress) throws Exception { 171 HashMap<String, String> primaryKeys = new HashMap<String, String>(4); 172 primaryKeys.put(SESSION_ID, sessionId); 173 if (documentNumber != null) { 174 primaryKeys.put(DOCUMENT_NUMBER, documentNumber); 175 } 176 primaryKeys.put(PRINCIPAL_ID, userSession.getPrincipalId()); 177 primaryKeys.put(IP_ADDRESS, ipAddress); 178 179 SessionDocument sessionDoc = getBusinessObjectService().findByPrimaryKey(SessionDocument.class, primaryKeys); 180 if (sessionDoc != null) { 181 byte[] formAsBytes = sessionDoc.getSerializedDocumentForm(); 182 if (sessionDoc.isEncrypted()) { 183 formAsBytes = getEncryptionService().decryptBytes(formAsBytes); 184 } 185 ByteArrayInputStream baip = new ByteArrayInputStream(formAsBytes); 186 ObjectInputStream ois = new ObjectInputStream(baip); 187 188 return ois.readObject(); 189 } 190 191 return null; 192 } 193 194 @Override 195 public WorkflowDocument getDocumentFromSession(UserSession userSession, String docId) { 196 return UserSessionUtils.getWorkflowDocument(userSession, docId); 197 } 198 199 /** 200 * @see org.kuali.rice.krad.service.SessionDocumentService#addDocumentToUserSession(org.kuali.rice.krad.UserSession, 201 * org.kuali.rice.kew.api.WorkflowDocument) 202 */ 203 @Override 204 public void addDocumentToUserSession(UserSession userSession, WorkflowDocument document) { 205 UserSessionUtils.addWorkflowDocument(userSession, document); 206 } 207 208 /** 209 * @see org.kuali.rice.krad.service.SessionDocumentService#purgeDocumentForm(String 210 * documentNumber, String docFormKey, UserSession userSession) 211 */ 212 @Override 213 public void purgeDocumentForm(String documentNumber, String docFormKey, UserSession userSession, String ipAddress) { 214 synchronized (userSession) { 215 216 LOG.debug("purge document form from session"); 217 userSession.removeObject(docFormKey); 218 try { 219 LOG.debug("purge document form from database"); 220 HashMap<String, String> primaryKeys = new HashMap<String, String>(4); 221 primaryKeys.put(SESSION_ID, userSession.getKualiSessionId()); 222 primaryKeys.put(DOCUMENT_NUMBER, documentNumber); 223 primaryKeys.put(PRINCIPAL_ID, userSession.getPrincipalId()); 224 primaryKeys.put(IP_ADDRESS, ipAddress); 225 getBusinessObjectService().deleteMatching(SessionDocument.class, primaryKeys); 226 } catch (Exception e) { 227 LOG.error("purgeDocumentForm failed for SessId/DocNum/PrinId/IP:" + userSession.getKualiSessionId() + 228 "/" + documentNumber + "/" + userSession.getPrincipalId() + "/" + ipAddress, e); 229 } 230 } 231 } 232 233 @Override 234 public void setDocumentForm(KualiDocumentFormBase form, UserSession userSession, String ipAddress) { 235 synchronized (userSession) { 236 //formKey was set in KualiDocumentActionBase execute method 237 String formKey = form.getFormKey(); 238 String key = userSession.getKualiSessionId() + "-" + formKey; 239 cachedObjects.put(key, new CachedObject(userSession, formKey)); 240 241 String documentNumber = form.getDocument().getDocumentNumber(); 242 243 if (StringUtils.isNotBlank(documentNumber)) { 244 persistDocumentForm(form, userSession, ipAddress, userSession.getKualiSessionId(), documentNumber); 245 } else { 246 LOG.warn("documentNumber is null on form's document: " + form); 247 } 248 } 249 } 250 251 protected void persistDocumentForm(Object form, UserSession userSession, String ipAddress, String sessionId, 252 String documentNumber) { 253 try { 254 LOG.debug("set Document Form into database"); 255 Timestamp currentTime = new Timestamp(System.currentTimeMillis()); 256 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 257 ObjectOutputStream oos = new ObjectOutputStream(baos); 258 oos.writeObject(form); 259 // serialize the KualiDocumentFormBase object into a byte array 260 byte[] formAsBytes = baos.toByteArray(); 261 boolean encryptContent = false; 262 263 if ((form instanceof KualiDocumentFormBase) && ((KualiDocumentFormBase) form).getDocTypeName() != null) { 264 DocumentEntry documentEntry = getDataDictionaryService().getDataDictionary() 265 .getDocumentEntry(((KualiDocumentFormBase) form).getDocTypeName()); 266 if (documentEntry != null) { 267 encryptContent = documentEntry.isEncryptDocumentDataInPersistentSessionStorage(); 268 } 269 } 270 if (encryptContent) { 271 formAsBytes = getEncryptionService().encryptBytes(formAsBytes); 272 } 273 274 // check if a record is already there in the database 275 // this may only happen under jMeter testing, but there is no way to be sure 276 HashMap<String, String> primaryKeys = new HashMap<String, String>(4); 277 primaryKeys.put(SESSION_ID, sessionId); 278 primaryKeys.put(DOCUMENT_NUMBER, documentNumber); 279 primaryKeys.put(PRINCIPAL_ID, userSession.getPrincipalId()); 280 primaryKeys.put(IP_ADDRESS, ipAddress); 281 282 SessionDocument sessionDocument = 283 getBusinessObjectService().findByPrimaryKey(SessionDocument.class, primaryKeys); 284 if (sessionDocument == null) { 285 sessionDocument = new SessionDocument(); 286 sessionDocument.setSessionId(sessionId); 287 sessionDocument.setDocumentNumber(documentNumber); 288 sessionDocument.setPrincipalId(userSession.getPrincipalId()); 289 sessionDocument.setIpAddress(ipAddress); 290 } 291 sessionDocument.setSerializedDocumentForm(formAsBytes); 292 sessionDocument.setEncrypted(encryptContent); 293 sessionDocument.setLastUpdatedDate(currentTime); 294 295 businessObjectService.save(sessionDocument); 296 } catch (Exception e) { 297 final String className = form != null ? form.getClass().getName() : "null"; 298 LOG.error("setDocumentForm failed for SessId/DocNum/PrinId/IP/class:" + userSession.getKualiSessionId() + 299 "/" + documentNumber + "/" + userSession.getPrincipalId() + "/" + ipAddress + "/" + className, e); 300 } 301 } 302 303 /** 304 * @see org.kuali.rice.krad.service.SessionDocumentService#purgeAllSessionDocuments(java.sql.Timestamp) 305 */ 306 @Override 307 public void purgeAllSessionDocuments(Timestamp expirationDate) { 308 sessionDocumentDao.purgeAllSessionDocuments(expirationDate); 309 } 310 311 protected SessionDocumentDao getSessionDocumentDao() { 312 return this.sessionDocumentDao; 313 } 314 315 public void setSessionDocumentDao(SessionDocumentDao sessionDocumentDao) { 316 this.sessionDocumentDao = sessionDocumentDao; 317 } 318 319 protected BusinessObjectService getBusinessObjectService() { 320 return this.businessObjectService; 321 } 322 323 public void setBusinessObjectService(BusinessObjectService businessObjectService) { 324 this.businessObjectService = businessObjectService; 325 } 326 327 public int getMaxCacheSize() { 328 return maxCacheSize; 329 } 330 331 public void setMaxCacheSize(int maxCacheSize) { 332 this.maxCacheSize = maxCacheSize; 333 } 334 335 protected EncryptionService getEncryptionService() { 336 if (encryptionService == null) { 337 encryptionService = CoreApiServiceLocator.getEncryptionService(); 338 } 339 return encryptionService; 340 } 341 342 protected DataDictionaryService getDataDictionaryService() { 343 if (dataDictionaryService == null) { 344 dataDictionaryService = KRADServiceLocatorWeb.getDataDictionaryService(); 345 } 346 return dataDictionaryService; 347 } 348}