brooklyn-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From henev...@apache.org
Subject [04/10] brooklyn-server git commit: initial work to support HttpEntity
Date Wed, 15 Feb 2017 18:57:27 GMT
initial work to support HttpEntity

- add HttpCommnadEffector
- add CompositeEffector
- add EntityInitializers util class to resolve DSL injected as params
  into the HttpCommandEffector


Project: http://git-wip-us.apache.org/repos/asf/brooklyn-server/repo
Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-server/commit/eadec9ac
Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-server/tree/eadec9ac
Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-server/diff/eadec9ac

Branch: refs/heads/master
Commit: eadec9ac9ca82ab1b5baea8c69b6ab35b55c452b
Parents: 0f649fe
Author: Andrea Turli <andrea.turli@gmail.com>
Authored: Wed Dec 21 12:03:27 2016 +0100
Committer: Andrea Turli <andrea.turli@gmail.com>
Committed: Mon Feb 13 15:09:31 2017 +0100

----------------------------------------------------------------------
 .../brooklyn/core/effector/AddSensor.java       |   2 +
 .../core/effector/CompositeEffector.java        | 130 +++++++++++++++++
 .../brooklyn/core/effector/Effectors.java       |   4 +
 .../core/effector/http/HttpCommandEffector.java | 145 +++++++++++++++++++
 .../core/entity/EntityInitializers.java         |  32 +++-
 .../core/sensor/http/HttpRequestSensor.java     |  47 +++---
 .../CompositeEffectorIntegrationTest.java       |  78 ++++++++++
 .../HttpCommandEffectorIntegrationTest.java     | 125 ++++++++++++++++
 .../apache/brooklyn/rest/api/EffectorApi.java   |  28 ++--
 9 files changed, 559 insertions(+), 32 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/eadec9ac/core/src/main/java/org/apache/brooklyn/core/effector/AddSensor.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/AddSensor.java b/core/src/main/java/org/apache/brooklyn/core/effector/AddSensor.java
index ba8d679..92cc4ec 100644
--- a/core/src/main/java/org/apache/brooklyn/core/effector/AddSensor.java
+++ b/core/src/main/java/org/apache/brooklyn/core/effector/AddSensor.java
@@ -57,6 +57,7 @@ public class AddSensor<T> implements EntityInitializer {
     protected final Duration period;
     protected final String type;
     protected AttributeSensor<T> sensor;
+    protected final ConfigBag params;
 
     public AddSensor(Map<String, String> params) {
         this(ConfigBag.newInstance(params));
@@ -66,6 +67,7 @@ public class AddSensor<T> implements EntityInitializer {
         this.name = Preconditions.checkNotNull(params.get(SENSOR_NAME), "Name must be supplied
when defining a sensor");
         this.period = params.get(SENSOR_PERIOD);
         this.type = params.get(SENSOR_TYPE);
+        this.params = params;
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/eadec9ac/core/src/main/java/org/apache/brooklyn/core/effector/CompositeEffector.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/CompositeEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/CompositeEffector.java
new file mode 100644
index 0000000..88cd92c
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/effector/CompositeEffector.java
@@ -0,0 +1,130 @@
+/*
+ * 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.brooklyn.core.effector;
+
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.Nullable;
+
+import org.apache.brooklyn.api.effector.Effector;
+import org.apache.brooklyn.api.entity.EntityLocal;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.core.effector.Effectors.EffectorBuilder;
+import org.apache.brooklyn.core.entity.EntityInitializers;
+import org.apache.brooklyn.core.entity.EntityInternal;
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.apache.brooklyn.util.guava.Maybe;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicate;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.reflect.TypeToken;
+
+@Beta
+public class CompositeEffector extends AddEffector {
+
+    private static final Logger LOG = LoggerFactory.getLogger(CompositeEffector.class);
+
+    public static final ConfigKey<List<String>> EFFECTORS = ConfigKeys.newConfigKey(new
TypeToken<List<String>>() {}, "effectors",
+            "Effector names to be chained together in the composite effector", ImmutableList.<String>of());
+    public static final ConfigKey<Boolean> OVERRIDE = ConfigKeys.newBooleanConfigKey("override",
+            "Wheter additional defined effectors should override pre-existing effector with
same name or not (default: false)", Boolean.FALSE);
+    public CompositeEffector(ConfigBag params) {
+        super(newEffectorBuilder(params).build());
+    }
+
+    public CompositeEffector(Map<?, ?> params) {
+        this(ConfigBag.newInstance(params));
+    }
+
+    public static EffectorBuilder<String> newEffectorBuilder(ConfigBag params) {
+        EffectorBuilder<String> eff = AddEffector.newEffectorBuilder(String.class,
params);
+        eff.impl(new Body(eff.buildAbstract(), params));
+        return eff;
+    }
+
+    @Override
+    public void apply(EntityLocal entity) {
+        Maybe<Effector<?>> effectorMaybe = entity.getEntityType().getEffectorByName(effector.getName());
+        if (!effectorMaybe.isAbsentOrNull()) {
+            Effector<?> original = Effectors.effector(effectorMaybe.get()).name("original-"
+ effector.getName()).build();
+            ((EntityInternal)entity).getMutableEntityType().addEffector(original);
+        }
+        super.apply(entity);
+    }
+
+    protected static class Body extends EffectorBody<String> {
+        private final Effector<?> effector;
+        private final ConfigBag params;
+
+        public Body(Effector<?> eff, ConfigBag params) {
+            this.effector = eff;
+            Preconditions.checkNotNull(params.getAllConfigRaw().get(EFFECTORS.getName()),
"Effector names must be supplied when defining this effector");
+            this.params = params;
+        }
+
+        @Override
+        public String call(final ConfigBag params) {
+            ConfigBag allConfig = ConfigBag.newInstanceCopying(this.params).putAll(params);
+            final List<String> effectorNames = EntityInitializers.resolve(allConfig,
EFFECTORS);
+            final Boolean override = allConfig.get(OVERRIDE);
+
+            List <Object> results = Lists.newArrayList();
+            if (!override) {
+                List<Effector<?>> originalEffectors = FluentIterable.from(entity().getEntityType().getEffectors())
+                        .filter(new Predicate<Effector<?>>() {
+                            @Override
+                            public boolean apply(@Nullable Effector<?> input) {
+                                return input.getName().equals("original-" + effector.getName());
+                            }
+                        })
+                        .toList();
+
+                for (Effector<?> originalEffector : originalEffectors) {
+                    results.add(invokeEffectorNamed(originalEffector.getName(), params));
+                }
+            }
+            for (String eff : effectorNames) {
+                results.add(invokeEffectorNamed(eff, params));
+            }
+            return Iterables.toString(results);
+        }
+
+        private Object invokeEffectorNamed(String effectorName, ConfigBag params) {
+            LOG.debug("{} invoking effector on {}, effector={}, parameters={}",
+                    new Object[]{this, entity(), effectorName, params});
+            Maybe<Effector<?>> effector = entity().getEntityType().getEffectorByName(effectorName);
+            if (effector.isAbsent()) {
+                // TODO
+            }
+            return entity().invoke(effector.get(), params.getAllConfig()).getUnchecked();
+            
+        }
+    }
+
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/eadec9ac/core/src/main/java/org/apache/brooklyn/core/effector/Effectors.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/Effectors.java b/core/src/main/java/org/apache/brooklyn/core/effector/Effectors.java
index c644001..53db25a 100644
--- a/core/src/main/java/org/apache/brooklyn/core/effector/Effectors.java
+++ b/core/src/main/java/org/apache/brooklyn/core/effector/Effectors.java
@@ -64,6 +64,10 @@ public class Effectors {
             this.returnType = returnType;
             this.effectorName = effectorName;
         }
+        public EffectorBuilder<T> name(String name) {
+            this.effectorName = name;
+            return this;
+        }
         public EffectorBuilder<T> description(String description) {
             this.description = description;
             return this;                

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/eadec9ac/core/src/main/java/org/apache/brooklyn/core/effector/http/HttpCommandEffector.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/http/HttpCommandEffector.java
b/core/src/main/java/org/apache/brooklyn/core/effector/http/HttpCommandEffector.java
new file mode 100644
index 0000000..7ef2126
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/effector/http/HttpCommandEffector.java
@@ -0,0 +1,145 @@
+/*
+ * 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.brooklyn.core.effector.http;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.net.URI;
+import java.util.Map;
+import java.util.concurrent.Callable;
+
+import org.apache.brooklyn.api.effector.Effector;
+import org.apache.brooklyn.api.mgmt.Task;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.core.config.MapConfigKey;
+import org.apache.brooklyn.core.effector.AddEffector;
+import org.apache.brooklyn.core.effector.EffectorBody;
+import org.apache.brooklyn.core.effector.Effectors.EffectorBuilder;
+import org.apache.brooklyn.core.entity.EntityInitializers;
+import org.apache.brooklyn.core.sensor.Sensors;
+import org.apache.brooklyn.util.collections.Jsonya;
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.apache.brooklyn.util.core.task.Tasks;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.http.executor.HttpConfig;
+import org.apache.brooklyn.util.http.executor.HttpExecutor;
+import org.apache.brooklyn.util.http.executor.HttpRequest;
+import org.apache.brooklyn.util.http.executor.HttpResponse;
+import org.apache.brooklyn.util.http.executor.UsernamePassword;
+import org.apache.brooklyn.util.http.executor.apacheclient.HttpExecutorImpl;
+
+import com.google.common.base.Preconditions;
+import com.google.common.io.ByteStreams;
+import com.jayway.jsonpath.JsonPath;
+
+public final class HttpCommandEffector extends AddEffector {
+
+    public static final ConfigKey<String> EFFECTOR_URI = ConfigKeys.newStringConfigKey("uri");
+    public static final ConfigKey<String> EFFECTOR_HTTP_VERB = ConfigKeys.newStringConfigKey("httpVerb");
+    public static final ConfigKey<String> EFFECTOR_HTTP_USERNAME = ConfigKeys.newStringConfigKey("httpUsername");
+    public static final ConfigKey<String> EFFECTOR_HTTP_PASSWORD = ConfigKeys.newStringConfigKey("httpPassword");
+    public static final ConfigKey<Map<String, String>> EFFECTOR_HTTP_HEADERS
= new MapConfigKey(String.class, "headers");
+    public static final ConfigKey<Map<String, Object>> EFFECTOR_HTTP_PAYLOAD
= new MapConfigKey(String.class, "httpPayload");
+    public static final ConfigKey<String> JSON_PATH = ConfigKeys.newStringConfigKey("jsonPath",
"JSON path to select in HTTP response; default $", "$");
+    public static final ConfigKey<String> PUBLISH_SENSOR = ConfigKeys.newStringConfigKey("publishSensor",
"Sensor name where to store json path extracted value");
+
+    public HttpCommandEffector(ConfigBag params) {
+        super(newEffectorBuilder(params).build());
+    }
+
+    public static EffectorBuilder<String> newEffectorBuilder(ConfigBag params) {
+        EffectorBuilder<String> eff = AddEffector.newEffectorBuilder(String.class,
params);
+        eff.impl(new Body(eff.buildAbstract(), params));
+        return eff;
+    }
+    
+    protected static class Body extends EffectorBody<String> {
+        private final Effector<?> effector;
+        private final ConfigBag params;
+
+        public Body(Effector<?> eff, final ConfigBag params) {
+            this.effector = eff;
+            Preconditions.checkNotNull(params.getAllConfigRaw().get(EFFECTOR_URI.getName()),
"uri must be supplied when defining this effector");
+            Preconditions.checkNotNull(params.getAllConfigRaw().get(EFFECTOR_HTTP_VERB.getName()),
"HTTP verb must be supplied when defining this effector");
+            this.params = params;
+        }
+
+        @Override
+        public String call(final ConfigBag params) {
+            ConfigBag allConfig = ConfigBag.newInstanceCopying(this.params).putAll(params);
+            final String uri = EntityInitializers.resolve(allConfig, EFFECTOR_URI);
+            final String httpVerb = EntityInitializers.resolve(allConfig, EFFECTOR_HTTP_VERB);
+            final String httpUsername = EntityInitializers.resolve(allConfig, EFFECTOR_HTTP_USERNAME);
+            final String httpPassword = EntityInitializers.resolve(allConfig, EFFECTOR_HTTP_PASSWORD);
+            final Map<String, String> headers = EntityInitializers.resolve(allConfig,
EFFECTOR_HTTP_HEADERS);
+            final Map<String, Object> payload = EntityInitializers.resolve(allConfig,
EFFECTOR_HTTP_PAYLOAD);
+            final String jsonPath = EntityInitializers.resolve(allConfig, JSON_PATH);
+            final String publishSensor = EntityInitializers.resolve(allConfig, PUBLISH_SENSOR);
+            Task t = Tasks.builder().displayName(effector.getName()).body(new Callable<Object>()
{
+                @Override
+                public Object call() throws Exception {
+                    HttpExecutor httpExecutor = HttpExecutorImpl.newInstance();
+
+                    String body = "";
+                    if (payload != null && !payload.isEmpty() && headers.containsKey("Content-Type"))
{
+                        body = Jsonya.newInstance().put(payload).toString();
+                    }
+                    HttpRequest.Builder httpRequestBuilder = new HttpRequest.Builder()
+                            .body(body.getBytes())
+                            .uri(URI.create(uri))
+                            .method(httpVerb)
+                            .config(HttpConfig.builder()
+                                    .trustSelfSigned(true)
+                                    .trustAll(true)
+                                    .laxRedirect(true)
+                                    .build());
+
+                    if (headers != null) {
+                        httpRequestBuilder.headers(headers);
+                    }
+
+                    if (httpUsername != null && httpPassword != null) {
+                        httpRequestBuilder.credentials(new UsernamePassword(httpUsername,
httpPassword));
+                    }
+                    
+                    HttpRequest httpRequest = httpRequestBuilder.build();
+                    ByteArrayOutputStream out = new ByteArrayOutputStream();
+                    try {
+                        HttpResponse response = httpExecutor.execute(httpRequest);
+                        ByteStreams.copy(response.getContent(), out);
+                        return new String(out.toByteArray());
+                    } catch (IOException e) {
+                        throw Exceptions.propagate(e);
+                    }
+                }
+            }).build();
+
+            String val = (String) queue(t).getUnchecked();
+            if (jsonPath != null) {
+                String extractedValue = JsonPath.parse(val).read(jsonPath, String.class);
+                entity().sensors().set(Sensors.newStringSensor(publishSensor), extractedValue);
+                return extractedValue;
+            } else {
+                return val;
+            }
+        }
+
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/eadec9ac/core/src/main/java/org/apache/brooklyn/core/entity/EntityInitializers.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/entity/EntityInitializers.java b/core/src/main/java/org/apache/brooklyn/core/entity/EntityInitializers.java
index a258007..cffcced7 100644
--- a/core/src/main/java/org/apache/brooklyn/core/entity/EntityInitializers.java
+++ b/core/src/main/java/org/apache/brooklyn/core/entity/EntityInitializers.java
@@ -22,6 +22,11 @@ import java.util.List;
 
 import org.apache.brooklyn.api.entity.EntityInitializer;
 import org.apache.brooklyn.api.entity.EntityLocal;
+import org.apache.brooklyn.api.mgmt.ExecutionContext;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.apache.brooklyn.util.core.internal.ConfigKeySelfExtracting;
+import org.apache.brooklyn.util.core.task.BasicExecutionContext;
 
 import com.google.common.collect.ImmutableList;
 
@@ -41,9 +46,32 @@ public class EntityInitializers {
         }
     }
 
-    
     public static EntityInitializer addingTags(Object... tags) {
         return new AddTags(tags);
     }
-    
+
+    /**
+     * Resolves key in the
+     * {@link BasicExecutionContext#getCurrentExecutionContext current execution context}.
+     * @see #resolve(ConfigBag, ConfigKey, ExecutionContext)
+     */
+    public static <T> T resolve(ConfigBag configBag, ConfigKey<T> key) {
+        return resolve(configBag, key, BasicExecutionContext.getCurrentExecutionContext());
+    }
+
+    /**
+     * Gets the value for key from configBag.
+     * <p>
+     * If key is an instance of {@link ConfigKeySelfExtracting} and executionContext is
+     * not null then its value will be retrieved per the key's implementation of
+     * {@link ConfigKeySelfExtracting#extractValue extractValue}. Otherwise, the value
+     * will be retrieved from configBag directly.
+     */
+    public static <T> T resolve(ConfigBag configBag, ConfigKey<T> key, ExecutionContext
executionContext) {
+        if (key instanceof ConfigKeySelfExtracting && executionContext != null) {
+            ConfigKeySelfExtracting<T> ckse = ((ConfigKeySelfExtracting<T>) key);
+            return ckse.extractValue(configBag.getAllConfigAsConfigKeyMap(), executionContext);
+        }
+        return configBag.get(key);
+    }
 }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/eadec9ac/core/src/main/java/org/apache/brooklyn/core/sensor/http/HttpRequestSensor.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/sensor/http/HttpRequestSensor.java
b/core/src/main/java/org/apache/brooklyn/core/sensor/http/HttpRequestSensor.java
index dea44d3..966a88c 100644
--- a/core/src/main/java/org/apache/brooklyn/core/sensor/http/HttpRequestSensor.java
+++ b/core/src/main/java/org/apache/brooklyn/core/sensor/http/HttpRequestSensor.java
@@ -19,13 +19,14 @@
 package org.apache.brooklyn.core.sensor.http;
 
 import java.net.URI;
-
-import net.minidev.json.JSONObject;
+import java.util.Map;
 
 import org.apache.brooklyn.api.entity.EntityLocal;
 import org.apache.brooklyn.config.ConfigKey;
 import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.core.config.MapConfigKey;
 import org.apache.brooklyn.core.effector.AddSensor;
+import org.apache.brooklyn.core.entity.EntityInitializers;
 import org.apache.brooklyn.core.sensor.ssh.SshCommandSensor;
 import org.apache.brooklyn.feed.http.HttpFeed;
 import org.apache.brooklyn.feed.http.HttpPollConfig;
@@ -38,6 +39,8 @@ import com.google.common.annotations.Beta;
 import com.google.common.base.Functions;
 import com.google.common.base.Supplier;
 
+import net.minidev.json.JSONObject;
+
 /**
  * Configurable {@link org.apache.brooklyn.api.entity.EntityInitializer} which adds an HTTP
sensor feed to retrieve the
  * {@link JSONObject} from a JSON response in order to populate the sensor with the data
at the {@code jsonPath}.
@@ -53,24 +56,10 @@ public final class HttpRequestSensor<T> extends AddSensor<T>
{
     public static final ConfigKey<String> JSON_PATH = ConfigKeys.newStringConfigKey("jsonPath",
"JSON path to select in HTTP response; default $", "$");
     public static final ConfigKey<String> USERNAME = ConfigKeys.newStringConfigKey("username",
"Username for HTTP request, if required");
     public static final ConfigKey<String> PASSWORD = ConfigKeys.newStringConfigKey("password",
"Password for HTTP request, if required");
-
-    protected final Supplier<URI> uri;
-    protected final String jsonPath;
-    protected final String username;
-    protected final String password;
+    public static final ConfigKey<Map<String, String>> HEADERS = new MapConfigKey(String.class,
"headers");
 
     public HttpRequestSensor(final ConfigBag params) {
         super(params);
-
-        uri = new Supplier<URI>() {
-            @Override
-            public URI get() {
-                return URI.create(params.get(SENSOR_URI));
-            }
-        };
-        jsonPath = params.get(JSON_PATH);
-        username = params.get(USERNAME);
-        password = params.get(PASSWORD);
     }
 
     @Override
@@ -81,18 +70,36 @@ public final class HttpRequestSensor<T> extends AddSensor<T>
{
             LOG.debug("Adding HTTP JSON sensor {} to {}", name, entity);
         }
 
+        final ConfigBag allConfig = ConfigBag.newInstanceCopying(this.params).putAll(params);
+        final Supplier<URI> uri = new Supplier<URI>() {
+            @Override
+            public URI get() {
+                return URI.create(EntityInitializers.resolve(allConfig, SENSOR_URI));
+            }
+        };
+        final String jsonPath = EntityInitializers.resolve(allConfig, JSON_PATH);
+        final String username = EntityInitializers.resolve(allConfig, USERNAME);
+        final String password = EntityInitializers.resolve(allConfig, PASSWORD);
+        final Map<String, String> headers = EntityInitializers.resolve(allConfig, HEADERS);
+
+        
         HttpPollConfig<T> pollConfig = new HttpPollConfig<T>(sensor)
                 .checkSuccess(HttpValueFunctions.responseCodeEquals(200))
                 .onFailureOrException(Functions.constant((T) null))
                 .onSuccess(HttpValueFunctions.<T>jsonContentsFromPath(jsonPath))
                 .period(period);
 
-        HttpFeed feed = HttpFeed.builder().entity(entity)
+        HttpFeed.Builder httpRequestBuilder = HttpFeed.builder().entity(entity)
                 .baseUri(uri)
                 .credentialsIfNotNull(username, password)
-                .poll(pollConfig)
-                .build();
+                .poll(pollConfig);
 
+        if (headers != null) {
+            httpRequestBuilder.headers(headers);
+        }
+        
+        HttpFeed feed = httpRequestBuilder.build();
         entity.addFeed(feed);
     }
+
 }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/eadec9ac/core/src/test/java/org/apache/brooklyn/core/effector/CompositeEffectorIntegrationTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/core/effector/CompositeEffectorIntegrationTest.java
b/core/src/test/java/org/apache/brooklyn/core/effector/CompositeEffectorIntegrationTest.java
new file mode 100644
index 0000000..e8cc30e
--- /dev/null
+++ b/core/src/test/java/org/apache/brooklyn/core/effector/CompositeEffectorIntegrationTest.java
@@ -0,0 +1,78 @@
+/*
+ * 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.brooklyn.core.effector;
+
+import org.apache.brooklyn.api.effector.Effector;
+import org.apache.brooklyn.api.entity.EntityLocal;
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.core.effector.http.HttpCommandEffector;
+import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.test.entity.TestApplication;
+import org.apache.brooklyn.core.test.entity.TestEntity;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+
+public class CompositeEffectorIntegrationTest {
+
+    final static Effector<String> EFFECTOR_START = Effectors.effector(String.class,
"start").buildAbstract();
+
+    private TestApplication app;
+    private EntityLocal entity;
+    
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() throws Exception {
+        app = TestApplication.Factory.newManagedInstanceForTests();
+        entity = app.createAndManageChild(EntitySpec.create(TestEntity.class).location(TestApplication.LOCALHOST_MACHINE_SPEC));
+        app.start(ImmutableList.<Location>of());
+    }
+
+    @AfterMethod(alwaysRun=true)
+    public void tearDown() throws Exception {
+        if (app != null) Entities.destroyAll(app.getManagementContext());
+    }
+
+    @Test(groups="Integration")
+    public void testCompositeEffector() throws Exception {
+        new HttpCommandEffector(ConfigBag.newInstance()
+                .configure(HttpCommandEffector.EFFECTOR_NAME, "eff1")
+                .configure(HttpCommandEffector.EFFECTOR_URI, "https://api.github.com/users/apache")
+                .configure(HttpCommandEffector.EFFECTOR_HTTP_VERB, "GET"))
+                .apply(entity);
+        new HttpCommandEffector(ConfigBag.newInstance()
+                .configure(HttpCommandEffector.EFFECTOR_NAME, "eff2")
+                .configure(HttpCommandEffector.EFFECTOR_URI, "https://api.github.com/users/brooklyncentral")
+                .configure(HttpCommandEffector.EFFECTOR_HTTP_VERB, "GET"))
+                .apply(entity);
+        new CompositeEffector(ConfigBag.newInstance()
+                .configure(CompositeEffector.EFFECTOR_NAME, "start")
+                .configure(CompositeEffector.EFFECTORS, ImmutableList.of("eff1", "eff2")))
+                .apply(entity);
+
+        String val = entity.invoke(EFFECTOR_START, MutableMap.<String,String>of()).get();
+        // TODO
+        System.out.println(val);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/eadec9ac/core/src/test/java/org/apache/brooklyn/core/effector/http/HttpCommandEffectorIntegrationTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/core/effector/http/HttpCommandEffectorIntegrationTest.java
b/core/src/test/java/org/apache/brooklyn/core/effector/http/HttpCommandEffectorIntegrationTest.java
new file mode 100644
index 0000000..080e08a
--- /dev/null
+++ b/core/src/test/java/org/apache/brooklyn/core/effector/http/HttpCommandEffectorIntegrationTest.java
@@ -0,0 +1,125 @@
+/*
+ * 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.brooklyn.core.effector.http;
+
+import org.apache.brooklyn.api.effector.Effector;
+import org.apache.brooklyn.api.entity.EntityLocal;
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.core.effector.Effectors;
+import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.sensor.Sensors;
+import org.apache.brooklyn.core.test.entity.TestApplication;
+import org.apache.brooklyn.core.test.entity.TestEntity;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.testng.Assert;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.jayway.jsonpath.JsonPath;
+
+public class HttpCommandEffectorIntegrationTest {
+
+    final static Effector<String> EFFECTOR_GITHUB_APACHE_ACCOUNT = Effectors.effector(String.class,
"GithubApacheAccount").buildAbstract();
+
+    private TestApplication app;
+    private EntityLocal entity;
+    
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() throws Exception {
+        app = TestApplication.Factory.newManagedInstanceForTests();
+        entity = app.createAndManageChild(EntitySpec.create(TestEntity.class).location(TestApplication.LOCALHOST_MACHINE_SPEC));
+        app.start(ImmutableList.<Location>of());
+    }
+
+    @AfterMethod(alwaysRun=true)
+    public void tearDown() throws Exception {
+        if (app != null) Entities.destroyAll(app.getManagementContext());
+    }
+
+    @Test(groups="Integration")
+    public void testHttpEffector() throws Exception {
+        new HttpCommandEffector(ConfigBag.newInstance()
+                .configure(HttpCommandEffector.EFFECTOR_NAME, "GithubApacheAccount")
+                .configure(HttpCommandEffector.EFFECTOR_URI, "https://api.github.com/users/apache")
+                .configure(HttpCommandEffector.EFFECTOR_HTTP_VERB, "GET")
+        ).apply(entity);
+
+        String val = entity.invoke(EFFECTOR_GITHUB_APACHE_ACCOUNT, MutableMap.<String,String>of()).get();
+        Assert.assertEquals(JsonPath.parse(val).read("$.login", String.class), "apache");
+    }
+
+    @Test(groups="Integration")
+    public void testHttpEffectorWithPayload() throws Exception {
+        new HttpCommandEffector(ConfigBag.newInstance()
+                .configure(HttpCommandEffector.EFFECTOR_NAME, "CreateGist")
+                .configure(HttpCommandEffector.EFFECTOR_URI, "https://api.github.com/gists")
+                .configure(HttpCommandEffector.EFFECTOR_HTTP_VERB, "POST")
+                .configure(HttpCommandEffector.EFFECTOR_HTTP_PAYLOAD, ImmutableMap.<String,
Object>of(
+                        "description", "Created via API", 
+                        "public", "false",
+                        "files", ImmutableMap.of("demo.txt", ImmutableMap.of("content","Demo"))))
+                .configure(HttpCommandEffector.EFFECTOR_HTTP_HEADERS, ImmutableMap.of("Content-Type",
"application/json"))
+                .configure(HttpCommandEffector.JSON_PATH, "$.url")
+                .configure(HttpCommandEffector.PUBLISH_SENSOR, "result")
+        ).apply(entity);
+
+        String url = entity.invoke(Effectors.effector(String.class, "CreateGist").buildAbstract(),
MutableMap.<String,String>of()).get();
+        Assert.assertNotNull(url, "url");
+    }
+
+    @Test(groups="Integration")
+    public void testHttpEffectorWithJsonPath() throws Exception {
+        new HttpCommandEffector(ConfigBag.newInstance()
+                .configure(HttpCommandEffector.EFFECTOR_NAME, "GithubApacheAccount")
+                .configure(HttpCommandEffector.EFFECTOR_URI, "https://api.github.com/users/apache")
+                .configure(HttpCommandEffector.EFFECTOR_HTTP_VERB, "GET")
+                .configure(HttpCommandEffector.JSON_PATH, "$.login")
+                .configure(HttpCommandEffector.PUBLISH_SENSOR, "result")
+        ).apply(entity);
+
+        String val = entity.invoke(EFFECTOR_GITHUB_APACHE_ACCOUNT, MutableMap.<String,String>of()).get();
+        Assert.assertEquals(val, "apache");
+        Assert.assertEquals(entity.sensors().get(Sensors.newStringSensor("result")), "apache");
+    }
+    
+    @Test(groups="Integration")
+    public void testHttpEffectorWithParameters() throws Exception {
+        new HttpCommandEffector(ConfigBag.newInstance()
+                .configure(HttpCommandEffector.EFFECTOR_NAME, "GithubApacheAccount")
+                .configure(HttpCommandEffector.EFFECTOR_URI, "https://api.github.com/users/$user")
+                .configure(HttpCommandEffector.EFFECTOR_HTTP_VERB, "GET")
+                .configure(HttpCommandEffector.EFFECTOR_PARAMETER_DEFS,
+                        MutableMap.<String,Object>of("user", MutableMap.of("defaultValue",
"apache"))))
+                .apply(entity);
+
+        String val;
+        // explicit value
+        val = entity.invoke(EFFECTOR_GITHUB_APACHE_ACCOUNT, MutableMap.of("user", "github")).get();
+        Assert.assertEquals(JsonPath.parse(val).read("$.login", String.class), "github");
+
+        // default value
+        val = entity.invoke(EFFECTOR_GITHUB_APACHE_ACCOUNT, MutableMap.<String,String>of()).get();
+        Assert.assertEquals(JsonPath.parse(val).read("$.login", String.class), "apache");
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/eadec9ac/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/EffectorApi.java
----------------------------------------------------------------------
diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/EffectorApi.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/EffectorApi.java
index 2865223..6143b6f 100644
--- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/EffectorApi.java
+++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/EffectorApi.java
@@ -18,19 +18,27 @@
  */
 package org.apache.brooklyn.rest.api;
 
-import io.swagger.annotations.Api;
-import org.apache.brooklyn.rest.domain.EffectorSummary;
-import io.swagger.annotations.ApiResponse;
-import io.swagger.annotations.ApiResponses;
-import io.swagger.annotations.ApiOperation;
-import io.swagger.annotations.ApiParam;
+import java.util.List;
+import java.util.Map;
 
 import javax.validation.Valid;
-import javax.ws.rs.*;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
-import java.util.List;
-import java.util.Map;
+
+import org.apache.brooklyn.rest.domain.EffectorSummary;
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
 
 @Path("/applications/{application}/entities/{entity}/effectors")
 @Api("Entity Effectors")
@@ -54,7 +62,7 @@ public interface EffectorApi {
     @POST
     @Path("/{effector}")
     @ApiOperation(value = "Trigger an effector",
-            notes="Returns the return value (status 200) if it completes, or an activity
task ID (status 202) if it times out")
+            notes="Returns the return value (status 200) if it completes, or an activity
task ID (status 202) if it times out", response = String.class)
     @ApiResponses(value = {
             @ApiResponse(code = 404, message = "Could not find application, entity or effector")
     })


Mime
View raw message