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