Merge remote-tracking branch 'origin/master' into bugfix/SLING-7359
diff --git a/src/main/java/org/apache/sling/distribution/agent/impl/SimpleDistributionAgent.java b/src/main/java/org/apache/sling/distribution/agent/impl/SimpleDistributionAgent.java
index 68b3ebd..1d97a89 100644
--- a/src/main/java/org/apache/sling/distribution/agent/impl/SimpleDistributionAgent.java
+++ b/src/main/java/org/apache/sling/distribution/agent/impl/SimpleDistributionAgent.java
@@ -297,7 +297,7 @@
         active = true;
 
         // register triggers if any
-        agentBasedRequestHandler = new TriggerAgentRequestHandler(this, agentAuthenticationInfo, log, active);
+        agentBasedRequestHandler = new TriggerAgentRequestHandler(this, name, agentAuthenticationInfo, log, active);
 
         if (!isPassive()) {
             try {
diff --git a/src/main/java/org/apache/sling/distribution/agent/impl/TriggerAgentRequestHandler.java b/src/main/java/org/apache/sling/distribution/agent/impl/TriggerAgentRequestHandler.java
index 34badbc..07851dc 100644
--- a/src/main/java/org/apache/sling/distribution/agent/impl/TriggerAgentRequestHandler.java
+++ b/src/main/java/org/apache/sling/distribution/agent/impl/TriggerAgentRequestHandler.java
@@ -24,6 +24,7 @@
 import org.apache.sling.api.resource.ResourceResolver;
 import org.apache.sling.distribution.DistributionRequest;
 import org.apache.sling.distribution.agent.DistributionAgent;
+import org.apache.sling.distribution.component.impl.DistributionComponentKind;
 import org.apache.sling.distribution.log.impl.DefaultDistributionLog;
 import org.apache.sling.distribution.trigger.DistributionRequestHandler;
 import org.apache.sling.distribution.util.impl.DistributionUtils;
@@ -36,8 +37,10 @@
     private final DefaultDistributionLog log;
     private final boolean active;
     private final DistributionAgent agent;
+    private final String agentName;
 
     public TriggerAgentRequestHandler(@Nonnull DistributionAgent agent,
+                                      @Nonnull String agentName,
                                       @Nonnull SimpleDistributionAgentAuthenticationInfo authenticationInfo,
                                       @Nonnull DefaultDistributionLog log,
                                       boolean active) {
@@ -45,6 +48,15 @@
         this.log = log;
         this.active = active;
         this.agent = agent;
+        this.agentName = agentName;
+    }
+
+    public String getName() {
+        return agentName;
+    }
+
+    public DistributionComponentKind getComponentKind() {
+        return DistributionComponentKind.AGENT;
     }
 
     public void handle(@Nullable ResourceResolver resourceResolver, @Nonnull DistributionRequest request) {
@@ -77,4 +89,11 @@
         }
 
     }
+
+    @Override
+    public String toString() {
+        return "TriggerAgentRequestHandler{" +
+                "agentName='" + agentName+ '\'' +
+                '}';
+    }
 }
diff --git a/src/main/java/org/apache/sling/distribution/servlet/DistributionTriggerServlet.java b/src/main/java/org/apache/sling/distribution/servlet/DistributionTriggerServlet.java
index 2fd9e37..fc3f771 100644
--- a/src/main/java/org/apache/sling/distribution/servlet/DistributionTriggerServlet.java
+++ b/src/main/java/org/apache/sling/distribution/servlet/DistributionTriggerServlet.java
@@ -32,6 +32,7 @@
 import org.apache.sling.api.servlets.SlingAllMethodsServlet;
 import org.apache.sling.distribution.DistributionRequest;
 import org.apache.sling.distribution.common.DistributionException;
+import org.apache.sling.distribution.component.impl.DistributionComponentKind;
 import org.apache.sling.distribution.resources.DistributionResourceTypes;
 import org.apache.sling.distribution.trigger.DistributionRequestHandler;
 import org.apache.sling.distribution.trigger.DistributionTrigger;
@@ -80,6 +81,14 @@
         final PrintWriter writer = response.getWriter();
 
         DistributionRequestHandler distributionRequestHandler = new DistributionRequestHandler() {
+            public String getName() {
+                return DistributionTriggerServlet.this.getClass().getSimpleName();
+            }
+
+            public DistributionComponentKind getComponentKind() {
+                return null; // unknown
+            }
+
             public void handle(@Nullable ResourceResolver resourceResolver, @Nonnull DistributionRequest request) {
                 writeEvent(writer, request);
             }
diff --git a/src/main/java/org/apache/sling/distribution/trigger/DistributionRequestHandler.java b/src/main/java/org/apache/sling/distribution/trigger/DistributionRequestHandler.java
index 93d7271..d690ff0 100644
--- a/src/main/java/org/apache/sling/distribution/trigger/DistributionRequestHandler.java
+++ b/src/main/java/org/apache/sling/distribution/trigger/DistributionRequestHandler.java
@@ -18,12 +18,14 @@
  */
 package org.apache.sling.distribution.trigger;
 
+import javax.annotation.CheckForNull;
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
 import aQute.bnd.annotation.ConsumerType;
 import org.apache.sling.api.resource.ResourceResolver;
 import org.apache.sling.distribution.DistributionRequest;
+import org.apache.sling.distribution.component.impl.DistributionComponentKind;
 
 /**
  * An handler for {@link org.apache.sling.distribution.DistributionRequest}s passed to a
@@ -33,6 +35,20 @@
 public interface DistributionRequestHandler {
 
     /**
+     * returns the name of the owner of this request handler.
+     * @return
+     */
+    @Nonnull
+    String getName();
+
+    /**
+     * returns the kind of component that owns this request handler. Might be null, for unknown kinds of components.
+     * @return
+     */
+    @CheckForNull
+    DistributionComponentKind getComponentKind();
+
+    /**
      * handle the request according to the trigger implementation.
      *
      * @param request a distribution request
diff --git a/src/main/java/org/apache/sling/distribution/trigger/impl/DistributionEventDistributeDistributionTrigger.java b/src/main/java/org/apache/sling/distribution/trigger/impl/DistributionEventDistributeDistributionTrigger.java
index b7ea954..2e037a0 100644
--- a/src/main/java/org/apache/sling/distribution/trigger/impl/DistributionEventDistributeDistributionTrigger.java
+++ b/src/main/java/org/apache/sling/distribution/trigger/impl/DistributionEventDistributeDistributionTrigger.java
@@ -26,6 +26,7 @@
 
 import org.apache.sling.distribution.DistributionRequestType;
 import org.apache.sling.distribution.SimpleDistributionRequest;
+import org.apache.sling.distribution.component.impl.DistributionComponentKind;
 import org.apache.sling.distribution.event.DistributionEventProperties;
 import org.apache.sling.distribution.event.DistributionEventTopics;
 import org.apache.sling.distribution.common.DistributionException;
@@ -105,6 +106,22 @@
         }
 
         public void handleEvent(Event event) {
+            String originKindName = String.valueOf(event.getProperty(DistributionEventProperties.DISTRIBUTION_COMPONENT_KIND));
+            String originName = String.valueOf(event.getProperty(DistributionEventProperties.DISTRIBUTION_COMPONENT_NAME));
+
+            DistributionComponentKind originKind;
+            try {
+                originKind = DistributionComponentKind.valueOf(originKindName);
+            } catch (IllegalArgumentException ex) {
+                log.debug("Unknown component kind {} of event {}.", originKindName, event);
+                originKind = null;
+            }
+
+            if (requestHandler.getName().equals(originName) && requestHandler.getComponentKind() == originKind) {
+                log.info("skip chain distribution from event {} to {}", event, requestHandler);
+                return;
+            }
+
             Object actionProperty = event.getProperty(DistributionEventProperties.DISTRIBUTION_TYPE);
             Object pathProperty = event.getProperty(DistributionEventProperties.DISTRIBUTION_PATHS);
             if (actionProperty != null && pathProperty != null) {
diff --git a/src/test/java/org/apache/sling/distribution/agent/impl/TriggerAgentRequestHandlerTest.java b/src/test/java/org/apache/sling/distribution/agent/impl/TriggerAgentRequestHandlerTest.java
index 46d6453..9f949e4 100644
--- a/src/test/java/org/apache/sling/distribution/agent/impl/TriggerAgentRequestHandlerTest.java
+++ b/src/test/java/org/apache/sling/distribution/agent/impl/TriggerAgentRequestHandlerTest.java
@@ -36,7 +36,7 @@
         DistributionAgent agent = mock(DistributionAgent.class);
         SimpleDistributionAgentAuthenticationInfo authenticationInfo = mock(SimpleDistributionAgentAuthenticationInfo.class);
         DefaultDistributionLog log = mock(DefaultDistributionLog.class);
-        TriggerAgentRequestHandler triggerAgentRequestHandler = new TriggerAgentRequestHandler(agent,
+        TriggerAgentRequestHandler triggerAgentRequestHandler = new TriggerAgentRequestHandler(agent, "agent",
                 authenticationInfo, log, true);
         ResourceResolver resourceResolver = mock(ResourceResolver.class);
         DistributionRequest request = mock(DistributionRequest.class);
@@ -48,7 +48,7 @@
         DistributionAgent agent = mock(DistributionAgent.class);
         SimpleDistributionAgentAuthenticationInfo authenticationInfo = mock(SimpleDistributionAgentAuthenticationInfo.class);
         DefaultDistributionLog log = mock(DefaultDistributionLog.class);
-        TriggerAgentRequestHandler triggerAgentRequestHandler = new TriggerAgentRequestHandler(agent,
+        TriggerAgentRequestHandler triggerAgentRequestHandler = new TriggerAgentRequestHandler(agent, "name",
                 authenticationInfo, log, false);
         ResourceResolver resourceResolver = mock(ResourceResolver.class);
         DistributionRequest request = mock(DistributionRequest.class);
diff --git a/src/test/java/org/apache/sling/distribution/trigger/impl/ChainDistributionTriggerTest.java b/src/test/java/org/apache/sling/distribution/trigger/impl/ChainDistributionTriggerTest.java
deleted file mode 100644
index 58bf444..0000000
--- a/src/test/java/org/apache/sling/distribution/trigger/impl/ChainDistributionTriggerTest.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * 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.distribution.trigger.impl;
-
-import org.apache.sling.distribution.trigger.DistributionRequestHandler;
-import org.junit.Test;
-import org.osgi.framework.BundleContext;
-
-import static org.mockito.Mockito.mock;
-
-/**
- * Testcase for {@link DistributionEventDistributeDistributionTrigger}
- */
-public class ChainDistributionTriggerTest {
-
-    @Test
-    public void testRegister() throws Exception {
-        String pathPrefix = "/prefix";
-        BundleContext bundleContext = mock(BundleContext.class);
-        DistributionEventDistributeDistributionTrigger chainDistributeDistributionTrigger = new DistributionEventDistributeDistributionTrigger(pathPrefix, bundleContext);
-        DistributionRequestHandler handler = mock(DistributionRequestHandler.class);
-        chainDistributeDistributionTrigger.register(handler);
-    }
-
-    @Test
-    public void testUnregister() throws Exception {
-        String pathPrefix = "/prefix";
-        BundleContext bundleContext = mock(BundleContext.class);
-        DistributionEventDistributeDistributionTrigger chainDistributeDistributionTrigger = new DistributionEventDistributeDistributionTrigger(pathPrefix, bundleContext);
-        DistributionRequestHandler handler = mock(DistributionRequestHandler.class);
-        chainDistributeDistributionTrigger.unregister(handler);
-    }
-
-
-    @Test
-    public void testDisable() throws Exception {
-        String pathPrefix = "/prefix";
-        BundleContext bundleContext = mock(BundleContext.class);
-        DistributionEventDistributeDistributionTrigger chainDistributeDistributionTrigger = new DistributionEventDistributeDistributionTrigger(pathPrefix, bundleContext);
-        chainDistributeDistributionTrigger.disable();
-    }
-}
\ No newline at end of file
diff --git a/src/test/java/org/apache/sling/distribution/trigger/impl/DistributionEventDistributeDistributionTriggerTest.java b/src/test/java/org/apache/sling/distribution/trigger/impl/DistributionEventDistributeDistributionTriggerTest.java
new file mode 100644
index 0000000..1b2544e
--- /dev/null
+++ b/src/test/java/org/apache/sling/distribution/trigger/impl/DistributionEventDistributeDistributionTriggerTest.java
@@ -0,0 +1,131 @@
+/*
+ * 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.distribution.trigger.impl;
+
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.distribution.DistributionRequest;
+import org.apache.sling.distribution.DistributionRequestType;
+import org.apache.sling.distribution.common.DistributionException;
+import org.apache.sling.distribution.component.impl.DistributionComponentKind;
+import org.apache.sling.distribution.event.DistributionEventTopics;
+import org.apache.sling.distribution.event.impl.DefaultDistributionEventFactory;
+import org.apache.sling.distribution.event.impl.DistributionEventFactory;
+import org.apache.sling.distribution.packaging.DistributionPackageInfo;
+import org.apache.sling.distribution.trigger.DistributionRequestHandler;
+import org.apache.sling.testing.mock.osgi.junit.OsgiContext;
+import org.junit.Rule;
+import org.junit.Test;
+import org.osgi.framework.BundleContext;
+
+import static org.mockito.Mockito.mock;
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.spy;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Testcase for {@link DistributionEventDistributeDistributionTrigger}
+ */
+public class DistributionEventDistributeDistributionTriggerTest {
+
+    @Rule
+    public final OsgiContext osgiContext = new OsgiContext();
+
+    @Test
+    public void testRegister() throws Exception {
+        String pathPrefix = "/prefix";
+        BundleContext bundleContext = mock(BundleContext.class);
+        DistributionEventDistributeDistributionTrigger chainDistributeDistributionTrigger = new DistributionEventDistributeDistributionTrigger(pathPrefix, bundleContext);
+        DistributionRequestHandler handler = mock(DistributionRequestHandler.class);
+        chainDistributeDistributionTrigger.register(handler);
+    }
+
+    @Test
+    public void testUnregister() throws Exception {
+        String pathPrefix = "/prefix";
+        BundleContext bundleContext = mock(BundleContext.class);
+        DistributionEventDistributeDistributionTrigger chainDistributeDistributionTrigger = new DistributionEventDistributeDistributionTrigger(pathPrefix, bundleContext);
+        DistributionRequestHandler handler = mock(DistributionRequestHandler.class);
+        chainDistributeDistributionTrigger.unregister(handler);
+    }
+
+
+    @Test
+    public void testDisable() throws Exception {
+        String pathPrefix = "/prefix";
+        BundleContext bundleContext = mock(BundleContext.class);
+        DistributionEventDistributeDistributionTrigger chainDistributeDistributionTrigger = new DistributionEventDistributeDistributionTrigger(pathPrefix, bundleContext);
+        chainDistributeDistributionTrigger.disable();
+    }
+
+    @Test
+    public void testDistributionLoop() throws Exception {
+        final AtomicInteger handled = new AtomicInteger(0);
+        final Map<String, Object> infoData = new HashMap<String, Object>();
+        infoData.put(DistributionPackageInfo.PROPERTY_REQUEST_PATHS, new String[] { "/foo/bar" });
+        infoData.put(DistributionPackageInfo.PROPERTY_REQUEST_TYPE, DistributionRequestType.ADD);
+        final DistributionPackageInfo info = new DistributionPackageInfo("any", infoData);
+
+        Thread testExecution = new Thread() {
+            @Override public void run() {
+                try {
+                    final DistributionEventFactory eventFactory = new DefaultDistributionEventFactory();
+                    osgiContext.registerInjectActivateService(eventFactory);
+
+                    DistributionEventDistributeDistributionTrigger trigger = new DistributionEventDistributeDistributionTrigger("/foo",
+                            osgiContext.bundleContext());
+                    DistributionRequestHandler testHandler = new DistributionRequestHandler() {
+                        public String getName() {
+                            return "test";
+                        }
+
+                        public DistributionComponentKind getComponentKind() {
+                            return DistributionComponentKind.AGENT;
+                        }
+
+                        public void handle(ResourceResolver resourceResolver, DistributionRequest request) {
+                            // we simple fire an event, to cause the loop
+                            eventFactory.generatePackageEvent(DistributionEventTopics.AGENT_PACKAGE_DISTRIBUTED,
+                                    DistributionComponentKind.AGENT, "test", info);
+                            handled.addAndGet(1);
+                        }
+                    };
+
+                    trigger.register(testHandler);
+                    eventFactory.generatePackageEvent(DistributionEventTopics.AGENT_PACKAGE_DISTRIBUTED, DistributionComponentKind.AGENT,
+                            "origin", info);
+                    trigger.unregister(testHandler);
+                } catch (DistributionException ex) {
+                    throw new AssertionError(ex);
+                }
+            }
+        };
+
+        testExecution.setDaemon(true);
+        testExecution.run();
+
+        Thread.sleep(200);
+
+        testExecution.interrupt();
+        assertEquals(1, handled.get());
+    }
+}
\ No newline at end of file