/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.openehealth.coala.beans; import java.io.Serializable; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.List; import java.util.Locale; import java.util.ResourceBundle; import java.util.regex.Pattern; import javax.faces.application.FacesMessage; import javax.faces.component.UIComponent; import javax.faces.component.UIInput; import javax.faces.context.FacesContext; import javax.faces.event.AjaxBehaviorEvent; import javax.faces.event.ComponentSystemEvent; import javax.faces.model.DataModel; import javax.faces.model.ListDataModel; import javax.persistence.Transient; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.richfaces.component.UIExtendedDataTable; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; import org.openehealth.coala.domain.FindPatientQuery; import org.openehealth.coala.domain.FindPatientResult; import org.openehealth.coala.domain.Patient; import org.openehealth.coala.domain.PatientAddress; import org.openehealth.coala.domain.PatientConsent; import org.openehealth.coala.domain.PatientSortParameter; import org.openehealth.coala.exception.ServiceParameterException; import org.openehealth.coala.interfacing.PatientService; /** * This class represents an managedBean for a PatientSearch via PXS-pdqv2. * * @author astiefer, mwiesner * */ @Component @Scope("session") public class PatientBean implements Serializable { private static final long serialVersionUID = 1L; private static final Log LOG = LogFactory.getLog(PatientBean.class); private String patientID; private String givenName = new String(); private String lastName = new String(); private Date birthdate; private PatientAddress address; @Transient private DataModel<Patient> patients = new ListDataModel<Patient>(); private List<Patient> patientsResList; private boolean initialSearchState = true; private Patient selectedPatient; private Collection<Object> selectionPatient; // SORTING private PatientSortParameter sortParameter = PatientSortParameter .getDefault(); // Search String private StringBuffer searchString = new StringBuffer(); /* * Managed bean injection to be capable of resetting old consent result * tables. */ @Transient @Autowired private ConsentBean consentBean; /* * Managed bean injection to get current locale which is needed for I18n * e.g. error messages/warnings. */ @Transient @Autowired private LocaleHandler localeHandler; /* * this setter is needed for consentBean inject by Spring/JSF */ public void setConsentBean(ConsentBean consentBean) { this.consentBean = consentBean; } /* * Communication layer bindings */ @Transient @Autowired private PatientService pxsQueryService; /** * @return the pxsQueryService */ public PatientService getPxsQueryService() { return pxsQueryService; } /** * @param pxsQueryService * the pxsQueryService to set */ public void setPxsQueryService(PatientService pxsQueryService) { this.pxsQueryService = pxsQueryService; LOG.info("PXSQueryService configured and ready... [OK]"); } /** * Default Constructor Initializes the PatientBean with default values */ public PatientBean() { patientsResList = new ArrayList<Patient>(); setPatients(new ListDataModel<Patient>(patientsResList)); } /** * @return the patientID */ public String getpatientID() { return patientID; } /** * @param patientID * the patientID to set */ public void setPatientID(String patientID) { this.patientID = patientID; } /** * @return the givenName */ public String getGivenName() { return givenName; } /** * @param givenName * the givenName to set */ public void setGivenName(String givenName) { this.givenName = givenName; } /** * @return the lastName */ public String getLastName() { return lastName; } /** * @param lastName * the lastName to set */ public void setLastName(String lastName) { this.lastName = lastName; } /** * @return the birthdate */ public Date getBirthdate() { return birthdate; } /** * @param birthdate * the birthdate to set */ public void setBirthdate(Date birthdate) { this.birthdate = birthdate; } /** * @return the adress */ public PatientAddress getAddress() { return address; } /** * @param address * the adress to set */ public void setAddress(PatientAddress address) { this.address = address; } /** * @return the initalSearchState */ public boolean isInitialSearchState() { return initialSearchState; } /** * @param initialSearchState * the initialSearchState to set */ public void setInitialSearchState(boolean initialSearchState) { this.initialSearchState = initialSearchState; } /** * Method to validate search parameters patientID, givenName, lastName and * dateOfBirth as a group. This is needed to have proper error notification * on failed search field input at search view, if user provides non * matching (invalid) input. * * @param event */ public void validateSearchParameters(ComponentSystemEvent event) { searchString.delete(0, searchString.length()); FacesContext fc = FacesContext.getCurrentInstance(); String messages = fc.getApplication().getMessageBundle(); Locale locale = new Locale(localeHandler.getLocale()); ResourceBundle bundle = ResourceBundle.getBundle(messages, locale); UIComponent components = event.getComponent(); UIInput patientIDInput = (UIInput) components .findComponent("patientID"); String patientID = patientIDInput.getLocalValue().toString(); UIInput givenNameInput = (UIInput) components .findComponent("givenName"); String givenName = givenNameInput.getLocalValue().toString(); UIInput lastNameInput = (UIInput) components.findComponent("lastName"); String lastName = lastNameInput.getLocalValue().toString(); UIInput dateOfBirthInput = (UIInput) components .findComponent("dateOfBirth"); boolean patientIDEmpty = false; boolean givenNameEmpty = false; boolean lastNameEmpty = false; boolean dateOfBirthEmpty = false; if (patientIDInput.getLocalValue() == null || patientIDInput.getLocalValue().toString().trim().isEmpty()) { patientIDEmpty = true; } if (givenNameInput.getLocalValue() == null || givenNameInput.getLocalValue().toString().trim().isEmpty()) { givenNameEmpty = true; } if (lastNameInput.getLocalValue() == null || lastNameInput.getLocalValue().toString().trim().isEmpty()) { lastNameEmpty = true; } if (dateOfBirthInput.getLocalValue() == null || dateOfBirthInput.getLocalValue().toString().trim().isEmpty()) { dateOfBirthEmpty = true; } if (patientIDEmpty && givenNameEmpty && lastNameEmpty && dateOfBirthEmpty) { FacesMessage msg = new FacesMessage( bundle.getString("errors.nonEmptySearchKey"), ""); msg.setSeverity(FacesMessage.SEVERITY_WARN); fc.addMessage(components.getClientId(), msg); // passed to the Render Response phase fc.renderResponse(); cleanOutdatedViewState(); setPatients(new ListDataModel<Patient>()); } else { boolean validationErrorOccured = false; // validating correctness of patientID value if it is set if (!patientIDEmpty) { if (!StringUtils.isNumeric(patientID)) { FacesMessage msg = new FacesMessage( bundle.getString("errors.numericPatientID"), ""); msg.setSeverity(FacesMessage.SEVERITY_WARN); fc.addMessage(patientIDInput.getClientId(), msg); validationErrorOccured = true; } else { // ok here -> no actions required, move on now :) } searchString.append(bundle.getString("search.patient_id") + ": " + patientID + " "); } // validating correctness of givenName value if it is set if (!givenNameEmpty) { // String givenNameChecked = checkAndFixWrongCharset(givenName); boolean patternOK = Pattern.matches("[a-zA-ZßÜüÖöÄä]*[*]?", givenName); if (!patternOK) { FacesMessage msg = new FacesMessage( bundle.getString("errors.nonNumericGivenName"), ""); msg.setSeverity(FacesMessage.SEVERITY_WARN); fc.addMessage(givenNameInput.getClientId(), msg); validationErrorOccured = true; } else { // ok here -> no actions required, move on now :) } searchString.append(bundle .getString("search.patient_givenName") + ": " + givenName + " "); } // validating correctness of lastname value if it is set if (!lastNameEmpty) { // String lastNameChecked = checkAndFixWrongCharset(lastName); boolean patternOK = Pattern.matches("[a-zA-ZßÜüÖöÄä]*[*]?", lastName); if (!patternOK) { FacesMessage msg = new FacesMessage( bundle.getString("errors.nonNumericLastName"), ""); msg.setSeverity(FacesMessage.SEVERITY_WARN); fc.addMessage(lastNameInput.getClientId(), msg); validationErrorOccured = true; } else { // ok here -> no actions required, move on now :) } searchString.append(bundle.getString("search.patient_lastname") + ": " + lastName + " "); } // validating correctness of dateOfBirth value if it is set if (!dateOfBirthEmpty) { try { // check for a valid date format by casting into a date Date dateOfBirth = (Date) dateOfBirthInput.getLocalValue(); // if we have real Date object -> check for future date // value which make no sense here! if (dateOfBirth.after(new Date())) { FacesMessage msg = new FacesMessage( "Date of birth must not be in the future!", ""); msg.setSeverity(FacesMessage.SEVERITY_WARN); fc.addMessage(dateOfBirthInput.getClientId(), msg); validationErrorOccured = true; } else { // ok here -> no actions required, move on now :) } } catch (RuntimeException rt) { // occurs if no valid Date object was in the input field // given by the user input FacesMessage msg = new FacesMessage( "Please provide a valid date of birth in a useful format. Hint: 'Calendar'!", ""); msg.setSeverity(FacesMessage.SEVERITY_WARN); fc.addMessage(dateOfBirthInput.getClientId(), msg); validationErrorOccured = true; } } /* * FINALLY CHECK IF ANY ERRORS HAVE OCCURED WHICH CAUSE THE EARLY * RENDER RESPONSE... -> STOP ANY SEARCH! */ if (validationErrorOccured) { // passed to the Render Response phase fc.renderResponse(); cleanOutdatedViewState(); setPatients(new ListDataModel<Patient>()); } } } /** * Helper method to display the last valid search string in the results * table. */ public String getSearchString() { if (!(searchString.toString().trim().equals(""))) { return "\"" + searchString.toString() + "\""; } return ""; } /** * Overriding this method ensures that rich:calender validation is not done * by standard validation procedure but by our own * {@link PatientBean#validateSearchParameters(ComponentSystemEvent)} * method. */ public void validator(javax.faces.context.FacesContext fc, javax.faces.component.UIComponent component, java.lang.Object object) { // JUST DO NOT VALIDATE ANYTHING HERE -> all done in // validateSearchParameters... } /** * Anchor point to trigger a real search within the coala-communication * layer. * * @return Always returns an empty string. */ public String search() { FacesContext fc = FacesContext.getCurrentInstance(); String messages = fc.getApplication().getMessageBundle(); Locale locale = new Locale(localeHandler.getLocale()); ResourceBundle bundle = ResourceBundle.getBundle(messages, locale); //Clean ConsentBean consentBean.cleanForNewPatientSearch(); patientsResList = null; try { String givenNameCharsetChecked = checkAndFixWrongCharset(givenName); String lastNameCharsetChecked = checkAndFixWrongCharset(lastName); // generate FindPatientQuery FindPatientQuery findPatientQuery = new FindPatientQuery(patientID, givenNameCharsetChecked, lastNameCharsetChecked, birthdate); LOG.info("Preparing pdqv2 queries against PXS: \n" + findPatientQuery.toString()); long start = System.currentTimeMillis(); // perform query against PXS finally FindPatientResult findPatientResult = pxsQueryService.findPatients( findPatientQuery, sortParameter); long end = System.currentTimeMillis(); LOG.info("PXS pdqv2 query took " + (end - start) + " ms."); // process results if (findPatientResult != null && findPatientResult.getPatients().size() != 0) { patientsResList = new ArrayList<Patient>(); // all the other dynamically queried patients patientsResList.addAll(findPatientResult.getPatients()); setPatients(new ListDataModel<Patient>(patientsResList)); /* * cleaning up the old values of the UIInput fields */ cleanOutdatedViewState(); /* * resetting all consents which are outdated from now on within * the consent bean */ consentBean.setConsents(new ListDataModel<PatientConsent>()); initialSearchState = false; } else { cleanOutdatedViewState(); setPatients(new ListDataModel<Patient>()); FacesMessage msg = new FacesMessage( bundle.getString("errors.noPatientFound")); msg.setSeverity(FacesMessage.SEVERITY_INFO); fc.addMessage(fc.getViewRoot().findComponent("patientResultPanel").getClientId(),msg); LOG.warn("PDQ did not result in any hits. FindPatientResult was NULL...!"); } } catch (ServiceParameterException spe) { /* * if invalid parameters were given - which should not happen due to * earlier validation, we just set an empty result list */ setPatients(new ListDataModel<Patient>()); } catch (RuntimeException rt) { LOG.error(rt.getLocalizedMessage(), rt); } /* * returning an empty string as no navigation rule/case is associated * here */ return ""; } /* * Charset conversion to UTF-8 from ISO encoding as MPI HL7 queries will * fail if umlauts contained, otherwise! */ private String checkAndFixWrongCharset(String s) { String checkedISO = new String( s.getBytes(Charset.forName("ISO-8859-1"))); LOG.info(s + " indirectly converted to ISO-8859-1: " + checkedISO); // return new String(checkedISO.getBytes(), Charset.forName("UTF-8")); return checkedISO; } /** * Helper method to clean some UI input fields and old selection data from * last query request. */ private void cleanOutdatedViewState() { // cleaning search input fields setPatientID(""); setGivenName(""); setLastName(""); setBirthdate(null); // cleaning potentially selected AND outdated patient setPatientSelection(new ArrayList<Object>()); this.selectedPatient = null; // cleaning potentially selected AND outdated consent consentBean.cleanOutdatedViewState(); } /** * @param patients * the patients to set */ public void setPatients(DataModel<Patient> patients) { this.patients = patients; } /** * @return the patients */ public DataModel<Patient> getPatients() { return patients; } /** * @return the sortParameter */ public PatientSortParameter getSortParameter() { return sortParameter; } /** * @param sortParameter * the sortParameter to set */ public void setSortParameter(PatientSortParameter sortParameter) { this.sortParameter = sortParameter; } /** * Sorts the patient table by patientID */ public void sortByPatientID() { if (sortParameter.equals(PatientSortParameter.PID_ASCENDING)) { setSortParameter(PatientSortParameter.PID_DESCENDING); } else { setSortParameter(PatientSortParameter.PID_ASCENDING); } search(); } /** * Sorts the patient table by givenName */ public void sortByGivenName() { if (sortParameter.equals(PatientSortParameter.GIVENNAME_ASCENDING)) { setSortParameter(PatientSortParameter.GIVENNAME_DESCENDING); } else { setSortParameter(PatientSortParameter.GIVENNAME_ASCENDING); } search(); } /** * Sorts the patient table by lastName */ public void sortByLastName() { if (sortParameter.equals(PatientSortParameter.LASTNAME_ASCENDING)) { setSortParameter(PatientSortParameter.LASTNAME_DESCENDING); } else { setSortParameter(PatientSortParameter.LASTNAME_ASCENDING); } search(); } /** * Sorts the patient table by birthdate */ public void sortByBirthdate() { if (sortParameter.equals(PatientSortParameter.BIRTHDATE_NEWEST_FIRST)) { setSortParameter(PatientSortParameter.BIRTHDATE_OLDEST_FIRST); } else { setSortParameter(PatientSortParameter.BIRTHDATE_NEWEST_FIRST); } search(); } /** * Selection-Listener for data table displaying {@link Patient} instances. * * @param event * Provided by UI context. */ public void selectionListenerPatient(AjaxBehaviorEvent event) { UIExtendedDataTable dataTable = (UIExtendedDataTable) event .getComponent(); Object originalKey = dataTable.getRowKey(); for (Object selectionKey : selectionPatient) { dataTable.setRowKey(selectionKey); if (dataTable.isRowAvailable()) { // cleaning potentially selected AND outdated consent consentBean.cleanOutdatedViewState(); selectedPatient = (Patient) dataTable.getRowData(); consentBean.setSelectedPatient(selectedPatient); LOG.info("[PATIENT SELECTED] " + selectedPatient.getPatientID()); consentBean.search(); } else { LOG.warn("ROW NOT AVAILABLE"); } } dataTable.setRowKey(originalKey); } /** * @param selection * the selectionPatient to set */ public void setPatientSelection(Collection<Object> selection) { this.selectionPatient = selection; } /** * @return the selectionPatient */ public Collection<Object> getPatientSelection() { return selectionPatient; } /** * @return the selectedPatient */ public Patient getSelectedPatient() { return selectedPatient; } }