Package org.apache.sling.discovery.impl.topology.connector

Source Code of org.apache.sling.discovery.impl.topology.connector.TopologyConnectorClient

/*
* 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.apache.sling.discovery.impl.topology.connector;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.URL;
import java.util.Date;
import java.util.Iterator;
import java.util.UUID;
import java.util.zip.GZIPOutputStream;

import javax.servlet.http.HttpServletResponse;

import org.apache.commons.httpclient.Credentials;
import org.apache.commons.httpclient.DefaultHttpMethodRetryHandler;
import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.URIException;
import org.apache.commons.httpclient.UsernamePasswordCredentials;
import org.apache.commons.httpclient.auth.AuthScope;
import org.apache.commons.httpclient.methods.ByteArrayRequestEntity;
import org.apache.commons.httpclient.methods.DeleteMethod;
import org.apache.commons.httpclient.methods.PutMethod;
import org.apache.commons.httpclient.methods.StringRequestEntity;
import org.apache.commons.httpclient.params.HttpMethodParams;
import org.apache.sling.commons.json.JSONException;
import org.apache.sling.discovery.ClusterView;
import org.apache.sling.discovery.InstanceDescription;
import org.apache.sling.discovery.impl.Config;
import org.apache.sling.discovery.impl.cluster.ClusterViewService;
import org.apache.sling.discovery.impl.topology.announcement.Announcement;
import org.apache.sling.discovery.impl.topology.announcement.AnnouncementFilter;
import org.apache.sling.discovery.impl.topology.announcement.AnnouncementRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* A topology connector client is used for sending (pinging) a remote topology
* connector servlet and exchanging announcements with it
*/
public class TopologyConnectorClient implements
        TopologyConnectorClientInformation {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    /** the endpoint url **/
    private final URL connectorUrl;

    /** the cluster view service **/
    private final ClusterViewService clusterViewService;

    /** the config service to user **/
    private final Config config;

    /** the id of this connection **/
    private final UUID id;

    /** the announcement registry **/
    private final AnnouncementRegistry announcementRegistry;

    /** the last inherited announcement **/
    private Announcement lastInheritedAnnouncement;

    /** the time when the last announcement was inherited - for webconsole use only **/
    private long lastPingedAt;
   
    /** the information about this server **/
    private final String serverInfo;
   
    /** the status code of the last post **/
    private int lastStatusCode = -1;
   
    /** SLING-3316: whether or not this connector was auto-stopped **/
    private boolean autoStopped = false;
   
    /** more details about connection failures **/
    private String statusDetails = null;
   
    /** SLING-2882: whether or not to suppress ping warnings **/
    private boolean suppressPingWarnings_ = false;

    private TopologyRequestValidator requestValidator;

    /** value of Content-Encoding of the last request **/
    private String lastRequestEncoding;

    /** value of Content-Encoding of the last repsonse **/
    private String lastResponseEncoding;

    /** SLING-3382: unix-time at which point the backoff-period ends and pings can be sent again **/
    private long backoffPeriodEnd = -1;
   
    TopologyConnectorClient(final ClusterViewService clusterViewService,
            final AnnouncementRegistry announcementRegistry, final Config config,
            final URL connectorUrl, final String serverInfo) {
        if (clusterViewService == null) {
            throw new IllegalArgumentException(
                    "clusterViewService must not be null");
        }
        if (announcementRegistry == null) {
            throw new IllegalArgumentException(
                    "announcementRegistry must not be null");
        }
        if (config == null) {
            throw new IllegalArgumentException("config must not be null");
        }
        if (connectorUrl == null) {
            throw new IllegalArgumentException("connectorUrl must not be null");
        }
        this.requestValidator = new TopologyRequestValidator(config);
        this.clusterViewService = clusterViewService;
        this.announcementRegistry = announcementRegistry;
        this.config = config;
        this.connectorUrl = connectorUrl;
        this.serverInfo = serverInfo;
        this.id = UUID.randomUUID();
    }

    /** ping the server and pass the announcements between the two **/
    void ping(final boolean force) {
      if (autoStopped) {
        // then we suppress any further pings!
        logger.debug("ping: autoStopped=true, hence suppressing any further pings.");
        return;
      }
      if (force) {
          backoffPeriodEnd = -1;
      } else if (backoffPeriodEnd>0) {
          if (System.currentTimeMillis()<backoffPeriodEnd) {
              logger.debug("ping: not issueing a heartbeat due to backoff instruction from peer.");
              return;
          } else {
                logger.debug("ping: backoff period ended, issuing another ping now.");
          }
      }
        final String uri = connectorUrl.toString()+"."+clusterViewService.getSlingId()+".json";
      if (logger.isDebugEnabled()) {
        logger.debug("ping: connectorUrl=" + connectorUrl + ", complete uri=" + uri);
      }
        HttpClient httpClient = new HttpClient();
        final PutMethod method = new PutMethod(uri);
        Announcement resultingAnnouncement = null;
        try {
            String userInfo = connectorUrl.getUserInfo();
            if (userInfo != null) {
                Credentials c = new UsernamePasswordCredentials(userInfo);
                httpClient.getState().setCredentials(
                        new AuthScope(method.getURI().getHost(), method
                                .getURI().getPort()), c);
            }

            Announcement topologyAnnouncement = new Announcement(
                    clusterViewService.getSlingId());
            topologyAnnouncement.setServerInfo(serverInfo);
            final ClusterView clusterView = clusterViewService
                    .getClusterView();
            topologyAnnouncement.setLocalCluster(clusterView);
            if (force) {
                logger.debug("ping: sending a resetBackoff");
                topologyAnnouncement.setResetBackoff(true);
            }
            announcementRegistry.addAllExcept(topologyAnnouncement, clusterView, new AnnouncementFilter() {
               
                public boolean accept(final String receivingSlingId, final Announcement announcement) {
                    // filter out announcements that are of old cluster instances
                    // which I dont really have in my cluster view at the moment
                    final Iterator<InstanceDescription> it =
                            clusterViewService.getClusterView().getInstances().iterator();
                    while(it.hasNext()) {
                        final InstanceDescription instance = it.next();
                        if (instance.getSlingId().equals(receivingSlingId)) {
                            // then I have the receiving instance in my cluster view
                            // all fine then
                            return true;
                        }
                    }
                    // looks like I dont have the receiving instance in my cluster view
                    // then I should also not propagate that announcement anywhere
                    return false;
                }
            });
            final String p = requestValidator.encodeMessage(topologyAnnouncement.asJSON());
           
            if (logger.isDebugEnabled()) {
                logger.debug("ping: topologyAnnouncement json is: " + p);
            }
            requestValidator.trustMessage(method, p);
            if (config.isGzipConnectorRequestsEnabled()) {
                // tell the server that the content is gzipped:
                method.addRequestHeader("Content-Encoding", "gzip");
                // and gzip the body:
                final ByteArrayOutputStream baos = new ByteArrayOutputStream();
                final GZIPOutputStream gzipOut = new GZIPOutputStream(baos);
                gzipOut.write(p.getBytes("UTF-8"));
                gzipOut.close();
                final byte[] gzippedEncodedJson = baos.toByteArray();
                method.setRequestEntity(new ByteArrayRequestEntity(gzippedEncodedJson, "application/json"));
                lastRequestEncoding = "gzip";
            } else {
                // otherwise plaintext:
                method.setRequestEntity(new StringRequestEntity(p, "application/json", "UTF-8"));
                lastRequestEncoding = "plaintext";
            }
            // independent of request-gzipping, we do accept the response to be gzipped,
            // so indicate this to the server:
            method.addRequestHeader("Accept-Encoding", "gzip");
            DefaultHttpMethodRetryHandler retryhandler = new DefaultHttpMethodRetryHandler(0, false);
            httpClient.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, retryhandler);
            httpClient.getHttpConnectionManager().getParams().setConnectionTimeout(1000*config.getConnectionTimeout());
            httpClient.getHttpConnectionManager().getParams().setSoTimeout(1000*config.getSoTimeout());
            method.getParams().setSoTimeout(1000*config.getSoTimeout());
            httpClient.executeMethod(method);
          if (logger.isDebugEnabled()) {
              logger.debug("ping: done. code=" + method.getStatusCode() + " - "
                      + method.getStatusText());
          }
            lastStatusCode = method.getStatusCode();
            lastResponseEncoding = null;
            if (method.getStatusCode()==HttpServletResponse.SC_OK) {
                final Header contentEncoding = method.getResponseHeader("Content-Encoding");
                if (contentEncoding!=null && contentEncoding.getValue()!=null &&
                        contentEncoding.getValue().contains("gzip")) {
                    lastResponseEncoding = "gzip";
                } else {
                    lastResponseEncoding = "plaintext";
                }
                String responseBody = requestValidator.decodeMessage(method); // limiting to 16MB, should be way enough
              if (logger.isDebugEnabled()) {
                logger.debug("ping: response body=" + responseBody);
              }
                if (responseBody!=null && responseBody.length()>0) {
                    Announcement inheritedAnnouncement = Announcement
                            .fromJSON(responseBody);
                    final long backoffInterval = inheritedAnnouncement.getBackoffInterval();
                    if (backoffInterval>0) {
                        // then reset the backoffPeriodEnd:
                       
                        /* minus 1 sec to avoid slipping the interval by a few millis */
                        this.backoffPeriodEnd = System.currentTimeMillis() + (1000 * backoffInterval) - 1000;
                        logger.debug("ping: servlet instructed to backoff: backoffInterval="+backoffInterval+", resulting in period end of "+new Date(backoffPeriodEnd));
                    } else {
                        logger.debug("ping: servlet did not instruct any backoff-ing at this stage");
                        this.backoffPeriodEnd = -1;
                    }
                    if (inheritedAnnouncement.isLoop()) {
                      if (logger.isDebugEnabled()) {
                          logger.debug("ping: connector response indicated a loop detected. not registering this announcement from "+
                                      inheritedAnnouncement.getOwnerId());
                      }
                      if (inheritedAnnouncement.getOwnerId().equals(clusterViewService.getSlingId())) {
                        // SLING-3316 : local-loop detected. Check config to see if we should stop this connector
                       
                          if (config.isAutoStopLocalLoopEnabled()) {
                          inheritedAnnouncement = null; // results in connected -> false and representsloop -> true
                          autoStopped = true; // results in isAutoStopped -> true
                        }
                      }
                    } else {
                        inheritedAnnouncement.setInherited(true);
                        if (announcementRegistry
                                .registerAnnouncement(inheritedAnnouncement)==-1) {
                          if (logger.isDebugEnabled()) {
                              logger.debug("ping: connector response is from an instance which I already see in my topology"
                                      + inheritedAnnouncement);
                          }
                            statusDetails = "receiving side is seeing me via another path (connector or cluster) already (loop)";
                            return;
                        }
                    }
                    resultingAnnouncement = inheritedAnnouncement;
                    statusDetails = null;
                } else {
                    statusDetails = "no response body received";
                }
            } else {
                statusDetails = "got HTTP Status-Code: "+lastStatusCode;
            }
          // SLING-2882 : reset suppressPingWarnings_ flag in success case
        suppressPingWarnings_ = false;
        } catch (URIException e) {
            logger.warn("ping: Got URIException: " + e + ", uri=" + uri);
            statusDetails = e.toString();
        } catch (IOException e) {
          // SLING-2882 : set/check the suppressPingWarnings_ flag
          if (suppressPingWarnings_) {
            if (logger.isDebugEnabled()) {
              logger.debug("ping: got IOException: " + e + ", uri=" + uri);
            }
          } else {
            suppressPingWarnings_ = true;
          logger.warn("ping: got IOException [suppressing further warns]: " + e + ", uri=" + uri);
          }
            statusDetails = e.toString();
        } catch (JSONException e) {
            logger.warn("ping: got JSONException: " + e);
            statusDetails = e.toString();
        } catch (RuntimeException re) {
            logger.warn("ping: got RuntimeException: " + re, re);
            statusDetails = re.toString();
        } finally {
            method.releaseConnection();
            lastInheritedAnnouncement = resultingAnnouncement;
            lastPingedAt = System.currentTimeMillis();
        }
    }

    public int getStatusCode() {
        return lastStatusCode;
    }
   
    public URL getConnectorUrl() {
        return connectorUrl;
    }
   
    public boolean representsLoop() {
      if (autoStopped) {
        return true;
      }
        if (lastInheritedAnnouncement == null) {
            return false;
        } else {
            return lastInheritedAnnouncement.isLoop();
        }
    }

    public boolean isConnected() {
      if (autoStopped) {
        return false;
      }
        if (lastInheritedAnnouncement == null) {
            return false;
        } else {
            return announcementRegistry.hasActiveAnnouncement(lastInheritedAnnouncement.getOwnerId());
        }
    }
   
    public String getStatusDetails() {
        if (autoStopped) {
            return "auto-stopped";
        }
        if (lastInheritedAnnouncement == null) {
            return statusDetails;
        } else {
            if (announcementRegistry.hasActiveAnnouncement(lastInheritedAnnouncement.getOwnerId())) {
                // still active - so no status details
                return null;
            } else {
                return "received announcement has expired (it was last renewed "+new Date(lastPingedAt)+") - consider increasing heartbeat timeout";
            }
        }
    }
   
    public long getLastHeartbeatSent() {
        return lastPingedAt;
    }
   
    public int getNextHeartbeatDue() {
        final long absDue;
        if (backoffPeriodEnd>0) {
            absDue = backoffPeriodEnd;
        } else {
            absDue = lastPingedAt + 1000*config.getHeartbeatInterval();
        }
        final int relDue = (int) ((absDue - System.currentTimeMillis()) / 1000);
        if (relDue<0) {
            return -1;
        } else {
            return relDue;
        }
    }
   
    public boolean isAutoStopped() {
      return autoStopped;
    }
   
    public String getLastRequestEncoding() {
        return lastRequestEncoding==null ? "" : lastRequestEncoding;
    }

    public String getLastResponseEncoding() {
        return lastResponseEncoding==null ? "" : lastResponseEncoding;
    }
   
    public String getRemoteSlingId() {
        if (lastInheritedAnnouncement == null) {
            return null;
        } else {
            return lastInheritedAnnouncement.getOwnerId();
        }
    }

    public String getId() {
        return id.toString();
    }

    /** Disconnect this connector **/
    public void disconnect() {
        final String uri = connectorUrl.toString()+"."+clusterViewService.getSlingId()+".json";
      if (logger.isDebugEnabled()) {
        logger.debug("disconnect: connectorUrl=" + connectorUrl + ", complete uri="+uri);
      }

        if (lastInheritedAnnouncement != null) {
            announcementRegistry
                    .unregisterAnnouncement(lastInheritedAnnouncement
                            .getOwnerId());
        }

        HttpClient httpClient = new HttpClient();
        final DeleteMethod method = new DeleteMethod(uri);

        try {
            String userInfo = connectorUrl.getUserInfo();
            if (userInfo != null) {
                Credentials c = new UsernamePasswordCredentials(userInfo);
                httpClient.getState().setCredentials(
                        new AuthScope(method.getURI().getHost(), method
                                .getURI().getPort()), c);
            }

            requestValidator.trustMessage(method, null);
            httpClient.executeMethod(method);
          if (logger.isDebugEnabled()) {
              logger.debug("disconnect: done. code=" + method.getStatusCode()
                      + " - " + method.getStatusText());
          }
            // ignoring the actual statuscode though as there's little we can
            // do about it after this point
        } catch (URIException e) {
            logger.warn("disconnect: Got URIException: " + e);
        } catch (IOException e) {
            logger.warn("disconnect: got IOException: " + e);
        } catch (RuntimeException re) {
            logger.error("disconnect: got RuntimeException: " + re, re);
        } finally {
            method.releaseConnection();
        }
    }
}
TOP

Related Classes of org.apache.sling.discovery.impl.topology.connector.TopologyConnectorClient

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.