/**
* The contents of this file are subject to the Mozilla Public License
* Version 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
* License for the specific language governing rights and limitations under
* the License.
*
* The Original Code is OpenELIS code.
*
* Copyright (C) The Minnesota Department of Health. All Rights Reserved.
*
* Contributor(s): CIRG, University of Washington, Seattle WA.
*/
package us.mn.state.health.lims.common.externalLinks;
import java.io.IOException;
import java.net.ConnectException;
import java.net.SocketTimeoutException;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.io.IOUtils;
import org.apache.commons.validator.GenericValidator;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.CoreConnectionPNames;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.dom4j.DocumentException;
import us.mn.state.health.lims.common.log.LogEvent;
import us.mn.state.health.lims.common.provider.query.PatientDemographicsSearchResults;
public class ExternalPatientSearch implements Runnable {
private static final String GET_PARAM_PWD = "pwd";
private static final String GET_PARAM_NAME = "name";
private static final String GET_PARAM_NATIONAL_ID = "nationalId";
private static final String GET_PARAM_ST = "ST";
private static final String GET_PARAM_SUBJECT = "subjectNumber";
private static final String GET_PARAM_LAST = "last";
private static final String GET_PARAM_FIRST = "first";
private static final String GET_PARAM_GUID = "guid";
public static final String MALFORMED_REPLY = "Malformed reply";
public static final String URI_BUILD_FAILURE = "Failed to build URI";
private boolean finished = false;
private String firstName;
private String lastName;
private String STNumber;
private String subjectNumber;
private String nationalId;
private String guid;
private String connectionString;
private String connectionName;
private String connectionPassword;
private int timeout = 0;
protected String resultXML;
protected List<PatientDemographicsSearchResults> searchResults;
protected List<String> errors;
protected int returnStatus = HttpStatus.SC_CREATED;
synchronized public void setConnectionCredentials(String connectionString, String name, String password,
int timeout_Mil) {
if (finished) {
throw new IllegalStateException("ServiceCredentials set after ExternalPatientSearch thread was started");
}
this.connectionString = connectionString;
connectionName = name;
connectionPassword = password;
timeout = timeout_Mil;
}
synchronized public void setSearchCriteria(String lastName, String firstName, String STNumber, String subjectNumber, String nationalID, String guid)
throws IllegalStateException {
if (finished) {
throw new IllegalStateException("Search criteria set after ExternalPatientSearch thread was started");
}
this.lastName = lastName;
this.firstName = firstName;
this.STNumber = STNumber;
this.subjectNumber = subjectNumber;
this.nationalId = nationalID;
this.guid = guid;
}
synchronized public List<PatientDemographicsSearchResults> getSearchResults() {
if (!finished) {
throw new IllegalStateException("Results requested before ExternalPatientSearch thread was finished");
}
if (searchResults == null) {
searchResults = new ArrayList<PatientDemographicsSearchResults>();
convertXMLToResults();
}
return searchResults;
}
public int getSearchResultStatus() {
if (!finished) {
throw new IllegalStateException("Result status requested ExternalPatientSearch before search was finished");
}
return returnStatus;
}
public void run() {
try {
synchronized (this) {
if (noSearchTerms()) {
return;
}
if (connectionCredentialsIncomplete()) {
throw new IllegalStateException("Search requested before connection credentials set.");
}
errors = new ArrayList<String>();
doSearch();
}
} finally {
finished = true;
}
}
private boolean connectionCredentialsIncomplete() {
return GenericValidator.isBlankOrNull(connectionString) || GenericValidator.isBlankOrNull(connectionName)
|| GenericValidator.isBlankOrNull(connectionPassword);
}
private boolean noSearchTerms() {
return GenericValidator.isBlankOrNull(firstName) && GenericValidator.isBlankOrNull(lastName)
&& GenericValidator.isBlankOrNull(nationalId) && GenericValidator.isBlankOrNull(STNumber);
}
// protected for unit testing called from synchronized block
protected void doSearch() {
HttpClient httpclient = new DefaultHttpClient();
setTimeout(httpclient);
HttpGet httpget = new HttpGet(connectionString);
URI getUri = buildConnectionString(httpget.getURI());
httpget.setURI(getUri);
try {
// Ignore hostname mismatches and allow trust of self-signed certs
SSLSocketFactory sslsf = new SSLSocketFactory(new TrustSelfSignedStrategy(), SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
Scheme https = new Scheme("https", 443, sslsf);
ClientConnectionManager ccm = httpclient.getConnectionManager();
ccm.getSchemeRegistry().register(https);
HttpResponse getResponse = httpclient.execute(httpget);
returnStatus = getResponse.getStatusLine().getStatusCode();
setPossibleErrors();
setResults(IOUtils.toString(getResponse.getEntity().getContent(), "UTF-8"));
} catch (SocketTimeoutException e) {
errors.add("Response from patient information server took too long.");
LogEvent.logError("ExternalPatientSearch", "doSearch()", e.toString());
// System.out.println("Tinny time out" + e);
} catch (ConnectException e) {
errors.add("Unable to connect to patient information form service. Service may not be running");
LogEvent.logError("ExternalPatientSearch", "doSearch()", e.toString());
// System.out.println("you no talks? " + e);
} catch (IOException e) {
errors.add("IO error trying to read input stream.");
LogEvent.logError("ExternalPatientSearch", "doSearch()", e.toString());
// System.out.println("all else failed " + e);
} catch (KeyManagementException e) {
errors.add("Key management error trying to connect to external search service.");
LogEvent.logError("ExternalPatientSearch", "doSearch()", e.toString());
} catch (UnrecoverableKeyException e) {
errors.add("Unrecoverable key error trying to connect to external search service.");
LogEvent.logError("ExternalPatientSearch", "doSearch()", e.toString());
} catch (NoSuchAlgorithmException e) {
errors.add("No such encyrption algorithm error trying to connect to external search service.");
LogEvent.logError("ExternalPatientSearch", "doSearch()", e.toString());
} catch (KeyStoreException e) {
errors.add("Keystore error trying to connect to external search service.");
LogEvent.logError("ExternalPatientSearch", "doSearch()", e.toString());
} catch (RuntimeException e) {
errors.add("Runtime error trying to retrieve patient information.");
LogEvent.logError("ExternalPatientSearch", "doSearch()", e.toString());
httpget.abort();
throw e;
} finally {
httpclient.getConnectionManager().shutdown();
}
}
private void convertXMLToResults() {
if (!GenericValidator.isBlankOrNull(resultXML)) {
ExternalPatientSearchResultsXMLConverter converter = new ExternalPatientSearchResultsXMLConverter();
try {
searchResults = converter.convertXMLToSearchResults( resultXML );
} catch (DocumentException e) {
errors.add(MALFORMED_REPLY);
}
}
}
protected void setResults(String resultsAsXml) {
resultXML = resultsAsXml;
}
private void setPossibleErrors() {
switch (returnStatus) {
case HttpStatus.SC_UNAUTHORIZED: {
errors.add("Access denied to patient information service.");
break;
}
case HttpStatus.SC_INTERNAL_SERVER_ERROR: {
errors.add("Internal error on patient information service.");
break;
}
case HttpStatus.SC_OK:{
break; //NO-OP
}
default: {
errors.add("Unknown error trying to connect to patient information service. Resturn status was "
+ returnStatus);
}
}
}
private void setTimeout(HttpClient httpclient) {
// this one causes a timeout if a connection is established but there is
// no response within <timeout> seconds
httpclient.getParams().setParameter(CoreConnectionPNames.SO_TIMEOUT, timeout);
// this one causes a timeout if no connection is established within 10 seconds
httpclient.getParams().setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, timeout);
}
private URI buildConnectionString(URI uriStart) {
URI uriFinal = null;
try {
uriFinal = new URIBuilder(uriStart)
.addParameter(GET_PARAM_FIRST, firstName)
.addParameter(GET_PARAM_LAST, lastName)
.addParameter(GET_PARAM_ST, STNumber)
.addParameter(GET_PARAM_SUBJECT, subjectNumber)
.addParameter(GET_PARAM_NATIONAL_ID, nationalId)
.addParameter(GET_PARAM_GUID, guid)
.addParameter(GET_PARAM_NAME, connectionName)
.addParameter(GET_PARAM_PWD, connectionPassword)
.build();
} catch (URISyntaxException e) {
errors.add(URI_BUILD_FAILURE);
}
return uriFinal;
}
}