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    }