Skip to content

Commit 4422617

Browse files
committed
This closes #155
2 parents 91a35e2 + 70d9aca commit 4422617

10 files changed

Lines changed: 569 additions & 5 deletions

File tree

camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/BrooklynDslCommon.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
import com.google.common.base.Function;
7171
import com.google.common.base.Objects;
7272
import com.google.common.collect.ImmutableList;
73+
import com.google.common.collect.ImmutableMap;
7374
import com.google.common.collect.Lists;
7475
import com.google.common.collect.Maps;
7576

@@ -233,6 +234,18 @@ public static BrooklynDslDeferredSupplier<?> entityId() {
233234
return new DslComponent(Scope.THIS, "").entityId();
234235
}
235236

237+
public static BrooklynDslDeferredSupplier<?> effector(String effectorName, Map<String, ?> args) {
238+
return new DslComponent(Scope.THIS, "").effector(effectorName, args);
239+
}
240+
241+
public static BrooklynDslDeferredSupplier<?> effector(String effectorName) {
242+
return new DslComponent(Scope.THIS, "").effector(effectorName, ImmutableMap.<String, Object>of());
243+
}
244+
245+
public static BrooklynDslDeferredSupplier<?> effector(String effectorName, Object... args) {
246+
return new DslComponent(Scope.THIS, "").effector(effectorName, args);
247+
}
248+
236249
/** Returns a {@link Sensor}, looking up the sensor on the context if available and using that,
237250
* or else defining an untyped (Object) sensor */
238251
@DslAccessible

camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/DslComponent.java

Lines changed: 127 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,19 @@
2020

2121
import static org.apache.brooklyn.camp.brooklyn.spi.dsl.DslUtils.resolved;
2222

23+
import java.util.Iterator;
24+
import java.util.List;
25+
import java.util.Map;
2326
import java.util.NoSuchElementException;
2427
import java.util.concurrent.Callable;
2528
import java.util.concurrent.ExecutionException;
2629

30+
import org.apache.brooklyn.api.effector.Effector;
2731
import org.apache.brooklyn.api.entity.Entity;
2832
import org.apache.brooklyn.api.mgmt.ExecutionContext;
2933
import org.apache.brooklyn.api.mgmt.Task;
34+
import org.apache.brooklyn.api.mgmt.TaskAdaptable;
35+
import org.apache.brooklyn.api.mgmt.TaskFactory;
3036
import org.apache.brooklyn.api.objs.BrooklynObject;
3137
import org.apache.brooklyn.api.sensor.AttributeSensor;
3238
import org.apache.brooklyn.api.sensor.Sensor;
@@ -43,25 +49,32 @@
4349
import org.apache.brooklyn.core.mgmt.internal.EntityManagerInternal;
4450
import org.apache.brooklyn.core.sensor.DependentConfiguration;
4551
import org.apache.brooklyn.core.sensor.Sensors;
52+
import org.apache.brooklyn.util.collections.MutableMap;
4653
import org.apache.brooklyn.util.core.flags.TypeCoercions;
4754
import org.apache.brooklyn.util.core.task.BasicExecutionContext;
4855
import org.apache.brooklyn.util.core.task.DeferredSupplier;
56+
import org.apache.brooklyn.util.core.task.HasSideEffects;
4957
import org.apache.brooklyn.util.core.task.ImmediateSupplier;
5058
import org.apache.brooklyn.util.core.task.TaskBuilder;
5159
import org.apache.brooklyn.util.core.task.Tasks;
5260
import org.apache.brooklyn.util.exceptions.Exceptions;
5361
import org.apache.brooklyn.util.groovy.GroovyJavaMethods;
5462
import org.apache.brooklyn.util.guava.Maybe;
63+
import org.apache.brooklyn.util.text.StringEscapes.JavaStringEscapes;
5564
import org.apache.brooklyn.util.text.Strings;
5665

5766
import com.google.common.base.CaseFormat;
5867
import com.google.common.base.Converter;
68+
import com.google.common.base.Function;
5969
import com.google.common.base.Objects;
6070
import com.google.common.base.Optional;
6171
import com.google.common.base.Preconditions;
6272
import com.google.common.base.Predicate;
6373
import com.google.common.base.Predicates;
74+
import com.google.common.collect.ImmutableList;
75+
import com.google.common.collect.ImmutableMap;
6476
import com.google.common.collect.Iterables;
77+
import com.google.common.collect.Lists;
6578
import com.google.common.util.concurrent.Callables;
6679

6780
public class DslComponent extends BrooklynDslDeferredSupplier<Entity> implements DslFunctionSource {
@@ -509,6 +522,119 @@ public String toString() {
509522
}
510523
}
511524

525+
@DslAccessible
526+
public BrooklynDslDeferredSupplier<?> effector(final String effectorName) {
527+
return new ExecuteEffector(this, effectorName, ImmutableMap.<String, Object>of());
528+
}
529+
@DslAccessible
530+
public BrooklynDslDeferredSupplier<?> effector(final String effectorName, final Map<String, ?> args) {
531+
return new ExecuteEffector(this, effectorName, args);
532+
}
533+
public BrooklynDslDeferredSupplier<?> effector(final String effectorName, Object... args) {
534+
return new ExecuteEffector(this, effectorName, ImmutableList.copyOf(args));
535+
}
536+
protected static class ExecuteEffector extends BrooklynDslDeferredSupplier<Object> implements HasSideEffects {
537+
private static final long serialVersionUID = 1740899524088902383L;
538+
private final DslComponent component;
539+
private final String effectorName;
540+
private final Map<String, ?> args;
541+
private final List<? extends Object> argList;
542+
private Task<?> cachedTask;
543+
544+
public ExecuteEffector(DslComponent component, String effectorName, Map<String, ?> args) {
545+
this.component = Preconditions.checkNotNull(component);
546+
this.effectorName = effectorName;
547+
this.args = args;
548+
this.argList = null;
549+
}
550+
551+
public ExecuteEffector(DslComponent component, String effectorName, List<? extends Object> args) {
552+
this.component = Preconditions.checkNotNull(component);
553+
this.effectorName = effectorName;
554+
this.argList = args;
555+
this.args = null;
556+
}
557+
558+
@Override
559+
public Maybe<Object> getImmediately() {
560+
return Maybe.absent();
561+
}
562+
563+
@SuppressWarnings("unchecked")
564+
@Override
565+
public Task<Object> newTask() {
566+
Entity targetEntity = component.get();
567+
Maybe<Effector<?>> targetEffector = targetEntity.getEntityType().getEffectorByName(effectorName);
568+
if (targetEffector.isAbsentOrNull()) {
569+
throw new IllegalArgumentException("Effector " + effectorName + " not found on entity: " + targetEntity);
570+
}
571+
synchronized (this) {
572+
if (cachedTask == null) {
573+
if (argList == null) {
574+
cachedTask = Entities.invokeEffector(targetEntity, targetEntity, targetEffector.get(), args);
575+
} else {
576+
cachedTask = invokeWithDeferredArgs(targetEntity, targetEffector.get(), argList);
577+
}
578+
}
579+
}
580+
return (Task<Object>) cachedTask;
581+
}
582+
583+
private Task<Object> invokeWithDeferredArgs(final Entity targetEntity, final Effector<?> targetEffector, final List<? extends Object> args) {
584+
List<TaskAdaptable<Object>> taskArgs = Lists.newArrayList();
585+
for (Object arg : args) {
586+
if (arg instanceof TaskAdaptable) {
587+
taskArgs.add((TaskAdaptable<Object>) arg);
588+
} else if (arg instanceof TaskFactory) {
589+
taskArgs.add(((TaskFactory<TaskAdaptable<Object>>) arg).newTask());
590+
}
591+
}
592+
593+
return DependentConfiguration.transformMultiple(
594+
MutableMap.of("displayName", "invoking '"+targetEffector.getName()+"' with "+taskArgs.size()+" task"+(taskArgs.size()!=1?"s":"")),
595+
new Function<List<Object>, Object>() {
596+
@Override
597+
public Object apply(List<Object> input) {
598+
Iterator<?> tri = input.iterator();
599+
Object[] vv = new Object[args.size()];
600+
int i=0;
601+
for (Object arg : args) {
602+
if (arg instanceof TaskAdaptable || arg instanceof TaskFactory) {
603+
vv[i] = tri.next();
604+
} else if (arg instanceof DeferredSupplier) {
605+
vv[i] = ((DeferredSupplier<?>) arg).get();
606+
} else {
607+
vv[i] = arg;
608+
}
609+
i++;
610+
}
611+
return Entities.invokeEffectorWithArgs(targetEntity, targetEntity, targetEffector, vv);
612+
}
613+
},
614+
taskArgs);
615+
}
616+
617+
@Override
618+
public int hashCode() {
619+
return Objects.hashCode(component, effectorName);
620+
}
621+
622+
@Override
623+
public boolean equals(Object obj) {
624+
if (this == obj) return true;
625+
if (obj == null || getClass() != obj.getClass()) return false;
626+
ExecuteEffector that = ExecuteEffector.class.cast(obj);
627+
return Objects.equal(this.component, that.component) &&
628+
Objects.equal(this.effectorName, that.effectorName);
629+
}
630+
631+
@Override
632+
public String toString() {
633+
return (component.scope==Scope.THIS ? "" : component.toString()+".") +
634+
"effector("+JavaStringEscapes.wrapJavaString(effectorName)+")";
635+
}
636+
}
637+
512638
@DslAccessible
513639
public BrooklynDslDeferredSupplier<?> config(final Object keyNameOrSupplier) {
514640
return new DslConfigSupplier(this, keyNameOrSupplier);
@@ -763,5 +889,5 @@ public String toString() {
763889

764890
return DslToStringHelpers.component(scopeComponent, remainder);
765891
}
766-
892+
767893
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.brooklyn.camp.brooklyn;
20+
21+
import com.google.common.collect.Iterables;
22+
import org.apache.brooklyn.api.entity.Entity;
23+
import org.apache.brooklyn.core.test.entity.TestEntity;
24+
import org.apache.brooklyn.entity.software.base.SameServerEntity;
25+
import org.apache.brooklyn.entity.software.base.VanillaSoftwareProcess;
26+
import org.slf4j.Logger;
27+
import org.slf4j.LoggerFactory;
28+
import org.testng.Assert;
29+
import org.testng.annotations.Test;
30+
31+
import java.nio.file.Files;
32+
import java.nio.file.Path;
33+
import java.util.List;
34+
35+
import static org.apache.brooklyn.core.entity.EntityPredicates.displayNameEqualTo;
36+
import static org.testng.Assert.assertEquals;
37+
38+
@Test
39+
public class EffectorsYamlIntegrationTest extends AbstractYamlTest {
40+
private static final Logger log = LoggerFactory.getLogger(EffectorsYamlIntegrationTest.class);
41+
42+
@Test(groups = "Integration")
43+
public void testInteractingWithAnotherEntityForStartup() throws Exception {
44+
45+
final Path tempFile = Files.createTempFile("testInteractingWithAnotherEntityForStartup", ".txt");
46+
getLogger().info("Temp file is {}", tempFile.toAbsolutePath());
47+
48+
try {
49+
Entity app = createAndStartApplication(
50+
"location: localhost:(name=localhost)",
51+
"services:",
52+
"- type: " + SameServerEntity.class.getName(),
53+
" brooklyn.children:",
54+
" - type: " + TestEntity.class.getName(),
55+
" id: testEntity",
56+
" name: testEntity",
57+
" brooklyn.initializers:",
58+
" - type: org.apache.brooklyn.core.sensor.ssh.SshCommandSensor",
59+
" brooklyn.config:",
60+
" name: greeting",
61+
" period: 2s",
62+
" command: |",
63+
" echo hello world",
64+
" - type: " + VanillaSoftwareProcess.class.getName(),
65+
" id: consumerEntity",
66+
" brooklyn.config:",
67+
" install.latch: $brooklyn:entity(\"testEntity\").attributeWhenReady(\"service.isUp\")",
68+
" launch.command: while true; do sleep 3600 ; done & echo $! > ${PID_FILE}",
69+
" shell.env:",
70+
" RESPONSE: $brooklyn:entity(\"testEntity\").effector(\"identityEffector\", $brooklyn:entity(\"testEntity\").attributeWhenReady(\"greeting\"))",
71+
" post.launch.command: echo ${RESPONSE} > " + tempFile.toAbsolutePath()
72+
);
73+
waitForApplicationTasks(app);
74+
75+
final String contents = new String(Files.readAllBytes(tempFile)).trim();
76+
assertEquals(contents, "hello world", "file contents: " + contents);
77+
78+
} finally {
79+
Files.delete(tempFile);
80+
}
81+
}
82+
83+
84+
private void assertCallHistory(TestEntity testEntity, String... expectedCalls) {
85+
List<String> callHistory = testEntity.getCallHistory();
86+
Assert.assertEquals(callHistory.size(), expectedCalls.length, "history = " + callHistory);
87+
int c = 0;
88+
for (String expected : expectedCalls) {
89+
Assert.assertEquals(callHistory.get(c++), expected, "history = " + callHistory);
90+
}
91+
}
92+
93+
@Override
94+
protected Logger getLogger() {
95+
return log;
96+
}
97+
98+
}

0 commit comments

Comments
 (0)