001    /**
002     * Copyright 2005-2012 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    
042    /**
043     * Implementation of <code>SessionDocumentService</code> that persists the document form
044     * contents to the underlying database
045     *
046     * @author Kuali Rice Team (rice.collab@kuali.org)
047     */
048    @Transactional
049    public class SessionDocumentServiceImpl implements SessionDocumentService {
050        private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(SessionDocumentServiceImpl.class);
051    
052        protected static final String IP_ADDRESS = "ipAddress";
053        protected static final String PRINCIPAL_ID = "principalId";
054        protected static final String DOCUMENT_NUMBER = "documentNumber";
055        protected static final String SESSION_ID = "sessionId";
056    
057        private EncryptionService encryptionService;
058    
059        private BusinessObjectService businessObjectService;
060        private DataDictionaryService dataDictionaryService;
061        private SessionDocumentDao sessionDocumentDao;
062    
063        @Override
064        public DocumentFormBase getDocumentForm(String documentNumber, String docFormKey, UserSession userSession,
065                String ipAddress) {
066            DocumentFormBase documentForm = null;
067    
068            LOG.debug("getDocumentForm DocumentFormBase from db");
069            try {
070                // re-create the DocumentFormBase object
071                documentForm = (DocumentFormBase) retrieveDocumentForm(userSession, docFormKey, documentNumber, ipAddress);
072    
073                //re-store workFlowDocument into session
074                WorkflowDocument workflowDocument =
075                        documentForm.getDocument().getDocumentHeader().getWorkflowDocument();
076                addDocumentToUserSession(userSession, workflowDocument);
077            } catch (Exception e) {
078                LOG.error("getDocumentForm failed for SessId/DocNum/PrinId/IP:" + userSession.getKualiSessionId() + "/" +
079                        documentNumber + "/" + userSession.getPrincipalId() + "/" + ipAddress, e);
080            }
081    
082            return documentForm;
083        }
084    
085        protected Object retrieveDocumentForm(UserSession userSession, String sessionId, String documentNumber,
086                String ipAddress) throws Exception {
087            HashMap<String, String> primaryKeys = new HashMap<String, String>(4);
088            primaryKeys.put(SESSION_ID, sessionId);
089            if (documentNumber != null) {
090                primaryKeys.put(DOCUMENT_NUMBER, documentNumber);
091            }
092            primaryKeys.put(PRINCIPAL_ID, userSession.getPrincipalId());
093            primaryKeys.put(IP_ADDRESS, ipAddress);
094    
095            SessionDocument sessionDoc = getBusinessObjectService().findByPrimaryKey(SessionDocument.class, primaryKeys);
096            if (sessionDoc != null) {
097                byte[] formAsBytes = sessionDoc.getSerializedDocumentForm();
098                if (sessionDoc.isEncrypted()) {
099                    formAsBytes = getEncryptionService().decryptBytes(formAsBytes);
100                }
101                ByteArrayInputStream baip = new ByteArrayInputStream(formAsBytes);
102                ObjectInputStream ois = new ObjectInputStream(baip);
103    
104                return ois.readObject();
105            }
106    
107            return null;
108        }
109    
110        @Override
111        public WorkflowDocument getDocumentFromSession(UserSession userSession, String docId) {
112            @SuppressWarnings("unchecked") Map<String, WorkflowDocument> workflowDocMap =
113                    (Map<String, WorkflowDocument>) userSession
114                            .retrieveObject(KewApiConstants.WORKFLOW_DOCUMENT_MAP_ATTR_NAME);
115    
116            if (workflowDocMap == null) {
117                workflowDocMap = new HashMap<String, WorkflowDocument>();
118                userSession.addObject(KewApiConstants.WORKFLOW_DOCUMENT_MAP_ATTR_NAME, workflowDocMap);
119                return null;
120            }
121            return workflowDocMap.get(docId);
122        }
123    
124        /**
125         * @see org.kuali.rice.krad.service.SessionDocumentService#addDocumentToUserSession(org.kuali.rice.krad.UserSession,
126         *      org.kuali.rice.krad.workflow.service.KualiWorkflowDocument)
127         */
128        @Override
129        public void addDocumentToUserSession(UserSession userSession, WorkflowDocument document) {
130            @SuppressWarnings("unchecked") Map<String, WorkflowDocument> workflowDocMap =
131                    (Map<String, WorkflowDocument>) userSession
132                            .retrieveObject(KewApiConstants.WORKFLOW_DOCUMENT_MAP_ATTR_NAME);
133            if (workflowDocMap == null) {
134                workflowDocMap = new HashMap<String, WorkflowDocument>();
135            }
136            workflowDocMap.put(document.getDocumentId(), document);
137            userSession.addObject(KewApiConstants.WORKFLOW_DOCUMENT_MAP_ATTR_NAME, workflowDocMap);
138        }
139    
140        /**
141         * @see org.kuali.rice.krad.service.SessionDocumentService#purgeDocumentForm(String
142         *      documentNumber, String docFormKey, UserSession userSession)
143         */
144        @Override
145        public void purgeDocumentForm(String documentNumber, String docFormKey, UserSession userSession, String ipAddress) {
146            synchronized (userSession) {
147    
148                LOG.debug("purge document form from session");
149                userSession.removeObject(docFormKey);
150                try {
151                    LOG.debug("purge document form from database");
152                    HashMap<String, String> primaryKeys = new HashMap<String, String>(4);
153                    primaryKeys.put(SESSION_ID, userSession.getKualiSessionId());
154                    primaryKeys.put(DOCUMENT_NUMBER, documentNumber);
155                    primaryKeys.put(PRINCIPAL_ID, userSession.getPrincipalId());
156                    primaryKeys.put(IP_ADDRESS, ipAddress);
157                    getBusinessObjectService().deleteMatching(SessionDocument.class, primaryKeys);
158                } catch (Exception e) {
159                    LOG.error("purgeDocumentForm failed for SessId/DocNum/PrinId/IP:" + userSession.getKualiSessionId() +
160                            "/" + documentNumber + "/" + userSession.getPrincipalId() + "/" + ipAddress, e);
161                }
162            }
163        }
164    
165        @Override
166        public void setDocumentForm(DocumentFormBase form, UserSession userSession, String ipAddress) {
167            synchronized (userSession) {
168                //formKey was set in KualiDocumentActionBase execute method
169                String formKey = form.getFormKey();
170                String key = userSession.getKualiSessionId() + "-" + formKey;
171    
172                String documentNumber = form.getDocument().getDocumentNumber();
173                if (StringUtils.isNotBlank(formKey)) {
174                    //FIXME: Currently using formKey for sessionId
175                    persistDocumentForm(form, userSession, ipAddress, formKey, documentNumber);
176                } else {
177                    LOG.warn("documentNumber is null on form's document: " + form);
178                }
179            }
180        }
181    
182        protected void persistDocumentForm(DocumentFormBase form, UserSession userSession, String ipAddress,
183                String sessionId, String documentNumber) {
184            try {
185                LOG.debug("set Document Form into database");
186    
187                Timestamp currentTime = new Timestamp(System.currentTimeMillis());
188                ByteArrayOutputStream baos = new ByteArrayOutputStream();
189                ObjectOutputStream oos = new ObjectOutputStream(baos);
190                oos.writeObject(form);
191    
192                // serialize the DocumentFormBase object into a byte array
193                byte[] formAsBytes = baos.toByteArray();
194                boolean encryptContent = false;
195                DocumentEntry documentEntry =
196                        getDataDictionaryService().getDataDictionary().getDocumentEntry(form.getDocTypeName());
197                if (documentEntry != null) {
198                    encryptContent = documentEntry.isEncryptDocumentDataInPersistentSessionStorage();
199                }
200    
201                if (encryptContent) {
202                    formAsBytes = getEncryptionService().encryptBytes(formAsBytes);
203                }
204    
205                // check if a record is already there in the database
206                // this may only happen under jMeter testing, but there is no way to be sure
207                HashMap<String, String> primaryKeys = new HashMap<String, String>(4);
208                primaryKeys.put(SESSION_ID, sessionId);
209                primaryKeys.put(DOCUMENT_NUMBER, documentNumber);
210                primaryKeys.put(PRINCIPAL_ID, userSession.getPrincipalId());
211                primaryKeys.put(IP_ADDRESS, ipAddress);
212    
213                SessionDocument sessionDocument =
214                        getBusinessObjectService().findByPrimaryKey(SessionDocument.class, primaryKeys);
215                if (sessionDocument == null) {
216                    sessionDocument = new SessionDocument();
217                    sessionDocument.setSessionId(sessionId);
218                    sessionDocument.setDocumentNumber(documentNumber);
219                    sessionDocument.setPrincipalId(userSession.getPrincipalId());
220                    sessionDocument.setIpAddress(ipAddress);
221                }
222                sessionDocument.setSerializedDocumentForm(formAsBytes);
223                sessionDocument.setEncrypted(encryptContent);
224                sessionDocument.setLastUpdatedDate(currentTime);
225    
226                businessObjectService.save(sessionDocument);
227            } catch (Exception e) {
228                final String className = form != null ? form.getClass().getName() : "null";
229                LOG.error("setDocumentForm failed for SessId/DocNum/PrinId/IP/class:" + userSession.getKualiSessionId() +
230                        "/" + documentNumber + "/" + userSession.getPrincipalId() + "/" + ipAddress + "/" + className, e);
231            }
232        }
233    
234        /**
235         * @see org.kuali.rice.krad.service.SessionDocumentService#purgeAllSessionDocuments(java.sql.Timestamp)
236         */
237        @Override
238        public void purgeAllSessionDocuments(Timestamp expirationDate) {
239            sessionDocumentDao.purgeAllSessionDocuments(expirationDate);
240        }
241    
242        protected SessionDocumentDao getSessionDocumentDao() {
243            return this.sessionDocumentDao;
244        }
245    
246        public void setSessionDocumentDao(SessionDocumentDao sessionDocumentDao) {
247            this.sessionDocumentDao = sessionDocumentDao;
248        }
249    
250        protected BusinessObjectService getBusinessObjectService() {
251            return this.businessObjectService;
252        }
253    
254        public void setBusinessObjectService(BusinessObjectService businessObjectService) {
255            this.businessObjectService = businessObjectService;
256        }
257    
258        protected EncryptionService getEncryptionService() {
259            if (encryptionService == null) {
260                encryptionService = CoreApiServiceLocator.getEncryptionService();
261            }
262            return encryptionService;
263        }
264    
265        protected DataDictionaryService getDataDictionaryService() {
266            if (dataDictionaryService == null) {
267                dataDictionaryService = KRADServiceLocatorWeb.getDataDictionaryService();
268            }
269            return dataDictionaryService;
270        }
271    }