org.openmrs.module.sync.server.ServerConnection.java Source code

Java tutorial

Introduction

Here is the source code for org.openmrs.module.sync.server.ServerConnection.java

Source

/**
 * 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;
    }

}