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