From ec01ecbf49698888e17f1f40a69e54f7e3bd27e1 Mon Sep 17 00:00:00 2001 From: Aled Sage Date: Fri, 14 Jul 2017 15:24:13 +0100 Subject: [PATCH 1/2] Adds coercion for simple string->predicate --- .../coerce/CommonAdaptorTypeCoercions.java | 14 ++++++++++++++ .../util/javalang/coerce/TypeCoercionsTest.java | 16 ++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/javalang/coerce/CommonAdaptorTypeCoercions.java b/utils/common/src/main/java/org/apache/brooklyn/util/javalang/coerce/CommonAdaptorTypeCoercions.java index b74548eef6..ae4c8b3e88 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/javalang/coerce/CommonAdaptorTypeCoercions.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/javalang/coerce/CommonAdaptorTypeCoercions.java @@ -48,6 +48,8 @@ import org.apache.brooklyn.util.yaml.Yamls; import com.google.common.base.Function; +import com.google.common.base.Predicate; +import com.google.common.base.Predicates; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Sets; @@ -273,6 +275,18 @@ public Date apply(final Integer input) { return new Date(input); } }); + registerAdapter(String.class, Predicate.class, new Function() { + @Override + public Predicate apply(final String input) { + switch (input) { + case "alwaysFalse" : return Predicates.alwaysFalse(); + case "alwaysTrue" : return Predicates.alwaysTrue(); + case "isNull" : return Predicates.isNull(); + case "notNull" : return Predicates.notNull(); + default: throw new IllegalArgumentException("Cannot convert string '" + input + "' to predicate"); + } + } + }); } @SuppressWarnings("rawtypes") diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/javalang/coerce/TypeCoercionsTest.java b/utils/common/src/test/java/org/apache/brooklyn/util/javalang/coerce/TypeCoercionsTest.java index e8ff59d298..74312ff9a9 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/javalang/coerce/TypeCoercionsTest.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/javalang/coerce/TypeCoercionsTest.java @@ -45,6 +45,7 @@ import org.testng.annotations.Test; import com.google.common.base.Predicate; +import com.google.common.base.Predicates; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; @@ -474,6 +475,21 @@ public void testCoerceStringToNumber() { assertEquals(coerce("1.0", Number.class), Double.valueOf(1.0)); } + @Test + public void testCoerceStringToPredicate() { + assertEquals(coerce("alwaysFalse", Predicate.class), Predicates.alwaysFalse()); + assertEquals(coerce("alwaysTrue", Predicate.class), Predicates.alwaysTrue()); + assertEquals(coerce("isNull", Predicate.class), Predicates.isNull()); + assertEquals(coerce("notNull", Predicate.class), Predicates.notNull()); + + try { + coerce("wrongInput", Predicate.class); + Asserts.shouldHaveFailedPreviously(); + } catch (RuntimeException e) { + Asserts.expectedFailureContains(e, "Cannot convert string 'wrongInput' to predicate"); + } + } + @Test(expectedExceptions = org.apache.brooklyn.util.javalang.coerce.ClassCoercionException.class) public void testInvalidCoercionThrowsClassCoercionException() { coerce(new Object(), TypeToken.of(Integer.class)); From d99e932b57eab13136d0488ed798038b04d8a27e Mon Sep 17 00:00:00 2001 From: Aled Sage Date: Fri, 14 Jul 2017 15:24:48 +0100 Subject: [PATCH 2/2] =?UTF-8?q?Adds=20Aggregator=20for=20=E2=80=98first?= =?UTF-8?q?=E2=80=99=20(and=20adds=20yaml=20tests)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../camp/brooklyn/AggregatorYamlTest.java | 199 ++++++++++++++++++ .../brooklyn/enricher/stock/Aggregator.java | 11 +- 2 files changed, 209 insertions(+), 1 deletion(-) create mode 100644 camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/AggregatorYamlTest.java diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/AggregatorYamlTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/AggregatorYamlTest.java new file mode 100644 index 0000000000..b5dadf869e --- /dev/null +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/AggregatorYamlTest.java @@ -0,0 +1,199 @@ +/* + * 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.camp.brooklyn; + +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.sensor.AttributeSensor; +import org.apache.brooklyn.core.entity.EntityAsserts; +import org.apache.brooklyn.core.sensor.Sensors; +import org.apache.brooklyn.core.test.entity.TestEntity; +import org.apache.brooklyn.enricher.stock.Aggregator; +import org.apache.brooklyn.entity.stock.BasicApplication; +import org.apache.brooklyn.util.collections.MutableList; +import org.apache.brooklyn.util.time.Duration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; + +@Test +public class AggregatorYamlTest extends AbstractYamlTest { + private static final Logger log = LoggerFactory.getLogger(AggregatorYamlTest.class); + + AttributeSensor myVal = Sensors.newSensor(Object.class, "myVal"); + AttributeSensor myResult = Sensors.newSensor(Object.class, "myResult"); + + @Test + public void testSum() throws Exception { + Entity app = createAndStartApplication( + "services:", + "- type: " + BasicApplication.class.getName(), + " brooklyn.children:", + " - type: " + TestEntity.class.getName(), + " - type: " + TestEntity.class.getName(), + " brooklyn.enrichers:", + " - type: " + Aggregator.class.getName(), + " brooklyn.config:", + " "+Aggregator.SOURCE_SENSOR.getName()+": myVal", + " "+Aggregator.TARGET_SENSOR.getName()+": myResult", + " "+Aggregator.TRANSFORMATION_UNTYPED.getName()+": sum"); + Entity child1 = Iterables.get(app.getChildren(), 0); + Entity child2 = Iterables.get(app.getChildren(), 1); + + child1.sensors().set(myVal, 1d); + EntityAsserts.assertAttributeEqualsEventually(app, myResult, 1d); + + child2.sensors().set(myVal, 2d); + EntityAsserts.assertAttributeEqualsEventually(app, myResult, 3d); + + child1.sensors().set(myVal, 3d); + EntityAsserts.assertAttributeEqualsEventually(app, myResult, 5d); + + child2.sensors().set(myVal, null); + EntityAsserts.assertAttributeEqualsEventually(app, myResult, 3d); + } + + @Test + public void testAverage() throws Exception { + Entity app = createAndStartApplication( + "services:", + "- type: " + BasicApplication.class.getName(), + " brooklyn.children:", + " - type: " + TestEntity.class.getName(), + " - type: " + TestEntity.class.getName(), + " brooklyn.enrichers:", + " - type: " + Aggregator.class.getName(), + " brooklyn.config:", + " "+Aggregator.SOURCE_SENSOR.getName()+": myVal", + " "+Aggregator.TARGET_SENSOR.getName()+": myResult", + " "+Aggregator.TRANSFORMATION_UNTYPED.getName()+": average"); + Entity child1 = Iterables.get(app.getChildren(), 0); + Entity child2 = Iterables.get(app.getChildren(), 1); + + child1.sensors().set(myVal, 1d); + EntityAsserts.assertAttributeEqualsEventually(app, myResult, 1d); + + child2.sensors().set(myVal, 3d); + EntityAsserts.assertAttributeEqualsEventually(app, myResult, 2d); + + child1.sensors().set(myVal, null); + EntityAsserts.assertAttributeEqualsEventually(app, myResult, 3d); + } + + @Test + public void testList() throws Exception { + Entity app = createAndStartApplication( + "services:", + "- type: " + BasicApplication.class.getName(), + " brooklyn.children:", + " - type: " + TestEntity.class.getName(), + " - type: " + TestEntity.class.getName(), + " brooklyn.enrichers:", + " - type: " + Aggregator.class.getName(), + " brooklyn.config:", + " "+Aggregator.SOURCE_SENSOR.getName()+": myVal", + " "+Aggregator.TARGET_SENSOR.getName()+": myResult", + " "+Aggregator.TRANSFORMATION_UNTYPED.getName()+": list"); + Entity child1 = Iterables.get(app.getChildren(), 0); + Entity child2 = Iterables.get(app.getChildren(), 1); + + child1.sensors().set(myVal, "val1"); + EntityAsserts.assertAttributeEqualsEventually(app, myResult, MutableList.of("val1", null)); + + child2.sensors().set(myVal, "val2"); + EntityAsserts.assertAttributeEqualsEventually(app, myResult, ImmutableList.of("val1", "val2")); + + child1.sensors().set(myVal, "val1b"); + EntityAsserts.assertAttributeEqualsEventually(app, myResult, ImmutableList.of("val1b", "val2")); + + child1.sensors().set(myVal, null); + EntityAsserts.assertAttributeEqualsEventually(app, myResult, MutableList.of(null, "val2")); + } + + @Test + public void testFirst() throws Exception { + Entity app = createAndStartApplication( + "services:", + "- type: " + BasicApplication.class.getName(), + " brooklyn.children:", + " - type: " + TestEntity.class.getName(), + " - type: " + TestEntity.class.getName(), + " brooklyn.enrichers:", + " - type: " + Aggregator.class.getName(), + " brooklyn.config:", + " "+Aggregator.SOURCE_SENSOR.getName()+": myVal", + " "+Aggregator.TARGET_SENSOR.getName()+": myResult", + " "+Aggregator.TRANSFORMATION_UNTYPED.getName()+": first", + " "+Aggregator.VALUE_FILTER.getName()+": notNull"); + Entity child1 = Iterables.get(app.getChildren(), 0); + Entity child2 = Iterables.get(app.getChildren(), 1); + + child1.sensors().set(myVal, "val1"); + EntityAsserts.assertAttributeEqualsEventually(app, myResult, "val1"); + + child2.sensors().set(myVal, "val2"); + EntityAsserts.assertAttributeEqualsContinually(ImmutableMap.of("timeout", Duration.millis(50)), app, myResult, "val1"); + + child1.sensors().set(myVal, null); + EntityAsserts.assertAttributeEqualsEventually(app, myResult, "val2"); + + child2.sensors().set(myVal, null); + EntityAsserts.assertAttributeEqualsEventually(app, myResult, null); + + child1.sensors().set(myVal, "val3"); + EntityAsserts.assertAttributeEqualsEventually(app, myResult, "val3"); + } + + @Test + public void testQuorum() throws Exception { + Entity app = createAndStartApplication( + "services:", + "- type: " + BasicApplication.class.getName(), + " brooklyn.children:", + " - type: " + TestEntity.class.getName(), + " - type: " + TestEntity.class.getName(), + " brooklyn.enrichers:", + " - type: " + Aggregator.class.getName(), + " brooklyn.config:", + " "+Aggregator.SOURCE_SENSOR.getName()+": myVal", + " "+Aggregator.TARGET_SENSOR.getName()+": myResult", + " "+Aggregator.TRANSFORMATION_UNTYPED.getName()+": isQuorate", + " "+Aggregator.QUORUM_CHECK_TYPE.getName()+": all", + " "+Aggregator.QUORUM_TOTAL_SIZE.getName()+": 2"); + Entity child1 = Iterables.get(app.getChildren(), 0); + Entity child2 = Iterables.get(app.getChildren(), 1); + + child1.sensors().set(myVal, true); + EntityAsserts.assertAttributeEqualsEventually(app, myResult, false); + + child2.sensors().set(myVal, true); + EntityAsserts.assertAttributeEqualsEventually(app, myResult, true); + + child2.sensors().set(myVal, null); + EntityAsserts.assertAttributeEqualsEventually(app, myResult, false); + } + + @Override + protected Logger getLogger() { + return log; + } +} diff --git a/core/src/main/java/org/apache/brooklyn/enricher/stock/Aggregator.java b/core/src/main/java/org/apache/brooklyn/enricher/stock/Aggregator.java index f94c652a79..1ba4e6dd34 100644 --- a/core/src/main/java/org/apache/brooklyn/enricher/stock/Aggregator.java +++ b/core/src/main/java/org/apache/brooklyn/enricher/stock/Aggregator.java @@ -62,7 +62,8 @@ public class Aggregator extends AbstractAggregator implements SensorEv "Specifies a transformation, as a function from a collection to the value, or as a string " + "matching a pre-defined named transformation, such as 'average' (for numbers), " + "'sum' (for numbers), 'isQuorate' (to compute a quorum), " + - "or 'list' (the default, putting any collection of items into a list)"); + "'list' (the default, putting any collection of items into a list), " + + "or 'first' (the first value, or null if empty)"); public static final ConfigKey, ?>> TRANSFORMATION = ConfigKeys.newConfigKey(new TypeToken, ?>>() {}, "enricher.transformation"); @@ -120,6 +121,7 @@ protected void setEntityLoadingConfig() { if ("isQuorate".equalsIgnoreCase(t1)) return new Enrichers.ComputingIsQuorate(targetSensor.getTypeToken(), QuorumChecks.of(config().get(QUORUM_CHECK_TYPE)), config().get(QUORUM_TOTAL_SIZE)); if ("list".equalsIgnoreCase(t1)) return new ComputingList(); + if ("first".equalsIgnoreCase(t1)) return new FirstOrNull(); return null; } @@ -131,6 +133,13 @@ public List apply(Collection input) { } } + private static class FirstOrNull implements Function, TT> { + @Override + public TT apply(Collection input) { + return (input==null) ? null : Iterables.getFirst(input, null); + } + } + @Override protected void setEntityBeforeSubscribingProducerChildrenEvents() { BrooklynLogging.log(LOG, BrooklynLogging.levelDebugOrTraceIfReadOnly(producer),