Added: 
sling/trunk/bundles/extensions/discovery/base/src/main/java/org/apache/sling/discovery/base/connectors/ping/TopologyConnectorClient.java
URL: 
http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/discovery/base/src/main/java/org/apache/sling/discovery/base/connectors/ping/TopologyConnectorClient.java?rev=1709601&view=auto
==============================================================================
--- 
sling/trunk/bundles/extensions/discovery/base/src/main/java/org/apache/sling/discovery/base/connectors/ping/TopologyConnectorClient.java
 (added)
+++ 
sling/trunk/bundles/extensions/discovery/base/src/main/java/org/apache/sling/discovery/base/connectors/ping/TopologyConnectorClient.java
 Tue Oct 20 14:12:31 2015
@@ -0,0 +1,496 @@
+/*
+ * 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.base.connectors.ping;
+
+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.http.Header;
+import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.Credentials;
+import org.apache.http.auth.UsernamePasswordCredentials;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpDelete;
+import org.apache.http.client.methods.HttpPut;
+import org.apache.http.client.protocol.HttpClientContext;
+import org.apache.http.config.SocketConfig;
+import org.apache.http.entity.ByteArrayEntity;
+import org.apache.http.entity.ContentType;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.sling.commons.json.JSONException;
+import org.apache.sling.discovery.ClusterView;
+import org.apache.sling.discovery.InstanceDescription;
+import org.apache.sling.discovery.base.commons.ClusterViewService;
+import org.apache.sling.discovery.base.commons.UndefinedClusterViewException;
+import org.apache.sling.discovery.base.connectors.BaseConfig;
+import org.apache.sling.discovery.base.connectors.announcement.Announcement;
+import 
org.apache.sling.discovery.base.connectors.announcement.AnnouncementFilter;
+import 
org.apache.sling.discovery.base.connectors.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 BaseConfig 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 BaseConfig 
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);
+       }
+       final HttpClientContext clientContext = HttpClientContext.create();
+       final CloseableHttpClient httpClient = createHttpClient();
+       final HttpPut putRequest = new HttpPut(uri);
+
+       // setting the connection timeout (idle connection, configured in 
seconds)
+       putRequest.setConfig(RequestConfig.
+                       custom().
+                       
setConnectTimeout(1000*config.getSocketConnectionTimeout()).
+                       build());
+
+        Announcement resultingAnnouncement = null;
+        try {
+            String userInfo = connectorUrl.getUserInfo();
+            if (userInfo != null) {
+                Credentials c = new UsernamePasswordCredentials(userInfo);
+               clientContext.getCredentialsProvider().setCredentials(
+                        new AuthScope(putRequest.getURI().getHost(), putRequest
+                                .getURI().getPort()), c);
+            }
+
+            Announcement topologyAnnouncement = new Announcement(
+                    clusterViewService.getSlingId());
+            topologyAnnouncement.setServerInfo(serverInfo);
+            final ClusterView clusterView;
+            try {
+                clusterView = clusterViewService
+                        .getLocalClusterView();
+            } catch (UndefinedClusterViewException e) {
+                // SLING-5030 : then we cannot ping
+                logger.warn("ping: no clusterView available at the moment, 
cannot ping others now: "+e);
+                return;
+            }
+            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 = 
+                            clusterView.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(putRequest, p);
+            if (config.isGzipConnectorRequestsEnabled()) {
+                // tell the server that the content is gzipped:
+                putRequest.addHeader("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();
+                putRequest.setEntity(new ByteArrayEntity(gzippedEncodedJson, 
ContentType.APPLICATION_JSON));
+                lastRequestEncoding = "gzip";
+            } else {
+                // otherwise plaintext:
+               final StringEntity plaintext = new StringEntity(p, "UTF-8");
+               
plaintext.setContentType(ContentType.APPLICATION_JSON.getMimeType());
+               putRequest.setEntity(plaintext);
+                lastRequestEncoding = "plaintext";
+            }
+            // independent of request-gzipping, we do accept the response to 
be gzipped,
+            // so indicate this to the server:
+            putRequest.addHeader("Accept-Encoding", "gzip");
+            final CloseableHttpResponse response = 
httpClient.execute(putRequest, clientContext);
+               if (logger.isDebugEnabled()) {
+                   logger.debug("ping: done. code=" + 
response.getStatusLine().getStatusCode() + " - "
+                           + response.getStatusLine().getReasonPhrase());
+               }
+            lastStatusCode = response.getStatusLine().getStatusCode();
+            lastResponseEncoding = null;
+            if 
(response.getStatusLine().getStatusCode()==HttpServletResponse.SC_OK) {
+                final Header contentEncoding = 
response.getFirstHeader("Content-Encoding");
+                if (contentEncoding!=null && contentEncoding.getValue()!=null 
&&
+                        contentEncoding.getValue().contains("gzip")) {
+                    lastResponseEncoding = "gzip";
+                } else {
+                    lastResponseEncoding = "plaintext";
+                }
+                final String responseBody = 
requestValidator.decodeMessage(putRequest.getURI().getPath(), response); // 
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 (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 {
+            putRequest.releaseConnection();
+            lastInheritedAnnouncement = resultingAnnouncement;
+            lastPingedAt = System.currentTimeMillis();
+            try {
+                               httpClient.close();
+                       } catch (IOException e) {
+                               logger.error("disconnect: could not close 
httpClient: "+e, e);
+                       }
+        }
+    }
+
+       private CloseableHttpClient createHttpClient() {
+               final HttpClientBuilder builder = HttpClientBuilder.create();
+       // setting the SoTimeout (which is configured in seconds)
+       builder.setDefaultSocketConfig(SocketConfig.
+                       custom().
+                       setSoTimeout(1000*config.getSoTimeout()).
+                       build());
+               builder.setRetryHandler(new DefaultHttpRequestRetryHandler(0, 
false));
+
+       return builder.build();
+       }
+
+    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 getLastPingSent() {
+        return lastPingedAt;
+    }
+    
+    public int getNextPingDue() {
+        final long absDue;
+        if (backoffPeriodEnd>0) {
+            absDue = backoffPeriodEnd;
+        } else {
+            absDue = lastPingedAt + 1000*config.getConnectorPingInterval();
+        }
+        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());
+        }
+
+        final HttpClientContext clientContext = HttpClientContext.create();
+        final CloseableHttpClient httpClient = createHttpClient();
+        final HttpDelete deleteRequest = new HttpDelete(uri);
+        // setting the connection timeout (idle connection, configured in 
seconds)
+        deleteRequest.setConfig(RequestConfig.
+                       custom().
+                       
setConnectTimeout(1000*config.getSocketConnectionTimeout()).
+                       build());
+
+        try {
+            String userInfo = connectorUrl.getUserInfo();
+            if (userInfo != null) {
+                Credentials c = new UsernamePasswordCredentials(userInfo);
+                clientContext.getCredentialsProvider().setCredentials(
+                        new AuthScope(deleteRequest.getURI().getHost(), 
deleteRequest
+                                .getURI().getPort()), c);
+            }
+
+            requestValidator.trustMessage(deleteRequest, null);
+            final CloseableHttpResponse response = 
httpClient.execute(deleteRequest, clientContext);
+               if (logger.isDebugEnabled()) {
+                   logger.debug("disconnect: done. code=" + 
response.getStatusLine().getStatusCode()
+                           + " - " + 
response.getStatusLine().getReasonPhrase());
+               }
+            // ignoring the actual statuscode though as there's little we can
+            // do about it after this point
+        } catch (IOException e) {
+            logger.warn("disconnect: got IOException: " + e);
+        } catch (RuntimeException re) {
+            logger.error("disconnect: got RuntimeException: " + re, re);
+        } finally {
+            deleteRequest.releaseConnection();
+            try {
+                               httpClient.close();
+                       } catch (IOException e) {
+                               logger.error("disconnect: could not close 
httpClient: "+e, e);
+                       }
+        }
+    }
+}

Propchange: 
sling/trunk/bundles/extensions/discovery/base/src/main/java/org/apache/sling/discovery/base/connectors/ping/TopologyConnectorClient.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: 
sling/trunk/bundles/extensions/discovery/base/src/main/java/org/apache/sling/discovery/base/connectors/ping/TopologyConnectorClientInformation.java
URL: 
http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/discovery/base/src/main/java/org/apache/sling/discovery/base/connectors/ping/TopologyConnectorClientInformation.java?rev=1709601&view=auto
==============================================================================
--- 
sling/trunk/bundles/extensions/discovery/base/src/main/java/org/apache/sling/discovery/base/connectors/ping/TopologyConnectorClientInformation.java
 (added)
+++ 
sling/trunk/bundles/extensions/discovery/base/src/main/java/org/apache/sling/discovery/base/connectors/ping/TopologyConnectorClientInformation.java
 Tue Oct 20 14:12:31 2015
@@ -0,0 +1,63 @@
+/*
+ * 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.base.connectors.ping;
+
+import java.net.URL;
+
+/**
+ * provides information about a topology connector client
+ */
+public interface TopologyConnectorClientInformation {
+
+    /** the endpoint url where this connector is connecting to **/
+    URL getConnectorUrl();
+
+    /** return the http status code of the last post to the servlet, -1 if no 
post was ever done **/
+    int getStatusCode();
+
+    /** SLING-3316 : whether or not this connector was auto-stopped **/
+    boolean isAutoStopped();
+    
+    /** whether or not this connector was able to successfully connect **/
+    boolean isConnected();
+    
+    /** provides more details about connection failures **/
+    String getStatusDetails();
+
+    /** whether or not the counterpart of this connector has detected a loop 
in the topology connectors **/
+    boolean representsLoop();
+    
+    /** the sling id of the remote end **/
+    String getRemoteSlingId();
+
+    /** the unique id of this connector **/
+    String getId();
+
+    /** the Content-Encoding of the last request **/
+    String getLastRequestEncoding();
+
+    /** the Content-Encoding of the last response **/
+    String getLastResponseEncoding();
+
+    /** the unix-millis when the last heartbeat was sent **/
+    long getLastPingSent();
+
+    /** the seconds until the next heartbeat is due **/
+    int getNextPingDue();
+}

Propchange: 
sling/trunk/bundles/extensions/discovery/base/src/main/java/org/apache/sling/discovery/base/connectors/ping/TopologyConnectorClientInformation.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: 
sling/trunk/bundles/extensions/discovery/base/src/main/java/org/apache/sling/discovery/base/connectors/ping/TopologyConnectorServlet.java
URL: 
http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/discovery/base/src/main/java/org/apache/sling/discovery/base/connectors/ping/TopologyConnectorServlet.java?rev=1709601&view=auto
==============================================================================
--- 
sling/trunk/bundles/extensions/discovery/base/src/main/java/org/apache/sling/discovery/base/connectors/ping/TopologyConnectorServlet.java
 (added)
+++ 
sling/trunk/bundles/extensions/discovery/base/src/main/java/org/apache/sling/discovery/base/connectors/ping/TopologyConnectorServlet.java
 Tue Oct 20 14:12:31 2015
@@ -0,0 +1,360 @@
+/*
+ * 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.base.connectors.ping;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.StringTokenizer;
+import java.util.zip.GZIPOutputStream;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.commons.json.JSONException;
+import org.apache.sling.discovery.ClusterView;
+import org.apache.sling.discovery.base.commons.ClusterViewHelper;
+import org.apache.sling.discovery.base.commons.ClusterViewService;
+import org.apache.sling.discovery.base.commons.UndefinedClusterViewException;
+import org.apache.sling.discovery.base.connectors.BaseConfig;
+import org.apache.sling.discovery.base.connectors.announcement.Announcement;
+import 
org.apache.sling.discovery.base.connectors.announcement.AnnouncementFilter;
+import 
org.apache.sling.discovery.base.connectors.announcement.AnnouncementRegistry;
+import org.apache.sling.discovery.base.connectors.ping.wl.SubnetWhitelistEntry;
+import org.apache.sling.discovery.base.connectors.ping.wl.WhitelistEntry;
+import 
org.apache.sling.discovery.base.connectors.ping.wl.WildcardWhitelistEntry;
+import org.osgi.service.component.ComponentContext;
+import org.osgi.service.http.HttpService;
+import org.osgi.service.http.NamespaceException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Servlet which receives topology announcements at
+ * /libs/sling/topology/connector*
+ * without authorization (authorization is handled either via
+ * hmac-signature with a shared key or via a flexible whitelist)
+ */
+@SuppressWarnings("serial")
+@Component(immediate = true)
+@Service(value=TopologyConnectorServlet.class)
+public class TopologyConnectorServlet extends HttpServlet {
+
+    /** 
+     * prefix under which the topology connector servlet is registered -
+     * the URL will consist of this prefix + "connector.slingId.json" 
+     */
+    private static final String TOPOLOGY_CONNECTOR_PREFIX = 
"/libs/sling/topology";
+
+    private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+    @Reference
+    private AnnouncementRegistry announcementRegistry;
+
+    @Reference
+    private ClusterViewService clusterViewService;
+
+    @Reference
+    private HttpService httpService;
+    
+    @Reference
+    private BaseConfig config;
+
+    /** 
+     * This list contains WhitelistEntry (ips/hostnames, cidr, wildcards),
+     * each filtering some hostname/addresses that are allowed to connect to 
this servlet.
+     **/
+    private final List<WhitelistEntry> whitelist = new 
ArrayList<WhitelistEntry>();
+    
+    /** Set of plaintext whitelist entries - for faster lookups **/
+    private final Set<String> plaintextWhitelist = new HashSet<String>();
+
+    private TopologyRequestValidator requestValidator;
+
+    @Activate
+    protected void activate(final ComponentContext context) {
+        whitelist.clear();
+        if (!config.isHmacEnabled()) {
+            String[] whitelistConfig = config.getTopologyConnectorWhitelist();
+            initWhitelist(whitelistConfig);
+        }
+        requestValidator = new TopologyRequestValidator(config);
+        
+        try {
+            
httpService.registerServlet(TopologyConnectorServlet.TOPOLOGY_CONNECTOR_PREFIX, 
+                    this, null, null);
+            logger.info("activate: connector servlet registered at "+
+                    TopologyConnectorServlet.TOPOLOGY_CONNECTOR_PREFIX);
+        } catch (ServletException e) {
+            logger.error("activate: ServletException while registering 
topology connector servlet: "+e, e);
+        } catch (NamespaceException e) {
+            logger.error("activate: NamespaceException while registering 
topology connector servlet: "+e, e);
+        }
+    }
+    
+    @Deactivate
+    protected void deactivate() {
+        httpService.unregister(TOPOLOGY_CONNECTOR_PREFIX);
+    }
+
+    void initWhitelist(String[] whitelistConfig) {
+        if (whitelistConfig==null) {
+            return;
+        }
+        for (int i = 0; i < whitelistConfig.length; i++) {
+            String aWhitelistEntry = whitelistConfig[i];
+            
+            WhitelistEntry whitelistEntry = null;
+            if (aWhitelistEntry.contains(".") && 
aWhitelistEntry.contains("/")) {
+                // then this is a CIDR notation
+                try{
+                    whitelistEntry = new SubnetWhitelistEntry(aWhitelistEntry);
+                } catch(Exception e) {
+                    logger.error("activate: wrongly formatted CIDR subnet 
definition. Expected eg '1.2.3.4/24'. ignoring: "+aWhitelistEntry);
+                    continue;
+                }
+            } else if (aWhitelistEntry.contains(".") && 
aWhitelistEntry.contains(" ")) {
+                // then this is a IP/subnet-mask notation
+                try{
+                    final StringTokenizer st = new 
StringTokenizer(aWhitelistEntry, " ");
+                    final String ip = st.nextToken();
+                    if (st.hasMoreTokens()) {
+                        final String mask = st.nextToken();
+                        if (st.hasMoreTokens()) {
+                            logger.error("activate: wrongly formatted ip 
subnet definition. Expected '10.1.2.3 255.0.0.0'. Ignoring: "+aWhitelistEntry);
+                            continue;
+                        }
+                        whitelistEntry = new SubnetWhitelistEntry(ip, mask);
+                    }
+                } catch(Exception e) {
+                    logger.error("activate: wrongly formatted ip subnet 
definition. Expected '10.1.2.3 255.0.0.0'. Ignoring: "+aWhitelistEntry);
+                    continue;
+                }
+            }
+            if (whitelistEntry==null) {
+                if (aWhitelistEntry.contains("*") || 
aWhitelistEntry.contains("?")) {
+                    whitelistEntry = new 
WildcardWhitelistEntry(aWhitelistEntry);
+                } else {
+                    plaintextWhitelist.add(aWhitelistEntry);
+                }
+            }
+            logger.info("activate: adding whitelist entry: " + 
aWhitelistEntry);
+            if (whitelistEntry!=null) {
+                whitelist.add(whitelistEntry);
+            }
+        }
+    }
+
+    @Override
+    protected void doDelete(HttpServletRequest request, HttpServletResponse 
response)
+            throws ServletException, IOException {
+
+        if (!isWhitelisted(request)) {
+            // in theory it would be 403==forbidden, but that would reveal that
+            // a resource would exist there in the first place
+            response.sendError(HttpServletResponse.SC_NOT_FOUND);
+            return;
+        }
+
+        final String[] pathInfo = request.getPathInfo().split("\\.");
+        final String extension = pathInfo.length==3 ? pathInfo[2] : "";
+        if (!"json".equals(extension)) {
+            response.sendError(HttpServletResponse.SC_NOT_FOUND);
+            return;
+        }
+        final String selector = pathInfo.length==3 ? pathInfo[1] : "";
+
+        announcementRegistry.unregisterAnnouncement(selector);
+    }
+    
+    @Override
+    protected void doPut(HttpServletRequest request, HttpServletResponse 
response)
+            throws ServletException, IOException {
+
+        if (!isWhitelisted(request)) {
+            // in theory it would be 403==forbidden, but that would reveal that
+            // a resource would exist there in the first place
+            response.sendError(HttpServletResponse.SC_NOT_FOUND);
+            return;
+        }
+
+        final String[] pathInfo = request.getPathInfo().split("\\.");
+        final String extension = pathInfo.length==3 ? pathInfo[2] : "";
+        if (!"json".equals(extension)) {
+            response.sendError(HttpServletResponse.SC_NOT_FOUND);
+            return;
+        }
+        
+        final String selector = pathInfo.length==3 ? pathInfo[1] : "";
+
+        String topologyAnnouncementJSON = 
requestValidator.decodeMessage(request);
+       if (logger.isDebugEnabled()) {
+               logger.debug("doPost: incoming topology announcement is: "
+                       + topologyAnnouncementJSON);
+       }
+        final Announcement incomingTopologyAnnouncement;
+        try {
+            incomingTopologyAnnouncement = Announcement
+                    .fromJSON(topologyAnnouncementJSON);
+
+            if (!incomingTopologyAnnouncement.getOwnerId().equals(selector)) {
+                response.sendError(HttpServletResponse.SC_BAD_REQUEST);
+                return;
+            }
+
+            String slingId = clusterViewService.getSlingId();
+            if (slingId==null) {
+               
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+               logger.info("doPut: no slingId available. Service not ready as 
expected at the moment.");
+               return;
+            }
+                       incomingTopologyAnnouncement.removeInherited(slingId);
+
+            final Announcement replyAnnouncement = new Announcement(
+                    slingId);
+
+            long backoffInterval = -1;
+            ClusterView clusterView = clusterViewService.getLocalClusterView();
+            if (!incomingTopologyAnnouncement.isCorrectVersion()) {
+                logger.warn("doPost: rejecting an announcement from an 
incompatible connector protocol version: "
+                        + incomingTopologyAnnouncement);
+                response.sendError(HttpServletResponse.SC_BAD_REQUEST);
+                return;
+            } else if (ClusterViewHelper.contains(clusterView, 
incomingTopologyAnnouncement
+                    .getOwnerId())) {
+               if (logger.isDebugEnabled()) {
+                       logger.debug("doPost: rejecting an announcement from an 
instance that is part of my cluster: "
+                               + incomingTopologyAnnouncement);
+               }
+                // marking as 'loop'
+                replyAnnouncement.setLoop(true);
+                backoffInterval = config.getBackoffStandbyInterval();
+            } else if (ClusterViewHelper.containsAny(clusterView, 
incomingTopologyAnnouncement
+                    .listInstances())) {
+               if (logger.isDebugEnabled()) {
+                       logger.debug("doPost: rejecting an announcement as it 
contains instance(s) that is/are part of my cluster: "
+                               + incomingTopologyAnnouncement);
+               }
+                // marking as 'loop'
+                replyAnnouncement.setLoop(true);
+                backoffInterval = config.getBackoffStandbyInterval();
+            } else {
+                backoffInterval = announcementRegistry
+                        .registerAnnouncement(incomingTopologyAnnouncement);
+                if (logger.isDebugEnabled()) {
+                    logger.debug("doPost: backoffInterval after registration: 
"+backoffInterval);
+                }
+                if (backoffInterval==-1) {
+                       if (logger.isDebugEnabled()) {
+                       logger.debug("doPost: rejecting an announcement from an 
instance that I already see in my topology: "
+                               + incomingTopologyAnnouncement);
+                       }
+                    // marking as 'loop'
+                    replyAnnouncement.setLoop(true);
+                    backoffInterval = config.getBackoffStandbyInterval();
+                } else {
+                    // normal, successful case: replying with the part of the 
topology which this instance sees
+                    replyAnnouncement.setLocalCluster(clusterView);
+                    announcementRegistry.addAllExcept(replyAnnouncement, 
clusterView,
+                            new AnnouncementFilter() {
+    
+                                public boolean accept(final String 
receivingSlingId, Announcement announcement) {
+                                    if (announcement.getPrimaryKey().equals(
+                                            incomingTopologyAnnouncement
+                                                    .getPrimaryKey())) {
+                                        return false;
+                                    }
+                                    return true;
+                                }
+                            });
+                }
+            }
+            if (backoffInterval>0) {
+                replyAnnouncement.setBackoffInterval(backoffInterval);
+                if (logger.isDebugEnabled()) {
+                    logger.debug("doPost: backoffInterval for client set to 
"+replyAnnouncement.getBackoffInterval());
+                }
+            }
+            final String p = 
requestValidator.encodeMessage(replyAnnouncement.asJSON());
+            requestValidator.trustMessage(response, request, p);
+            // gzip the response if the client accepts this
+            final String acceptEncodingHeader = 
request.getHeader("Accept-Encoding");
+            if (acceptEncodingHeader!=null && 
acceptEncodingHeader.contains("gzip")) {
+                // tell the client that the content is gzipped:
+                response.setHeader("Content-Encoding", "gzip");
+                
+                // then gzip the body
+                final GZIPOutputStream gzipOut = new 
GZIPOutputStream(response.getOutputStream());
+                gzipOut.write(p.getBytes("UTF-8"));
+                gzipOut.close();
+            } else {
+                // otherwise plaintext
+                final PrintWriter pw = response.getWriter();
+                pw.print(p);
+                pw.flush();
+            }
+        } catch (JSONException e) {
+            logger.error("doPost: Got a JSONException: " + e, e);
+            response.sendError(500);
+        } catch (UndefinedClusterViewException e) {
+            logger.warn("doPost: no clusterView available at the moment - 
cannot handle connectors now: "+e);
+            response.sendError(503); // "please retry, but atm I can't help 
since I'm isolated"
+        }
+
+    }
+    
+    /** Checks if the provided request's remote server is whitelisted **/
+    boolean isWhitelisted(final HttpServletRequest request) {
+        if (config.isHmacEnabled()) {
+            final boolean isTrusted = requestValidator.isTrusted(request);
+            if (!isTrusted) {
+                logger.info("isWhitelisted: rejecting distrusted " + 
request.getRemoteAddr()
+                        + ", " + request.getRemoteHost());
+            }
+            return isTrusted;
+        }
+        
+        if (plaintextWhitelist.contains(request.getRemoteHost()) ||
+                plaintextWhitelist.contains(request.getRemoteAddr())) {
+            return true;
+        }
+
+        for (Iterator<WhitelistEntry> it = whitelist.iterator(); 
it.hasNext();) {
+            WhitelistEntry whitelistEntry = it.next();
+            if (whitelistEntry.accepts(request)) {
+                return true;
+            }
+        }
+        logger.info("isWhitelisted: rejecting " + request.getRemoteAddr()
+                + ", " + request.getRemoteHost());
+        return false;
+    }
+
+}

Propchange: 
sling/trunk/bundles/extensions/discovery/base/src/main/java/org/apache/sling/discovery/base/connectors/ping/TopologyConnectorServlet.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: 
sling/trunk/bundles/extensions/discovery/base/src/main/java/org/apache/sling/discovery/base/connectors/ping/TopologyRequestValidator.java
URL: 
http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/discovery/base/src/main/java/org/apache/sling/discovery/base/connectors/ping/TopologyRequestValidator.java?rev=1709601&view=auto
==============================================================================
--- 
sling/trunk/bundles/extensions/discovery/base/src/main/java/org/apache/sling/discovery/base/connectors/ping/TopologyRequestValidator.java
 (added)
+++ 
sling/trunk/bundles/extensions/discovery/base/src/main/java/org/apache/sling/discovery/base/connectors/ping/TopologyRequestValidator.java
 Tue Oct 20 14:12:31 2015
@@ -0,0 +1,585 @@
+/*
+ * 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 SF 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.base.connectors.ping;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.security.AlgorithmParameters;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.InvalidParameterSpecException;
+import java.security.spec.KeySpec;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.zip.GZIPInputStream;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.Mac;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.PBEKeySpec;
+import javax.crypto.spec.SecretKeySpec;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.io.IOUtils;
+import org.apache.http.Header;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.sling.commons.json.JSONArray;
+import org.apache.sling.commons.json.JSONException;
+import org.apache.sling.commons.json.JSONObject;
+import org.apache.sling.discovery.base.connectors.BaseConfig;
+
+/**
+ * Request Validator helper.
+ */
+public class TopologyRequestValidator {
+
+    public static final String SIG_HEADER = "X-SlingTopologyTrust";
+
+    public static final String HASH_HEADER = "X-SlingTopologyHash";
+
+    /**
+     * Maximum number of keys to keep in memory.
+     */
+    private static final int MAXKEYS = 5;
+
+    /**
+     * Minimum number of keys to keep in memory.
+     */
+    private static final int MINKEYS = 3;
+
+    /**
+     * true if trust information should be in request headers.
+     */
+    private boolean trustEnabled;
+
+    /**
+     * true if encryption of the message payload should be encrypted.
+     */
+    private boolean encryptionEnabled;
+
+    /**
+     * map of hmac keys, keyed by key number.
+     */
+    private Map<Integer, Key> keys = new ConcurrentHashMap<Integer, Key>();
+
+    /**
+     * The shared key.
+     */
+    private String sharedKey;
+
+    /**
+     * TTL of each shared key generation.
+     */
+    private long interval;
+
+    /**
+     * If true, everything is deactivated.
+     */
+    private boolean deactivated;
+
+    private SecureRandom random = new SecureRandom();
+
+    /**
+     * Create a TopologyRequestValidator.
+     *
+     * @param config the configuation object
+     */
+    public TopologyRequestValidator(BaseConfig config) {
+        trustEnabled = false;
+        encryptionEnabled = false;
+        if (config.isHmacEnabled()) {
+            trustEnabled = true;
+            sharedKey = config.getSharedKey();
+            interval = config.getKeyInterval();
+            encryptionEnabled = config.isEncryptionEnabled();
+        }
+        deactivated = false;
+    }
+
+    /**
+     * Encodes a request returning the encoded body
+     *
+     * @param body
+     * @return the encoded body.
+     * @throws IOException
+     */
+    public String encodeMessage(String body) throws IOException {
+        checkActive();
+        if (encryptionEnabled) {
+            try {
+                JSONObject json = new JSONObject();
+                json.put("payload", new JSONArray(encrypt(body)));
+                return json.toString();
+            } catch (InvalidKeyException e) {
+                e.printStackTrace();
+                throw new IOException("Unable to Encrypt Message " + 
e.getMessage());
+            } catch (IllegalBlockSizeException e) {
+                throw new IOException("Unable to Encrypt Message " + 
e.getMessage());
+            } catch (BadPaddingException e) {
+                throw new IOException("Unable to Encrypt Message " + 
e.getMessage());
+            } catch (UnsupportedEncodingException e) {
+                throw new IOException("Unable to Encrypt Message " + 
e.getMessage());
+            } catch (NoSuchAlgorithmException e) {
+                throw new IOException("Unable to Encrypt Message " + 
e.getMessage());
+            } catch (NoSuchPaddingException e) {
+                throw new IOException("Unable to Encrypt Message " + 
e.getMessage());
+            } catch (JSONException e) {
+                throw new IOException("Unable to Encrypt Message " + 
e.getMessage());
+            } catch (InvalidKeySpecException e) {
+                throw new IOException("Unable to Encrypt Message " + 
e.getMessage());
+            } catch (InvalidParameterSpecException e) {
+                throw new IOException("Unable to Encrypt Message " + 
e.getMessage());
+            }
+
+        }
+        return body;
+    }
+
+    /**
+     * Decode a message sent from the client.
+     *
+     * @param request the request object for the message.
+     * @return the message in clear text.
+     * @throws IOException if there is a problem decoding the message or the
+     *             message is invalid.
+     */
+    public String decodeMessage(HttpServletRequest request) throws IOException 
{
+        checkActive();
+        return decodeMessage("request:", request.getRequestURI(), 
getRequestBody(request),
+            request.getHeader(HASH_HEADER));
+    }
+
+    /**
+     * Decode a response from the server.
+     *
+     * @param response the response.
+     * @return the message in clear text.
+     * @throws IOException if there was a problem decoding the message.
+     */
+    public String decodeMessage(String uri, HttpResponse response) throws 
IOException {
+        checkActive();
+        return decodeMessage("response:", uri, getResponseBody(response),
+            getResponseHeader(response, HASH_HEADER));
+    }
+
+    /**
+     * Decode a message
+     *
+     * @param prefix the prefix to indicate if the message is a request or
+     *            response message.
+     * @param url the url associated with the message.
+     * @param body the body of the message.
+     * @param requestHash a hash of the message.
+     * @return the message in clear text
+     * @throws IOException if the message can't be decrypted.
+     */
+    private String decodeMessage(String prefix, String url, String body, 
String requestHash)
+            throws IOException {
+        if (trustEnabled) {
+            String bodyHash = hash(prefix + url + ":" + body);
+            if (bodyHash.equals(requestHash)) {
+                if (encryptionEnabled) {
+                    try {
+                        JSONObject json = new JSONObject(body);
+                        if (json.has("payload")) {
+                            return decrypt(json.getJSONArray("payload"));
+                        }
+                    } catch (JSONException e) {
+                        throw new IOException("Encrypted Message is in the 
correct json format");
+                    } catch (InvalidKeyException e) {
+                        throw new IOException("Encrypted Message is in the 
correct json format");
+                    } catch (IllegalBlockSizeException e) {
+                        throw new IOException("Encrypted Message is in the 
correct json format");
+                    } catch (BadPaddingException e) {
+                        throw new IOException("Encrypted Message is in the 
correct json format");
+                    } catch (NoSuchAlgorithmException e) {
+                        throw new IOException("Encrypted Message is in the 
correct json format");
+                    } catch (NoSuchPaddingException e) {
+                        throw new IOException("Encrypted Message is in the 
correct json format");
+                    } catch (InvalidAlgorithmParameterException e) {
+                        throw new IOException("Encrypted Message is in the 
correct json format");
+                    } catch (InvalidKeySpecException e) {
+                        throw new IOException("Encrypted Message is in the 
correct json format");
+                    }
+
+                }
+            }
+            throw new IOException("Message is not valid, hash does not match 
message");
+        }
+        return body;
+    }
+
+    /**
+     * Is the request from the client trusted, based on the signature headers.
+     *
+     * @param request the request.
+     * @return true if trusted, or true if this component is disabled.
+     */
+    public boolean isTrusted(HttpServletRequest request) {
+        checkActive();
+        if (trustEnabled) {
+            return checkTrustHeader(request.getHeader(HASH_HEADER),
+                request.getHeader(SIG_HEADER));
+        }
+        return false;
+    }
+
+    /**
+     * Is the response from the server to be trusted by the client.
+     *
+     * @param response the response
+     * @return true if trusted, or true if this component is disabled.
+     */
+    public boolean isTrusted(HttpResponse response) {
+        checkActive();
+        if (trustEnabled) {
+            return checkTrustHeader(getResponseHeader(response, HASH_HEADER),
+                getResponseHeader(response, SIG_HEADER));
+        }
+        return false;
+    }
+
+    /**
+     * Trust a message on the client before sending, only if trust is enabled.
+     *
+     * @param method the method which will have headers set after the call.
+     * @param body the body.
+     */
+    public void trustMessage(HttpUriRequest method, String body) {
+        checkActive();
+        if (trustEnabled) {
+            String bodyHash = hash("request:" + method.getURI().getPath() + 
":" + body);
+            method.setHeader(HASH_HEADER, bodyHash);
+            method.setHeader(SIG_HEADER, createTrustHeader(bodyHash));
+        }
+    }
+
+    /**
+     * Trust a response message sent from the server to the client.
+     *
+     * @param response the response.
+     * @param request the request,
+     * @param body body of the response.
+     */
+    public void trustMessage(HttpServletResponse response, HttpServletRequest 
request, String body) {
+        checkActive();
+        if (trustEnabled) {
+            String bodyHash = hash("response:" + request.getRequestURI() + ":" 
+ body);
+            response.setHeader(HASH_HEADER, bodyHash);
+            response.setHeader(SIG_HEADER, createTrustHeader(bodyHash));
+        }
+    }
+
+    /**
+     * @param body
+     * @return a hash of body base64 encoded.
+     */
+    private String hash(String toHash) {
+        try {
+            MessageDigest m = MessageDigest.getInstance("SHA-256");
+            return new 
String(Base64.encodeBase64(m.digest(toHash.getBytes("UTF-8"))), "UTF-8");
+        } catch (NoSuchAlgorithmException e) {
+            throw new RuntimeException(e.getMessage(), e);
+        } catch (UnsupportedEncodingException e) {
+            throw new RuntimeException(e.getMessage(), e);
+        }
+    }
+
+    /**
+     * Generate a signature of the bodyHash and encode it so that it contains
+     * the key number used to generate the signature.
+     *
+     * @param bodyHash a hash
+     * @return the signature.
+     */
+    private String createTrustHeader(String bodyHash) {
+        try {
+            int keyNo = getCurrentKey();
+            return keyNo + "/" + hmac(keyNo, bodyHash);
+        } catch (UnsupportedEncodingException e) {
+            throw new RuntimeException(e.getMessage(), e);
+        } catch (InvalidKeyException e) {
+            throw new RuntimeException(e.getMessage(), e);
+        } catch (IllegalStateException e) {
+            throw new RuntimeException(e.getMessage(), e);
+        } catch (NoSuchAlgorithmException e) {
+            throw new RuntimeException(e.getMessage(), e);
+        }
+    }
+
+    /**
+     * Check that the signature is a signature of the body hash.
+     *
+     * @param bodyHash the body hash.
+     * @param signature the signature.
+     * @return true if the signature can be trusted.
+     */
+    private boolean checkTrustHeader(String bodyHash, String signature) {
+        try {
+            if (bodyHash == null || signature == null ) {
+                return false;
+            }
+            String[] parts = signature.split("/", 2);
+            int keyNo = Integer.parseInt(parts[0]);
+            return hmac(keyNo, bodyHash).equals(parts[1]);
+        } catch (ArrayIndexOutOfBoundsException e) {
+            return false;
+        } catch (IllegalArgumentException e) {
+            return false;
+        } catch (InvalidKeyException e) {
+            throw new RuntimeException(e.getMessage(), e);
+        } catch (UnsupportedEncodingException e) {
+            throw new RuntimeException(e.getMessage(), e);
+        } catch (IllegalStateException e) {
+            throw new RuntimeException(e.getMessage(), e);
+        } catch (NoSuchAlgorithmException e) {
+            throw new RuntimeException(e.getMessage(), e);
+        } catch (Exception e) {
+            throw new RuntimeException(e.getMessage(), e);
+        }
+    }
+
+    /**
+     * Get a Mac instance for the key number.
+     *
+     * @param keyNo the key number.
+     * @return the mac instance.
+     * @throws NoSuchAlgorithmException
+     * @throws InvalidKeyException
+     * @throws UnsupportedEncodingException
+     */
+    private Mac getMac(int keyNo) throws NoSuchAlgorithmException, 
InvalidKeyException,
+            UnsupportedEncodingException {
+        Mac m = Mac.getInstance("HmacSHA256");
+        m.init(getKey(keyNo));
+        return m;
+    }
+
+    /**
+     * Perform a HMAC on the body using the key specified.
+     *
+     * @param keyNo the key number.
+     * @param bodyHash a hash of the body.
+     * @return the hmac signature.
+     * @throws InvalidKeyException
+     * @throws UnsupportedEncodingException
+     * @throws IllegalStateException
+     * @throws NoSuchAlgorithmException
+     */
+    private String hmac(int keyNo, String bodyHash) throws InvalidKeyException,
+            UnsupportedEncodingException, IllegalStateException, 
NoSuchAlgorithmException {
+        return new 
String(Base64.encodeBase64(getMac(keyNo).doFinal(bodyHash.getBytes("UTF-8"))),
+            "UTF-8");
+    }
+
+    /**
+     * Decrypt the body.
+     *
+     * @param jsonArray the encrypted payload
+     * @return the decrypted payload.
+     * @throws IllegalBlockSizeException
+     * @throws BadPaddingException
+     * @throws UnsupportedEncodingException
+     * @throws InvalidKeyException
+     * @throws NoSuchAlgorithmException
+     * @throws NoSuchPaddingException
+     * @throws InvalidKeySpecException
+     * @throws InvalidAlgorithmParameterException
+     * @throws JSONException
+     */
+    private String decrypt(JSONArray jsonArray) throws 
IllegalBlockSizeException,
+            BadPaddingException, UnsupportedEncodingException, 
InvalidKeyException,
+            NoSuchAlgorithmException, NoSuchPaddingException, 
InvalidAlgorithmParameterException, InvalidKeySpecException, JSONException {
+        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
+        cipher.init(Cipher.DECRYPT_MODE, 
getCiperKey(Base64.decodeBase64(jsonArray.getString(0).getBytes("UTF-8"))), new 
IvParameterSpec(Base64.decodeBase64(jsonArray.getString(1).getBytes("UTF-8"))));
+        return new 
String(cipher.doFinal(Base64.decodeBase64(jsonArray.getString(2).getBytes("UTF-8"))));
+    }
+
+    /**
+     * Encrypt a payload with the numbed key/
+     *
+     * @param payload the payload.
+     * @param keyNo the key number.
+     * @return an encrypted version.
+     * @throws IllegalBlockSizeException
+     * @throws BadPaddingException
+     * @throws UnsupportedEncodingException
+     * @throws InvalidKeyException
+     * @throws NoSuchAlgorithmException
+     * @throws NoSuchPaddingException
+     * @throws InvalidKeySpecException
+     * @throws InvalidParameterSpecException
+     */
+    private List<String> encrypt(String payload) throws 
IllegalBlockSizeException,
+            BadPaddingException, UnsupportedEncodingException, 
InvalidKeyException,
+            NoSuchAlgorithmException, NoSuchPaddingException, 
InvalidKeySpecException, InvalidParameterSpecException {
+        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
+        byte[] salt = new byte[9];
+        random.nextBytes(salt);
+        cipher.init(Cipher.ENCRYPT_MODE, getCiperKey(salt));
+        AlgorithmParameters params = cipher.getParameters();
+        List<String> encrypted = new ArrayList<String>();
+        encrypted.add(new String(Base64.encodeBase64(salt)));
+        encrypted.add(new 
String(Base64.encodeBase64(params.getParameterSpec(IvParameterSpec.class).getIV())));
+        encrypted.add(new 
String(Base64.encodeBase64(cipher.doFinal(payload.getBytes("UTF-8")))));
+        return encrypted;
+    }
+
+    /**
+     * @param salt number of the key.
+     * @return the CupherKey.
+     * @throws UnsupportedEncodingException
+     * @throws NoSuchAlgorithmException
+     * @throws InvalidKeySpecException
+     */
+    private Key getCiperKey(byte[] salt) throws UnsupportedEncodingException, 
NoSuchAlgorithmException, InvalidKeySpecException {
+        SecretKeyFactory factory = 
SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
+        // hashing the password 65K times takes 151ms, hashing 256 times takes 
2ms.
+        // Since the salt has 2^^72 values, 256 times is probably good enough.
+        KeySpec spec = new PBEKeySpec(sharedKey.toCharArray(), salt, 256, 128);
+        SecretKey tmp = factory.generateSecret(spec);
+        SecretKey key = new SecretKeySpec(tmp.getEncoded(), "AES");
+        return key;
+    }
+
+    /**
+     * @param keyNo number of the key.
+     * @return the HMac key.
+     * @throws UnsupportedEncodingException
+     */
+    private Key getKey(int keyNo) throws UnsupportedEncodingException {
+        if(Math.abs(keyNo - getCurrentKey()) > 1 ) {
+            throw new IllegalArgumentException("Key has expired");
+        }
+        if (keys.containsKey(keyNo)) {
+            return keys.get(keyNo);
+        }
+        trimKeys();
+        SecretKeySpec key = new SecretKeySpec(hash(sharedKey + 
keyNo).getBytes("UTF-8"),
+            "HmacSHA256");
+        keys.put(keyNo, key);
+        return key;
+    }
+
+    private int getCurrentKey() {
+        return (int) (System.currentTimeMillis() / interval);
+    }
+
+    /**
+     * dump olf keys.
+     */
+    private void trimKeys() {
+        if (keys.size() > MAXKEYS) {
+            List<Integer> keysKeys = new ArrayList<Integer>(keys.keySet());
+            Collections.sort(keysKeys);
+            for (Integer k : keysKeys) {
+                if (keys.size() < MINKEYS) {
+                    break;
+                }
+                keys.remove(k);
+            }
+        }
+
+    }
+
+    /**
+     * Get the value of a response header.
+     *
+     * @param response the response
+     * @param name the name of the response header.
+     * @return the value of the response header, null if none.
+     */
+    private String getResponseHeader(HttpResponse response, String name) {
+        Header h = response.getFirstHeader(name);
+        if (h == null) {
+            return null;
+        }
+        return h.getValue();
+    }
+
+    /**
+     * Get the request body.
+     *
+     * @param request the request.
+     * @return the body as a string.
+     * @throws IOException
+     */
+    private String getRequestBody(HttpServletRequest request) throws 
IOException {
+        final String contentEncoding = request.getHeader("Content-Encoding");
+        if (contentEncoding!=null && contentEncoding.contains("gzip")) {
+            // then treat the request body as gzip:
+            final GZIPInputStream gzipIn = new 
GZIPInputStream(request.getInputStream());
+            final String gunzippedEncodedJson = IOUtils.toString(gzipIn);
+            gzipIn.close();
+            return gunzippedEncodedJson;
+        } else {
+            // otherwise assume plain-text:
+            return IOUtils.toString(request.getReader());
+        }
+    }
+
+    /**
+     * @param response the response
+     * @return the body of the response from the server.
+     * @throws IOException
+     */
+    private String getResponseBody(HttpResponse response) throws IOException {
+        final Header contentEncoding = 
response.getFirstHeader("Content-Encoding");
+        if (contentEncoding!=null && contentEncoding.getValue()!=null &&
+                contentEncoding.getValue().contains("gzip")) {
+            // then the server sent gzip - treat it so:
+            final GZIPInputStream gzipIn = new 
GZIPInputStream(response.getEntity().getContent());
+            final String gunzippedEncodedJson = IOUtils.toString(gzipIn);
+            gzipIn.close();
+            return gunzippedEncodedJson;
+        } else {
+               // otherwise the server sent plaintext:
+               return IOUtils.toString(response.getEntity().getContent(), 
"UTF-8");
+        }
+    }
+
+    /**
+     * throw an exception if not active.
+     */
+    private void checkActive() {
+        if (deactivated) {
+            throw new IllegalStateException(this.getClass().getName() + " is 
not active");
+        }
+        if ((trustEnabled || encryptionEnabled) && sharedKey == null) {
+            throw new IllegalStateException(this.getClass().getName()
+                + " Shared Key must be set if encryption or signing is 
enabled.");
+        }
+    }
+
+}

Propchange: 
sling/trunk/bundles/extensions/discovery/base/src/main/java/org/apache/sling/discovery/base/connectors/ping/TopologyRequestValidator.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: 
sling/trunk/bundles/extensions/discovery/base/src/main/java/org/apache/sling/discovery/base/connectors/ping/package-info.java
URL: 
http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/discovery/base/src/main/java/org/apache/sling/discovery/base/connectors/ping/package-info.java?rev=1709601&view=auto
==============================================================================
--- 
sling/trunk/bundles/extensions/discovery/base/src/main/java/org/apache/sling/discovery/base/connectors/ping/package-info.java
 (added)
+++ 
sling/trunk/bundles/extensions/discovery/base/src/main/java/org/apache/sling/discovery/base/connectors/ping/package-info.java
 Tue Oct 20 14:12:31 2015
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+
+/**
+ * Provides topology connector implementations for discovery
+ * implementors that choose to extend from discovery.base
+ *
+ * @version 1.0.0
+ */
+@Version("1.0.0")
+package org.apache.sling.discovery.base.connectors.ping;
+
+import aQute.bnd.annotation.Version;
+

Propchange: 
sling/trunk/bundles/extensions/discovery/base/src/main/java/org/apache/sling/discovery/base/connectors/ping/package-info.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: 
sling/trunk/bundles/extensions/discovery/base/src/main/java/org/apache/sling/discovery/base/connectors/ping/wl/SubnetWhitelistEntry.java
URL: 
http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/discovery/base/src/main/java/org/apache/sling/discovery/base/connectors/ping/wl/SubnetWhitelistEntry.java?rev=1709601&view=auto
==============================================================================
--- 
sling/trunk/bundles/extensions/discovery/base/src/main/java/org/apache/sling/discovery/base/connectors/ping/wl/SubnetWhitelistEntry.java
 (added)
+++ 
sling/trunk/bundles/extensions/discovery/base/src/main/java/org/apache/sling/discovery/base/connectors/ping/wl/SubnetWhitelistEntry.java
 Tue Oct 20 14:12:31 2015
@@ -0,0 +1,47 @@
+/*
+ * 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.base.connectors.ping.wl;
+
+import javax.servlet.ServletRequest;
+
+import org.apache.commons.net.util.SubnetUtils;
+import org.apache.commons.net.util.SubnetUtils.SubnetInfo;
+
+/**
+ * Implementation of a WhitelistEntry which accepts
+ * cidr and ip mask notations.
+ */
+public class SubnetWhitelistEntry implements WhitelistEntry {
+
+    private final SubnetInfo subnetInfo;
+    
+    public SubnetWhitelistEntry(String cidrNotation) {
+        subnetInfo = new SubnetUtils(cidrNotation).getInfo();
+    }
+    
+    public SubnetWhitelistEntry(String ip, String subnetMask) {
+        subnetInfo = new SubnetUtils(ip, subnetMask).getInfo();
+    }
+    
+    public boolean accepts(ServletRequest request) {
+        final String remoteAddr = request.getRemoteAddr();
+        return subnetInfo.isInRange(remoteAddr);
+    }
+
+}

Propchange: 
sling/trunk/bundles/extensions/discovery/base/src/main/java/org/apache/sling/discovery/base/connectors/ping/wl/SubnetWhitelistEntry.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: 
sling/trunk/bundles/extensions/discovery/base/src/main/java/org/apache/sling/discovery/base/connectors/ping/wl/WhitelistEntry.java
URL: 
http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/discovery/base/src/main/java/org/apache/sling/discovery/base/connectors/ping/wl/WhitelistEntry.java?rev=1709601&view=auto
==============================================================================
--- 
sling/trunk/bundles/extensions/discovery/base/src/main/java/org/apache/sling/discovery/base/connectors/ping/wl/WhitelistEntry.java
 (added)
+++ 
sling/trunk/bundles/extensions/discovery/base/src/main/java/org/apache/sling/discovery/base/connectors/ping/wl/WhitelistEntry.java
 Tue Oct 20 14:12:31 2015
@@ -0,0 +1,35 @@
+/*
+ * 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.base.connectors.ping.wl;
+
+import javax.servlet.ServletRequest;
+
+/**
+ * A WhitelistEntry is capable of accepting certain requests
+ * depending on a configuration.
+ */
+public interface WhitelistEntry {
+
+    /**
+     * @param request the incoming request which should be accepted or rejected
+     * @return true if the request is accepted by this WhitelistEntry
+     */
+    public boolean accepts(ServletRequest request);
+    
+}

Propchange: 
sling/trunk/bundles/extensions/discovery/base/src/main/java/org/apache/sling/discovery/base/connectors/ping/wl/WhitelistEntry.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: 
sling/trunk/bundles/extensions/discovery/base/src/main/java/org/apache/sling/discovery/base/connectors/ping/wl/WildcardHelper.java
URL: 
http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/discovery/base/src/main/java/org/apache/sling/discovery/base/connectors/ping/wl/WildcardHelper.java?rev=1709601&view=auto
==============================================================================
--- 
sling/trunk/bundles/extensions/discovery/base/src/main/java/org/apache/sling/discovery/base/connectors/ping/wl/WildcardHelper.java
 (added)
+++ 
sling/trunk/bundles/extensions/discovery/base/src/main/java/org/apache/sling/discovery/base/connectors/ping/wl/WildcardHelper.java
 Tue Oct 20 14:12:31 2015
@@ -0,0 +1,52 @@
+/*
+ * 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.base.connectors.ping.wl;
+
+import java.util.regex.Pattern;
+
+/** Helper class for wildcards **/
+public class WildcardHelper {
+
+    /** converts a string containing wildcards (* and ?) into a valid regex **/
+    public static String wildcardAsRegex(String patternWithWildcards) {
+        if (patternWithWildcards==null) {
+            throw new IllegalArgumentException("patternWithWildcards must not 
be null");
+        }
+        return "\\Q"+patternWithWildcards.replace("?", "\\E.\\Q").replace("*", 
"\\E.*\\Q")+"\\E";
+    }
+
+    /**
+     * Compare a given string (comparee) against a pattern that contains 
wildcards
+     * and return true if it matches.
+     * @param comparee the string which should be tested against a pattern 
containing wildcards
+     * @param patternWithWildcards the pattern containing wildcards (* and ?)
+     * @return true if the comparee string matches against the pattern 
containing wildcards
+     */
+    public static boolean matchesWildcard(String comparee, String 
patternWithWildcards) {
+        if (comparee==null) {
+            throw new IllegalArgumentException("comparee must not be null");
+        }
+        if (patternWithWildcards==null) {
+            throw new IllegalArgumentException("patternWithEWildcards must not 
be null");
+        }
+        final String regex = wildcardAsRegex(patternWithWildcards);
+        return Pattern.matches(regex, comparee);
+    }
+
+}

Propchange: 
sling/trunk/bundles/extensions/discovery/base/src/main/java/org/apache/sling/discovery/base/connectors/ping/wl/WildcardHelper.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: 
sling/trunk/bundles/extensions/discovery/base/src/main/java/org/apache/sling/discovery/base/connectors/ping/wl/WildcardWhitelistEntry.java
URL: 
http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/discovery/base/src/main/java/org/apache/sling/discovery/base/connectors/ping/wl/WildcardWhitelistEntry.java?rev=1709601&view=auto
==============================================================================
--- 
sling/trunk/bundles/extensions/discovery/base/src/main/java/org/apache/sling/discovery/base/connectors/ping/wl/WildcardWhitelistEntry.java
 (added)
+++ 
sling/trunk/bundles/extensions/discovery/base/src/main/java/org/apache/sling/discovery/base/connectors/ping/wl/WildcardWhitelistEntry.java
 Tue Oct 20 14:12:31 2015
@@ -0,0 +1,45 @@
+/*
+ * 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.base.connectors.ping.wl;
+
+import javax.servlet.ServletRequest;
+
+/**
+ * Implementation of a WhitelistEntry which can accept
+ * wildcards (* and ?) in both IP and hostname
+ */
+public class WildcardWhitelistEntry implements WhitelistEntry {
+
+    private final String hostOrAddressWithWildcard;
+    
+    public WildcardWhitelistEntry(String hostOrAddressWithWildcard) {
+        this.hostOrAddressWithWildcard = hostOrAddressWithWildcard;
+    }
+    
+    public boolean accepts(ServletRequest request) {
+        if (WildcardHelper.matchesWildcard(request.getRemoteAddr(), 
hostOrAddressWithWildcard)) {
+            return true;
+        }
+        if (WildcardHelper.matchesWildcard(request.getRemoteHost(), 
hostOrAddressWithWildcard)) {
+            return true;
+        }
+        return false;
+    }
+
+}

Propchange: 
sling/trunk/bundles/extensions/discovery/base/src/main/java/org/apache/sling/discovery/base/connectors/ping/wl/WildcardWhitelistEntry.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: 
sling/trunk/bundles/extensions/discovery/base/src/main/java/org/apache/sling/discovery/base/connectors/ping/wl/package-info.java
URL: 
http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/discovery/base/src/main/java/org/apache/sling/discovery/base/connectors/ping/wl/package-info.java?rev=1709601&view=auto
==============================================================================
--- 
sling/trunk/bundles/extensions/discovery/base/src/main/java/org/apache/sling/discovery/base/connectors/ping/wl/package-info.java
 (added)
+++ 
sling/trunk/bundles/extensions/discovery/base/src/main/java/org/apache/sling/discovery/base/connectors/ping/wl/package-info.java
 Tue Oct 20 14:12:31 2015
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+
+/**
+ * Provides whitelist-related classes for discovery
+ * implementors that choose to extend from discovery.base
+ *
+ * @version 1.0.0
+ */
+@Version("1.0.0")
+package org.apache.sling.discovery.base.connectors.ping.wl;
+
+import aQute.bnd.annotation.Version;
+

Propchange: 
sling/trunk/bundles/extensions/discovery/base/src/main/java/org/apache/sling/discovery/base/connectors/ping/wl/package-info.java
------------------------------------------------------------------------------
    svn:eol-style = native


Reply via email to