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 }