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}