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 */ 016 package org.kuali.rice.krad.service.impl; 017 018 import org.apache.commons.lang.StringUtils; 019 import org.kuali.rice.core.api.CoreApiServiceLocator; 020 import org.kuali.rice.core.api.encryption.EncryptionService; 021 import org.kuali.rice.kew.api.KewApiConstants; 022 import org.kuali.rice.kew.api.WorkflowDocument; 023 import org.kuali.rice.krad.UserSession; 024 import org.kuali.rice.krad.bo.SessionDocument; 025 import org.kuali.rice.krad.dao.SessionDocumentDao; 026 import org.kuali.rice.krad.datadictionary.DocumentEntry; 027 import org.kuali.rice.krad.service.BusinessObjectService; 028 import org.kuali.rice.krad.service.DataDictionaryService; 029 import org.kuali.rice.krad.service.KRADServiceLocatorWeb; 030 import org.kuali.rice.krad.service.SessionDocumentService; 031 import org.kuali.rice.krad.web.form.DocumentFormBase; 032 import org.springframework.transaction.annotation.Transactional; 033 034 import java.io.ByteArrayInputStream; 035 import java.io.ByteArrayOutputStream; 036 import java.io.ObjectInputStream; 037 import java.io.ObjectOutputStream; 038 import java.sql.Timestamp; 039 import java.util.HashMap; 040 import java.util.Map; 041 import java.util.concurrent.ConcurrentHashMap; 042 043 /** 044 * Implementation of <code>SessionDocumentService</code> that persists the document form 045 * contents to the underlying database 046 * 047 * @author Kuali Rice Team (rice.collab@kuali.org) 048 */ 049 @Transactional 050 public class SessionDocumentServiceImpl implements SessionDocumentService { 051 private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(SessionDocumentServiceImpl.class); 052 053 protected static final String IP_ADDRESS = "ipAddress"; 054 protected static final String PRINCIPAL_ID = "principalId"; 055 protected static final String DOCUMENT_NUMBER = "documentNumber"; 056 protected static final String SESSION_ID = "sessionId"; 057 058 private EncryptionService encryptionService; 059 060 private BusinessObjectService businessObjectService; 061 private DataDictionaryService dataDictionaryService; 062 private SessionDocumentDao sessionDocumentDao; 063 064 @Override 065 public DocumentFormBase getDocumentForm(String documentNumber, String docFormKey, UserSession userSession, 066 String ipAddress) { 067 DocumentFormBase documentForm = null; 068 069 LOG.debug("getDocumentForm DocumentFormBase from db"); 070 try { 071 // re-create the DocumentFormBase object 072 documentForm = (DocumentFormBase) retrieveDocumentForm(userSession, docFormKey, documentNumber, ipAddress); 073 074 //re-store workFlowDocument into session 075 WorkflowDocument workflowDocument = 076 documentForm.getDocument().getDocumentHeader().getWorkflowDocument(); 077 addDocumentToUserSession(userSession, workflowDocument); 078 } catch (Exception e) { 079 LOG.error("getDocumentForm failed for SessId/DocNum/PrinId/IP:" + userSession.getKualiSessionId() + "/" + 080 documentNumber + "/" + userSession.getPrincipalId() + "/" + ipAddress, e); 081 } 082 083 return documentForm; 084 } 085 086 protected Object retrieveDocumentForm(UserSession userSession, String sessionId, String documentNumber, 087 String ipAddress) throws Exception { 088 HashMap<String, String> primaryKeys = new HashMap<String, String>(4); 089 primaryKeys.put(SESSION_ID, sessionId); 090 if (documentNumber != null) { 091 primaryKeys.put(DOCUMENT_NUMBER, documentNumber); 092 } 093 primaryKeys.put(PRINCIPAL_ID, userSession.getPrincipalId()); 094 primaryKeys.put(IP_ADDRESS, ipAddress); 095 096 SessionDocument sessionDoc = getBusinessObjectService().findByPrimaryKey(SessionDocument.class, primaryKeys); 097 if (sessionDoc != null) { 098 byte[] formAsBytes = sessionDoc.getSerializedDocumentForm(); 099 if (sessionDoc.isEncrypted()) { 100 formAsBytes = getEncryptionService().decryptBytes(formAsBytes); 101 } 102 ByteArrayInputStream baip = new ByteArrayInputStream(formAsBytes); 103 ObjectInputStream ois = new ObjectInputStream(baip); 104 105 return ois.readObject(); 106 } 107 108 return null; 109 } 110 111 @Override 112 public WorkflowDocument getDocumentFromSession(UserSession userSession, String docId) { 113 synchronized (userSession) { 114 @SuppressWarnings("unchecked") Map<String, WorkflowDocument> workflowDocMap = 115 (Map<String, WorkflowDocument>) userSession 116 .retrieveObject(KewApiConstants.WORKFLOW_DOCUMENT_MAP_ATTR_NAME); 117 118 if (workflowDocMap == null) { 119 workflowDocMap = new ConcurrentHashMap<String, WorkflowDocument> (); 120 userSession.addObject(KewApiConstants.WORKFLOW_DOCUMENT_MAP_ATTR_NAME, workflowDocMap); 121 return null; 122 } 123 return workflowDocMap.get(docId); 124 } 125 } 126 127 /** 128 * @see org.kuali.rice.krad.service.SessionDocumentService#addDocumentToUserSession(org.kuali.rice.krad.UserSession, 129 * org.kuali.rice.krad.workflow.service.KualiWorkflowDocument) 130 */ 131 @Override 132 public void addDocumentToUserSession(UserSession userSession, WorkflowDocument document) { 133 synchronized (userSession) { 134 @SuppressWarnings("unchecked") Map<String, WorkflowDocument> workflowDocMap = 135 (Map<String, WorkflowDocument>) userSession 136 .retrieveObject(KewApiConstants.WORKFLOW_DOCUMENT_MAP_ATTR_NAME); 137 if (workflowDocMap == null) { 138 workflowDocMap = new ConcurrentHashMap<String, WorkflowDocument> (); 139 } 140 // verify key and value are not null 141 if(document != null && document.getDocumentId() != null) { 142 workflowDocMap.put(document.getDocumentId(), document); 143 } 144 userSession.addObject(KewApiConstants.WORKFLOW_DOCUMENT_MAP_ATTR_NAME, workflowDocMap); 145 } 146 } 147 148 /** 149 * @see org.kuali.rice.krad.service.SessionDocumentService#purgeDocumentForm(String 150 * documentNumber, String docFormKey, UserSession userSession) 151 */ 152 @Override 153 public void purgeDocumentForm(String documentNumber, String docFormKey, UserSession userSession, String ipAddress) { 154 synchronized (userSession) { 155 LOG.debug("purge document form from session"); 156 userSession.removeObject(docFormKey); 157 try { 158 LOG.debug("purge document form from database"); 159 HashMap<String, String> primaryKeys = new HashMap<String, String>(4); 160 primaryKeys.put(SESSION_ID, userSession.getKualiSessionId()); 161 primaryKeys.put(DOCUMENT_NUMBER, documentNumber); 162 primaryKeys.put(PRINCIPAL_ID, userSession.getPrincipalId()); 163 primaryKeys.put(IP_ADDRESS, ipAddress); 164 getBusinessObjectService().deleteMatching(SessionDocument.class, primaryKeys); 165 } catch (Exception e) { 166 LOG.error("purgeDocumentForm failed for SessId/DocNum/PrinId/IP:" + userSession.getKualiSessionId() + 167 "/" + documentNumber + "/" + userSession.getPrincipalId() + "/" + ipAddress, e); 168 } 169 } 170 } 171 172 @Override 173 public void setDocumentForm(DocumentFormBase form, UserSession userSession, String ipAddress) { 174 synchronized (userSession) { 175 //formKey was set in KualiDocumentActionBase execute method 176 String formKey = form.getFormKey(); 177 String key = userSession.getKualiSessionId() + "-" + formKey; 178 179 String documentNumber = form.getDocument().getDocumentNumber(); 180 if (StringUtils.isNotBlank(formKey)) { 181 //FIXME: Currently using formKey for sessionId 182 persistDocumentForm(form, userSession, ipAddress, formKey, documentNumber); 183 } else { 184 LOG.warn("documentNumber is null on form's document: " + form); 185 } 186 } 187 } 188 189 protected void persistDocumentForm(DocumentFormBase form, UserSession userSession, String ipAddress, 190 String sessionId, String documentNumber) { 191 try { 192 LOG.debug("set Document Form into database"); 193 194 Timestamp currentTime = new Timestamp(System.currentTimeMillis()); 195 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 196 ObjectOutputStream oos = new ObjectOutputStream(baos); 197 oos.writeObject(form); 198 199 // serialize the DocumentFormBase object into a byte array 200 byte[] formAsBytes = baos.toByteArray(); 201 boolean encryptContent = false; 202 DocumentEntry documentEntry = 203 getDataDictionaryService().getDataDictionary().getDocumentEntry(form.getDocTypeName()); 204 if (documentEntry != null) { 205 encryptContent = documentEntry.isEncryptDocumentDataInPersistentSessionStorage(); 206 } 207 208 if (encryptContent) { 209 formAsBytes = getEncryptionService().encryptBytes(formAsBytes); 210 } 211 212 // check if a record is already there in the database 213 // this may only happen under jMeter testing, but there is no way to be sure 214 HashMap<String, String> primaryKeys = new HashMap<String, String>(4); 215 primaryKeys.put(SESSION_ID, sessionId); 216 primaryKeys.put(DOCUMENT_NUMBER, documentNumber); 217 primaryKeys.put(PRINCIPAL_ID, userSession.getPrincipalId()); 218 primaryKeys.put(IP_ADDRESS, ipAddress); 219 220 SessionDocument sessionDocument = 221 getBusinessObjectService().findByPrimaryKey(SessionDocument.class, primaryKeys); 222 if (sessionDocument == null) { 223 sessionDocument = new SessionDocument(); 224 sessionDocument.setSessionId(sessionId); 225 sessionDocument.setDocumentNumber(documentNumber); 226 sessionDocument.setPrincipalId(userSession.getPrincipalId()); 227 sessionDocument.setIpAddress(ipAddress); 228 } 229 sessionDocument.setSerializedDocumentForm(formAsBytes); 230 sessionDocument.setEncrypted(encryptContent); 231 sessionDocument.setLastUpdatedDate(currentTime); 232 233 businessObjectService.save(sessionDocument); 234 } catch (Exception e) { 235 final String className = form != null ? form.getClass().getName() : "null"; 236 LOG.error("setDocumentForm failed for SessId/DocNum/PrinId/IP/class:" + userSession.getKualiSessionId() + 237 "/" + documentNumber + "/" + userSession.getPrincipalId() + "/" + ipAddress + "/" + className, e); 238 } 239 } 240 241 /** 242 * @see org.kuali.rice.krad.service.SessionDocumentService#purgeAllSessionDocuments(java.sql.Timestamp) 243 */ 244 @Override 245 public void purgeAllSessionDocuments(Timestamp expirationDate) { 246 sessionDocumentDao.purgeAllSessionDocuments(expirationDate); 247 } 248 249 protected SessionDocumentDao getSessionDocumentDao() { 250 return this.sessionDocumentDao; 251 } 252 253 public void setSessionDocumentDao(SessionDocumentDao sessionDocumentDao) { 254 this.sessionDocumentDao = sessionDocumentDao; 255 } 256 257 protected BusinessObjectService getBusinessObjectService() { 258 return this.businessObjectService; 259 } 260 261 public void setBusinessObjectService(BusinessObjectService businessObjectService) { 262 this.businessObjectService = businessObjectService; 263 } 264 265 protected EncryptionService getEncryptionService() { 266 if (encryptionService == null) { 267 encryptionService = CoreApiServiceLocator.getEncryptionService(); 268 } 269 return encryptionService; 270 } 271 272 protected DataDictionaryService getDataDictionaryService() { 273 if (dataDictionaryService == null) { 274 dataDictionaryService = KRADServiceLocatorWeb.getDataDictionaryService(); 275 } 276 return dataDictionaryService; 277 } 278 }