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 }