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;
047import 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
057public 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}