org.jumpmind.symmetric.transport.http.HttpTransportManager.java Source code

Java tutorial

Introduction

Here is the source code for org.jumpmind.symmetric.transport.http.HttpTransportManager.java

Source

/**
 * Licensed to JumpMind Inc under one or more contributor
 * license agreements.  See the NOTICE file distributed
 * with this work for additional information regarding
 * copyright ownership.  JumpMind Inc licenses this file
 * to you under the GNU General Public License, version 3.0 (GPLv3)
 * (the "License"); you may not use this file except in compliance
 * with the License.
 *
 * You should have received a copy of the GNU General Public License,
 * version 3.0 (GPLv3) along with this library; if not, see
 * <http://www.gnu.org/licenses/>.
 *
 * 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.jumpmind.symmetric.transport.http;

import static org.apache.commons.lang.StringUtils.isNotBlank;
import static org.jumpmind.symmetric.web.WebConstants.MAKE_RESERVATION_PATH;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.List;
import java.util.Map;
import java.util.zip.GZIPInputStream;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.jumpmind.exception.IoException;
import org.jumpmind.symmetric.ISymmetricEngine;
import org.jumpmind.symmetric.common.Constants;
import org.jumpmind.symmetric.common.ParameterConstants;
import org.jumpmind.symmetric.io.IoConstants;
import org.jumpmind.symmetric.model.BatchId;
import org.jumpmind.symmetric.model.IncomingBatch;
import org.jumpmind.symmetric.model.Node;
import org.jumpmind.symmetric.model.OutgoingBatch;
import org.jumpmind.symmetric.transport.AbstractTransportManager;
import org.jumpmind.symmetric.transport.IIncomingTransport;
import org.jumpmind.symmetric.transport.IOutgoingWithResponseTransport;
import org.jumpmind.symmetric.transport.ITransportManager;
import org.jumpmind.symmetric.transport.TransportUtils;
import org.jumpmind.symmetric.util.SymmetricUtils;
import org.jumpmind.symmetric.web.WebConstants;
import org.jumpmind.util.AppUtils;

/**
 * Allow remote communication to nodes, in order to push data, pull data, and
 * send messages.
 */
public class HttpTransportManager extends AbstractTransportManager implements ITransportManager {

    private ISymmetricEngine engine;

    public HttpTransportManager() {
    }

    public HttpTransportManager(ISymmetricEngine engine) {
        super(engine.getExtensionService());
        this.engine = engine;
    }

    public int sendCopyRequest(Node local) throws IOException {
        StringBuilder data = new StringBuilder();
        Map<String, BatchId> batchIds = engine.getIncomingBatchService().findMaxBatchIdsByChannel();
        for (String channelId : batchIds.keySet()) {
            if (!Constants.CHANNEL_CONFIG.equals(channelId) && !Constants.CHANNEL_HEARTBEAT.equals(channelId)) {
                BatchId batchId = batchIds.get(channelId);
                append(data, channelId + "-" + batchId.getNodeId(), batchId.getBatchId());
            }
        }
        String securityToken = engine.getNodeService().findNodeSecurity(local.getNodeId()).getNodePassword();
        String url = addParameterTokens(engine.getParameterService().getRegistrationUrl() + "/copy",
                local.getNodeId(), null, securityToken);
        url = add(url, WebConstants.EXTERNAL_ID, engine.getParameterService().getExternalId(), "&");
        url = add(url, WebConstants.NODE_GROUP_ID, engine.getParameterService().getNodeGroupId(), "&");

        log.info("Contact server to do node copy using a url of: " + url);
        return sendMessage(new URL(url), data.toString());
    }

    public int sendAcknowledgement(Node remote, List<IncomingBatch> list, Node local, String securityToken,
            String registrationUrl) throws IOException {
        if (list != null && list.size() > 0) {
            String data = getAcknowledgementData(remote.requires13Compatiblity(), local.getNodeId(), list);
            return sendMessage("ack", remote, local, data, securityToken, registrationUrl);
        }
        return HttpURLConnection.HTTP_OK;
    }

    public void writeAcknowledgement(OutputStream out, Node remote, List<IncomingBatch> list, Node local,
            String securityToken) throws IOException {
        writeMessage(out, getAcknowledgementData(remote.requires13Compatiblity(), local.getNodeId(), list));
    }

    protected int sendMessage(String action, Node remote, Node local, String data, String securityToken,
            String registrationUrl) throws IOException {
        return sendMessage(new URL(buildURL(action, remote, local, securityToken, null, registrationUrl)), data);
    }

    protected int sendMessage(URL url, String data) throws IOException {
        HttpURLConnection conn = openConnection(url, getBasicAuthUsername(), getBasicAuthPassword());
        conn.setRequestMethod("POST");
        conn.setAllowUserInteraction(false);
        conn.setDoOutput(true);
        conn.setConnectTimeout(getHttpTimeOutInMs());
        conn.setReadTimeout(getHttpTimeOutInMs());
        OutputStream os = conn.getOutputStream();
        try {
            writeMessage(os, data);
            return conn.getResponseCode();
        } finally {
            IOUtils.closeQuietly(os);
        }
    }

    public static HttpURLConnection openConnection(URL url, String username, String password) throws IOException {
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setRequestProperty(WebConstants.HEADER_ACCEPT_CHARSET, IoConstants.ENCODING);
        setBasicAuthIfNeeded(conn, username, password);
        return conn;
    }

    public static void setBasicAuthIfNeeded(HttpURLConnection conn, String username, String password) {
        if (StringUtils.isNotEmpty(username) && StringUtils.isNotEmpty(password)) {
            String userpassword = username + ":" + password;
            String encodedAuthorization = new String(Base64.encodeBase64(userpassword.getBytes()));
            conn.setRequestProperty("Authorization", "Basic " + encodedAuthorization);
        }
    }

    public int getOutputStreamSize() {
        return engine.getParameterService().getInt(ParameterConstants.TRANSPORT_HTTP_PUSH_STREAM_SIZE);
    }

    public boolean isOutputStreamEnabled() {
        return engine.getParameterService().is(ParameterConstants.TRANSPORT_HTTP_PUSH_STREAM_ENABLED);
    }

    public int getHttpTimeOutInMs() {
        return engine.getParameterService().getInt(ParameterConstants.TRANSPORT_HTTP_TIMEOUT);
    }

    public boolean isUseCompression() {
        return engine.getParameterService().is(ParameterConstants.TRANSPORT_HTTP_USE_COMPRESSION_CLIENT);
    }

    public int getCompressionLevel() {
        return engine.getParameterService().getInt(ParameterConstants.TRANSPORT_HTTP_COMPRESSION_LEVEL);
    }

    public int getCompressionStrategy() {
        return engine.getParameterService().getInt(ParameterConstants.TRANSPORT_HTTP_COMPRESSION_STRATEGY);
    }

    public String getBasicAuthUsername() {
        return engine.getParameterService().getString(ParameterConstants.TRANSPORT_HTTP_BASIC_AUTH_USERNAME);
    }

    public String getBasicAuthPassword() {
        return engine.getParameterService().getString(ParameterConstants.TRANSPORT_HTTP_BASIC_AUTH_PASSWORD);
    }

    public void writeMessage(OutputStream out, String data) throws IOException {
        PrintWriter pw = new PrintWriter(new OutputStreamWriter(out, IoConstants.ENCODING), true);
        pw.println(data);
        pw.flush();
    }

    public IIncomingTransport getFilePullTransport(Node remote, Node local, String securityToken,
            Map<String, String> requestProperties, String registrationUrl) throws IOException {
        HttpURLConnection conn = createGetConnectionFor(
                new URL(buildURL("filesync/pull", remote, local, securityToken, null, registrationUrl)));
        if (requestProperties != null) {
            for (String key : requestProperties.keySet()) {
                conn.addRequestProperty(key, requestProperties.get(key));
            }
        }
        return new HttpIncomingTransport(conn, engine.getParameterService());
    }

    public IIncomingTransport getPullTransport(Node remote, Node local, String securityToken,
            Map<String, String> requestProperties, String registrationUrl) throws IOException {
        HttpURLConnection conn = createGetConnectionFor(
                new URL(buildURL("pull", remote, local, securityToken, null, registrationUrl)));
        if (requestProperties != null) {
            for (String key : requestProperties.keySet()) {
                conn.addRequestProperty(key, requestProperties.get(key));
            }
        }
        return new HttpIncomingTransport(conn, engine.getParameterService());
    }

    public IIncomingTransport getAckStatusTransport(OutgoingBatch batch, Node remote, Node local,
            String securityToken, String registrationUrl) {
        try {
            HttpURLConnection conn = createGetConnectionFor(
                    new URL(buildURL(String.format("ackstatus/%s", batch.getBatchId()), remote, local,
                            securityToken, null, registrationUrl)));
            return new HttpIncomingTransport(conn, engine.getParameterService());
        } catch (IOException ex) {
            throw new IoException(ex);
        }
    }

    @Override
    public void makeReservationTransport(String poolId, String channelId, Node remote, Node local,
            String securityToken, String registrationUrl) {
        try {
            HttpURLConnection conn = createGetConnectionFor(
                    new URL(buildURL(String.format("%s/%s", MAKE_RESERVATION_PATH, poolId), remote, local,
                            securityToken, channelId, registrationUrl)));
            conn.connect();
            SymmetricUtils.analyzeResponseCode(conn.getResponseCode());
        } catch (IOException ex) {
            throw new IoException(ex);
        }
    }

    public IOutgoingWithResponseTransport getPushTransport(Node remote, Node local, String securityToken,
            String channelId, String registrationUrl) throws IOException {
        URL url = new URL(buildURL("push", remote, local, securityToken, channelId, registrationUrl));
        return new HttpOutgoingTransport(url, getHttpTimeOutInMs(), isUseCompression(), getCompressionStrategy(),
                getCompressionLevel(), getBasicAuthUsername(), getBasicAuthPassword(), isOutputStreamEnabled(),
                getOutputStreamSize(), false);
    }

    public IOutgoingWithResponseTransport getFilePushTransport(Node remote, Node local, String securityToken,
            String registrationUrl) throws IOException {
        URL url = new URL(buildURL("filesync/push", remote, local, securityToken, null, registrationUrl));
        return new HttpOutgoingTransport(url, getHttpTimeOutInMs(), isUseCompression(), getCompressionStrategy(),
                getCompressionLevel(), getBasicAuthUsername(), getBasicAuthPassword(), isOutputStreamEnabled(),
                getOutputStreamSize(), true);
    }

    public IIncomingTransport getRegisterTransport(Node node, String registrationUrl) throws IOException {
        return new HttpIncomingTransport(
                createGetConnectionFor(new URL(buildRegistrationUrl(registrationUrl, node))),
                engine.getParameterService());
    }

    public static String buildRegistrationUrl(String baseUrl, Node node) throws IOException {
        if (baseUrl == null) {
            baseUrl = "";
        }
        StringBuilder builder = new StringBuilder(baseUrl);
        builder.append("/registration?");
        append(builder, WebConstants.NODE_GROUP_ID, node.getNodeGroupId());
        append(builder, WebConstants.EXTERNAL_ID, node.getExternalId());
        append(builder, WebConstants.SYNC_URL, node.getSyncUrl());
        append(builder, WebConstants.SCHEMA_VERSION, node.getSchemaVersion());
        append(builder, WebConstants.DATABASE_TYPE, node.getDatabaseType());
        append(builder, WebConstants.DATABASE_VERSION, node.getDatabaseVersion());
        append(builder, WebConstants.SYMMETRIC_VERSION, node.getSymmetricVersion());
        append(builder, WebConstants.HOST_NAME, AppUtils.getHostName());
        append(builder, WebConstants.IP_ADDRESS, AppUtils.getIpAddress());
        return builder.toString();
    }

    protected HttpURLConnection createGetConnectionFor(URL url) throws IOException {
        HttpURLConnection conn = HttpTransportManager.openConnection(url, getBasicAuthUsername(),
                getBasicAuthPassword());
        conn.setRequestProperty("accept-encoding", "gzip");
        conn.setConnectTimeout(getHttpTimeOutInMs());
        conn.setReadTimeout(getHttpTimeOutInMs());
        conn.setRequestMethod("GET");
        return conn;
    }

    protected static InputStream getInputStreamFrom(HttpURLConnection connection) throws IOException {
        String type = connection.getContentEncoding();
        InputStream in = connection.getInputStream();
        if (!StringUtils.isBlank(type) && type.equals("gzip")) {
            in = new GZIPInputStream(in);
        }
        return in;
    }

    /**
     * If the content is gzip'd, then uncompress.
     */
    protected static BufferedReader getReaderFrom(HttpURLConnection connection) throws IOException {
        String type = connection.getContentEncoding();
        InputStream in = connection.getInputStream();
        if (!StringUtils.isBlank(type) && type.equals("gzip")) {
            in = new GZIPInputStream(in);
        }
        return TransportUtils.toReader(in);
    }

    /**
     * Build a url for an action. Include the nodeid and the security token.
     */
    protected String buildURL(String action, Node remote, Node local, String securityToken, String channelId,
            String registrationUrl) throws IOException {
        return addParameterTokens((resolveURL(remote.getSyncUrl(), registrationUrl) + "/" + action),
                local.getNodeId(), channelId, securityToken);
    }

    protected String addParameterTokens(String base, String nodeId, String channelId, String securityToken) {
        StringBuilder sb = new StringBuilder(addNodeId(base, nodeId, "?"));
        sb.append("&");
        sb.append(WebConstants.SECURITY_TOKEN);
        sb.append("=");
        sb.append(securityToken);
        if (isNotBlank(channelId)) {
            append(sb, WebConstants.CHANNEL_ID, channelId);
        }
        append(sb, WebConstants.HOST_NAME, AppUtils.getHostName());
        append(sb, WebConstants.IP_ADDRESS, AppUtils.getIpAddress());
        return sb.toString();
    }

    protected String addNodeId(String base, String nodeId, String connector) {
        return add(base, WebConstants.NODE_ID, nodeId, connector);
    }

    protected String add(String base, String key, String value, String connector) {
        StringBuilder sb = new StringBuilder(base);
        sb.append(connector);
        sb.append(key);
        sb.append("=");
        try {
            sb.append(URLEncoder.encode(value, IoConstants.ENCODING));
        } catch (UnsupportedEncodingException e) {
            throw new IoException(e);
        }
        return sb.toString();
    }
}