/** * The contents of this file are subject to the OpenMRS Public License * Version 1.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://license.openmrs.org * * 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. * * Copyright (C) OpenMRS, LLC. All Rights Reserved. */ package org.openmrs.module.sync.server; import java.io.IOException; import java.net.MalformedURLException; import java.security.GeneralSecurityException; import org.apache.commons.httpclient.Header; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.HttpStatus; import org.apache.commons.httpclient.URI; import org.apache.commons.httpclient.contrib.ssl.EasySSLProtocolSocketFactory; import org.apache.commons.httpclient.methods.PostMethod; import org.apache.commons.httpclient.methods.multipart.ByteArrayPartSource; import org.apache.commons.httpclient.methods.multipart.FilePart; import org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity; import org.apache.commons.httpclient.methods.multipart.Part; import org.apache.commons.httpclient.methods.multipart.StringPart; import org.apache.commons.httpclient.protocol.Protocol; import org.apache.commons.httpclient.protocol.ProtocolSocketFactory; import org.apache.commons.httpclient.protocol.SSLProtocolSocketFactory; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.openmrs.api.context.Context; import org.openmrs.module.sync.SyncConstants; import org.springframework.util.StringUtils; /** * */ public class ServerConnection { private static final Log log = LogFactory.getLog(ServerConnection.class); public static ConnectionResponse cloneParentDB(String address, String username, String password) { return sendExportedData(address, username, password, SyncConstants.CLONE_MESSAGE); } public static ConnectionResponse test(String address, String username, String password) { return sendExportedData(address, username, password, SyncConstants.TEST_MESSAGE); } public static ConnectionResponse sendExportedData(RemoteServer server, String message) { return sendExportedData(server.getAddress(), server.getUsername(), server.getPassword(), message, false); } public static ConnectionResponse sendExportedData(RemoteServer server, String message, boolean isResponse) { return sendExportedData(server.getAddress(), server.getUsername(), server.getPassword(), message, isResponse); } public static ConnectionResponse sendExportedData(String address, String username, String password, String message) { return sendExportedData(address, username, password, message, false); } public static ConnectionResponse sendExportedData(String url, String username, String password, String content, boolean isResponse) { // Default response - default constructor instantiates contains error codes ConnectionResponse syncResponse = new ConnectionResponse(); HttpClient client = new HttpClient(); url = url + SyncConstants.DATA_IMPORT_SERVLET; log.info("POST multipart request to " + url); if (url.startsWith("https")){ try { if (Boolean.parseBoolean(Context.getAdministrationService() .getGlobalProperty(SyncConstants.PROPERTY_ALLOW_SELFSIGNED_CERTS))){ // It is necessary to provide a relative url (from the host name and port to the right) String relativeUrl; URI uri = new URI(url, true); String host = uri.getHost(); int port = uri.getPort(); // URI.getPort() returns -1 if port is not explicitly set if (port <= 0){ port = SyncConstants.DEFAULT_HTTPS_PORT; relativeUrl = url.split(host, 2)[1]; } else { relativeUrl = url.split(host + ":" + port, 2)[1]; } Protocol easyhttps = new Protocol("https", (ProtocolSocketFactory) new EasySSLProtocolSocketFactory(), port); client.getHostConfiguration().setHost(host, port, easyhttps); url = relativeUrl; } } catch(IOException ioe){ log.error("Unable to configure SSL to accept self-signed certificates"); } catch (GeneralSecurityException e) { log.error("Unable to configure SSL to accept self-signed certificates"); } } PostMethod method = new PostMethod(url); try { boolean useCompression = Boolean.parseBoolean(Context.getAdministrationService().getGlobalProperty(SyncConstants.PROPERTY_ENABLE_COMPRESSION, "true")); log.info("use compression: " + useCompression); // Compress content ConnectionRequest request = new ConnectionRequest(content, useCompression); // Create up multipart request Part[] parts = { new FilePart("syncDataFile", new ByteArrayPartSource("syncDataFile", request.getBytes())), new StringPart("username", username), new StringPart("password", password), new StringPart("compressed", String.valueOf(useCompression)), new StringPart("isResponse", String.valueOf(isResponse)), new StringPart("checksum", String.valueOf(request.getChecksum())) }; method.setRequestEntity(new MultipartRequestEntity(parts, method.getParams())); // Open a connection to the server and post the data client.getHttpConnectionManager().getParams().setSoTimeout(ServerConnection.getTimeout().intValue()); client.getHttpConnectionManager().getParams().setConnectionTimeout(ServerConnection.getTimeout().intValue()); int status = client.executeMethod(method); // As long as the response is OK (200) if (status == HttpStatus.SC_OK) { // Decompress the response from the server //log.info("Response from server:" + method.getResponseBodyAsString()); // Check to see if the child/parent sent back a compressed response Header compressionHeader = method.getResponseHeader("Enable-Compression"); useCompression = (compressionHeader!=null)?new Boolean(compressionHeader.getValue()):false; log.info("Response header Enable-Compression: " + useCompression); // Decompress the data received (if compression is enabled) syncResponse = new ConnectionResponse(method.getResponseBodyAsStream(), useCompression); // Now we want to validate the checksum Header checksumHeader = method.getResponseHeader("Content-Checksum"); long checksumReceived = (checksumHeader!=null)?new Long(checksumHeader.getValue()):0; log.info("Response header Content-Checksum: " + checksumReceived); log.info("checksum value received in response header: " + checksumReceived ); log.info("checksum of payload: " + syncResponse.getChecksum()); // TODO Need to figure out what to do with this response if (checksumReceived > 0 && (checksumReceived != syncResponse.getChecksum())) { log.error("ERROR: FAILED CHECKSUM!"); syncResponse.setState(ServerConnectionState.CONNECTION_FAILED); // contains error message } } // if there's an error response code we should set the tran else { // HTTP error response code syncResponse.setResponsePayload("HTTP " + status + " Error Code: " + method.getResponseBodyAsString()); syncResponse.setState(ServerConnectionState.CONNECTION_FAILED); // contains error message } } catch (MalformedURLException mue) { log.error("Malformed URL " + url, mue); syncResponse.setState(ServerConnectionState.MALFORMED_URL); } catch (Exception e) { // all other exceptions really just mean that the connection was bad log.error("Error occurred while sending/receiving data ", e); syncResponse.setState(ServerConnectionState.CONNECTION_FAILED); } finally { method.releaseConnection(); } return syncResponse; } /** * Gets the sync server connection timeout. * * @return the connection timeout in milliseconds. * * @should not throw NPE when timeout global property is not set */ public static Double getTimeout() { // let's figure out a suitable timeout String timeoutGP = Context.getAdministrationService().getGlobalProperty(SyncConstants.PROPERTY_CONNECTION_TIMEOUT); try { if (StringUtils.hasText(timeoutGP)) return Double.valueOf(timeoutGP.trim()); } catch (Exception ex){ log.error("Could not convert " + timeoutGP + " to Double. Please enter a valid number of miliseconds, or leave " + SyncConstants.PROPERTY_CONNECTION_TIMEOUT + " blank to use the default."); } Double timeout = 300000.0; // let's just default at 5 min for now try { Integer maxRecords = new Integer(Context.getAdministrationService() .getGlobalProperty(SyncConstants.PROPERTY_NAME_MAX_RECORDS_WEB, SyncConstants.PROPERTY_NAME_MAX_RECORDS_DEFAULT)); timeout = (3 + (maxRecords * 0.1)) * 6000; // formula we cooked // up after running // several tests: // latency + 0.1N } catch (NumberFormatException nfe) { // it's ok if this fails (not sure how it could) = we'll just do 5 min timeout } return timeout; } }