/*
* 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.entity;
import static org.apache.brooklyn.core.entity.AbstractEntity.CHILD_ADDED;
import static org.apache.brooklyn.core.entity.AbstractEntity.CHILD_REMOVED;
import static org.apache.brooklyn.core.entity.AbstractEntity.CONFIG_KEY_ADDED;
import static org.apache.brooklyn.core.entity.AbstractEntity.CONFIG_KEY_REMOVED;
import static org.apache.brooklyn.core.entity.AbstractEntity.EFFECTOR_ADDED;
import static org.apache.brooklyn.core.entity.AbstractEntity.EFFECTOR_CHANGED;
import static org.apache.brooklyn.core.entity.AbstractEntity.EFFECTOR_REMOVED;
import static org.apache.brooklyn.core.entity.AbstractEntity.GROUP_ADDED;
import static org.apache.brooklyn.core.entity.AbstractEntity.GROUP_REMOVED;
import static org.apache.brooklyn.core.entity.AbstractEntity.LOCATION_ADDED;
import static org.apache.brooklyn.core.entity.AbstractEntity.LOCATION_REMOVED;
import static org.apache.brooklyn.core.entity.AbstractEntity.POLICY_ADDED;
import static org.apache.brooklyn.core.entity.AbstractEntity.POLICY_REMOVED;
import static org.apache.brooklyn.core.entity.AbstractEntity.SENSOR_ADDED;
import static org.apache.brooklyn.core.entity.AbstractEntity.SENSOR_REMOVED;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertNull;
import static org.testng.Assert.assertTrue;
import java.util.List;
import java.util.Set;
import javax.annotation.Nullable;
import org.apache.brooklyn.api.effector.Effector;
import org.apache.brooklyn.api.entity.Entity;
import org.apache.brooklyn.api.entity.EntitySpec;
import org.apache.brooklyn.api.sensor.AttributeSensor;
import org.apache.brooklyn.api.sensor.Sensor;
import org.apache.brooklyn.api.sensor.SensorEvent;
import org.apache.brooklyn.core.effector.MethodEffector;
import org.apache.brooklyn.core.sensor.BasicSensorEvent;
import org.apache.brooklyn.core.sensor.Sensors;
import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport;
import org.apache.brooklyn.core.test.entity.TestEntity;
import org.apache.brooklyn.core.test.entity.TestEntityImpl;
import org.apache.brooklyn.test.Asserts;
import org.apache.brooklyn.util.collections.MutableSet;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
public class EntityTypeTest extends BrooklynAppUnitTestSupport {
private static final AttributeSensor<String> TEST_SENSOR = Sensors.newStringSensor("test.sensor");
private EntityInternal entity;
@SuppressWarnings("rawtypes")
private RecordingSensorEventListener<Sensor> listener;
public final static Set<Sensor<?>> DEFAULT_SENSORS = ImmutableSet.<Sensor<?>>of(
SENSOR_ADDED, SENSOR_REMOVED,
CONFIG_KEY_ADDED, CONFIG_KEY_REMOVED,
EFFECTOR_ADDED, EFFECTOR_REMOVED, EFFECTOR_CHANGED,
POLICY_ADDED, POLICY_REMOVED,
CHILD_ADDED, CHILD_REMOVED,
LOCATION_ADDED, LOCATION_REMOVED,
GROUP_ADDED, GROUP_REMOVED);
public static class EmptyEntityForTesting extends AbstractEntity {}
@BeforeMethod(alwaysRun=true)
@Override
public void setUp() throws Exception{
super.setUp();
entity = (EntityInternal) app.createAndManageChild(EntitySpec.create(Entity.class, EmptyEntityForTesting.class));
listener = new RecordingSensorEventListener<>();
app.subscriptions().subscribe(entity, SENSOR_ADDED, listener);
app.subscriptions().subscribe(entity, SENSOR_REMOVED, listener);
}
@Test
public void testGetName() throws Exception {
TestEntity entity2 = app.createAndManageChild(EntitySpec.create(TestEntity.class));
assertEquals(entity2.getEntityType().getName(), TestEntity.class.getCanonicalName());
}
@Test
public void testGetSimpleName() throws Exception {
TestEntity entity2 = app.createAndManageChild(EntitySpec.create(TestEntity.class));
assertEquals(entity2.getEntityType().getSimpleName(), TestEntity.class.getSimpleName());
}
@Test
public void testGetEffectors() throws Exception {
TestEntity entity2 = app.createAndManageChild(EntitySpec.create(TestEntity.class));
Set<Effector<?>> effectors = entity2.getEntityType().getEffectors();
class MatchesNamePredicate implements Predicate<Effector<?>> {
private final String name;
public MatchesNamePredicate(String name) {
this.name = name;
}
@Override public boolean apply(@Nullable Effector<?> input) {
return name.equals(input.getName());
}
};
assertNotNull(Iterables.find(effectors, new MatchesNamePredicate("myEffector")), null);
assertNotNull(Iterables.find(effectors, new MatchesNamePredicate("identityEffector")), null);
}
@Test
public void testGetEffector() throws Exception {
TestEntity entity2 = app.createAndManageChild(EntitySpec.create(TestEntity.class));
Effector<?> effector = entity2.getEntityType().getEffectorByName("myEffector").get();
Effector<?> effector2 = entity2.getEntityType().getEffectorByName("identityEffector").get();
assertEquals(effector.getName(), "myEffector");
assertTrue(effector.getParameters().isEmpty(), "myEffector should have had no params, but had "+effector.getParameters());
assertEquals(effector2.getName(), "identityEffector");
assertEquals(effector2.getParameters().size(), 1, "identityEffector should have had one param, but had "+effector2.getParameters());
assertEquals(Iterables.getOnlyElement(effector2.getParameters()).getName(), "arg", "identityEffector should have had 'arg' param, but had "+effector2.getParameters());
}
@Test
public void testGetEffectorDeprecated() throws Exception {
TestEntity entity2 = app.createAndManageChild(EntitySpec.create(TestEntity.class));
Effector<?> effector = entity2.getEntityType().getEffectorByName("myEffector").get();
Effector<?> effector2 = entity2.getEntityType().getEffectorByName("identityEffector").get();
assertEquals(effector.getName(), "myEffector");
assertEquals(effector2.getName(), "identityEffector");
}
@Test
public void testCustomSimpleName() throws Exception {
class CustomTypeNamedEntity extends AbstractEntity {
private final String typeName;
@SuppressWarnings("deprecation")
CustomTypeNamedEntity(Entity parent, String typeName) {
super(parent);
this.typeName = typeName;
}
@Override protected String getEntityTypeName() {
return typeName;
}
}
CustomTypeNamedEntity entity2 = new CustomTypeNamedEntity(app, "a.b.with space");
Entities.manage(entity2);
assertEquals(entity2.getEntityType().getSimpleName(), "with_space");
CustomTypeNamedEntity entity3 = new CustomTypeNamedEntity(app, "a.b.with$dollar");
Entities.manage(entity3);
assertEquals(entity3.getEntityType().getSimpleName(), "with_dollar");
CustomTypeNamedEntity entity4 = new CustomTypeNamedEntity(app, "a.nothingafterdot.");
Entities.manage(entity4);
assertEquals(entity4.getEntityType().getSimpleName(), "a.nothingafterdot.");
}
@Test
public void testGetSensors() throws Exception{
assertEquals(entity.getEntityType().getSensors(), DEFAULT_SENSORS);
}
private void assertEventuallyListenerEventsEqual(@SuppressWarnings("rawtypes") final List<SensorEvent<Sensor>> sensorEvents) {
assertEventuallyListenerEventsEqual(listener, sensorEvents);
}
protected static <T> void assertEventuallyListenerEventsEqual(final RecordingSensorEventListener<T> listener, final List<SensorEvent<T>> sensorEvents) {
Asserts.succeedsEventually(new Runnable() {
@Override
public void run() {
assertEquals(listener.getEvents(), sensorEvents);
}
});
}
@Test
public void testAddSensors() throws Exception{
entity.getMutableEntityType().addSensor(TEST_SENSOR);
assertEquals(entity.getEntityType().getSensors(),
ImmutableSet.builder().addAll(DEFAULT_SENSORS).add(TEST_SENSOR).build());
assertEventuallyListenerEventsEqual(ImmutableList.of(BasicSensorEvent.ofUnchecked(SENSOR_ADDED, entity, TEST_SENSOR)));
}
@Test
public void testAddSensorValueThroughEntity() throws Exception{
entity.sensors().set(TEST_SENSOR, "abc");
assertEquals(entity.getEntityType().getSensors(),
ImmutableSet.builder().addAll(DEFAULT_SENSORS).add(TEST_SENSOR).build());
assertEventuallyListenerEventsEqual(ImmutableList.of(BasicSensorEvent.ofUnchecked(SENSOR_ADDED, entity, TEST_SENSOR)));
}
@Test
public void testRemoveSensorThroughEntity() throws Exception{
entity.sensors().set(TEST_SENSOR, "abc");
entity.removeAttribute(TEST_SENSOR);
assertFalse(entity.getEntityType().getSensors().contains(TEST_SENSOR), "sensors="+entity.getEntityType().getSensors());
assertEquals(entity.getAttribute(TEST_SENSOR), null);
assertEventuallyListenerEventsEqual(ImmutableList.of(BasicSensorEvent.ofUnchecked(SENSOR_ADDED, entity, TEST_SENSOR),
BasicSensorEvent.ofUnchecked(SENSOR_REMOVED, entity, TEST_SENSOR)));
}
@Test
public void testRemoveSensor() throws Exception {
entity.getMutableEntityType().removeSensor(SENSOR_ADDED);
assertEquals(entity.getEntityType().getSensors(),
MutableSet.builder().addAll(DEFAULT_SENSORS).remove(SENSOR_ADDED).build().asUnmodifiable());
assertEventuallyListenerEventsEqual(ImmutableList.of(
BasicSensorEvent.ofUnchecked(SENSOR_REMOVED, entity, SENSOR_ADDED)));
}
@Test
public void testRemoveSensors() throws Exception {
entity.getMutableEntityType().removeSensor(SENSOR_ADDED.getName());
entity.getMutableEntityType().removeSensor(POLICY_ADDED.getName());
assertEquals(entity.getEntityType().getSensors(),
MutableSet.builder().addAll(DEFAULT_SENSORS).remove(SENSOR_ADDED).remove(POLICY_ADDED).build().asUnmodifiable());
final RecordingSensorEventListener<?> listener = this.listener;
Asserts.succeedsEventually(new Runnable() {
@Override
public void run() {
assertEquals(Iterables.size(listener.getEvents()), 2);
}
});
assertEventuallyListenerEventsEqual(ImmutableList.of(
BasicSensorEvent.ofUnchecked(SENSOR_REMOVED, entity, SENSOR_ADDED),
BasicSensorEvent.ofUnchecked(SENSOR_REMOVED, entity, POLICY_ADDED)));
}
@Test
public void testGetSensor() throws Exception {
Sensor<?> sensor = entity.getEntityType().getSensor("entity.sensor.added");
assertEquals(sensor.getDescription(), "Sensor dynamically added to entity");
assertEquals(sensor.getName(), "entity.sensor.added");
assertNull(entity.getEntityType().getSensor("does.not.exist"));
}
@Test
public void testHasSensor() throws Exception {
assertTrue(entity.getEntityType().hasSensor("entity.sensor.added"));
assertFalse(entity.getEntityType().hasSensor("does.not.exist"));
}
// Previously EntityDynamicType's constructor when passed `entity` during the entity's construction (!)
// would pass this to EntityDynamicType.findEffectors, which would do log.warn in some cirumstances,
// with entity.toString as part of the log message. But if the toString called getConfig() this would
// fail because we were still in the middle of constructing the entity.entityType!
@Test
public void testEntityDynamicTypeDoesNotCallToStringDuringConstruction() throws Exception {
entity = app.createAndManageChild(EntitySpec.create(TestEntity.class).impl(EntityWithToStringAccessingConfig.class));
entity.toString();
}
public static class EntityWithToStringAccessingConfig extends TestEntityImpl {
// to cause warning to be logged: non-static constant
public final MethodEffector<Void> NON_STATIC_EFFECTOR = new MethodEffector<Void>(EntityWithToStringAccessingConfig.class, "nonStaticEffector");
public void nonStaticEffector() {
}
@Override
public String toString() {
return super.toString() + getConfig(CONF_NAME);
}
}
}