http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/entity/EntityAsserts.java ---------------------------------------------------------------------- diff --cc brooklyn-server/core/src/main/java/org/apache/brooklyn/core/entity/EntityAsserts.java index 0000000,4641447..e78a1af mode 000000,100644..100644 --- a/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/entity/EntityAsserts.java +++ b/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/entity/EntityAsserts.java @@@ -1,0 -1,228 +1,226 @@@ + /* + * 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 com.google.common.annotations.Beta; + import com.google.common.base.Objects; + import com.google.common.base.Predicate; + import com.google.common.base.Predicates; + import com.google.common.collect.ImmutableMap; + import com.google.common.collect.Maps; + import com.google.common.collect.Sets; + import org.apache.brooklyn.api.entity.Entity; + import org.apache.brooklyn.api.entity.Group; + import org.apache.brooklyn.api.mgmt.SubscriptionHandle; + import org.apache.brooklyn.api.sensor.AttributeSensor; + import org.apache.brooklyn.api.sensor.SensorEvent; + import org.apache.brooklyn.api.sensor.SensorEventListener; + import org.apache.brooklyn.config.ConfigKey; + import org.apache.brooklyn.test.Asserts; + import org.apache.brooklyn.util.time.Duration; + + import java.util.Collection; + import java.util.Map; + import java.util.Set; + import java.util.concurrent.Callable; + import java.util.concurrent.atomic.AtomicBoolean; + import java.util.concurrent.atomic.AtomicReference; + + import static org.apache.brooklyn.test.Asserts.assertEquals; + + /** + * Convenience class containing assertions that may be made about entities. + */ + public class EntityAsserts { + + + public static <T> void assertAttributeEquals(Entity entity, AttributeSensor<T> attribute, T expected) { + assertEquals(entity.getAttribute(attribute), expected, "entity=" + entity + "; attribute=" + attribute); + } + + public static <T> void assertConfigEquals(Entity entity, ConfigKey<T> configKey, T expected) { + assertEquals(entity.getConfig(configKey), expected, "entity=" + entity + "; configKey=" + configKey); + } + + public static <T> void assertAttributeEqualsEventually(final Entity entity, final AttributeSensor<T> attribute, final T expected) { + assertAttributeEqualsEventually(Maps.newLinkedHashMap(), entity, attribute, expected); + } + + public static <T> void assertAttributeEqualsEventually(Map<?,?> flags, final Entity entity, final AttributeSensor<T> attribute, final T expected) { + // Not using assertAttributeEventually(predicate) so get nicer error message + Asserts.succeedsEventually((Map) flags, new Runnable() { + @Override + public void run() { + assertAttributeEquals(entity, attribute, expected); + } + }); + } + + public static <T> T assertAttributeEventuallyNonNull(final Entity entity, final AttributeSensor<T> attribute) { + return assertAttributeEventuallyNonNull(Maps.newLinkedHashMap(), entity, attribute); + } + + public static <T> T assertAttributeEventuallyNonNull(Map<?,?> flags, final Entity entity, final AttributeSensor<T> attribute) { + return assertAttributeEventually(flags, entity, attribute, Predicates.notNull()); + } + + public static <T> T assertAttributeEventually(final Entity entity, final AttributeSensor<T> attribute, Predicate<? super T> predicate) { + return assertAttributeEventually(ImmutableMap.of(), entity, attribute, predicate); + } + + public static <T> T assertAttributeEventually(Map<?,?> flags, final Entity entity, final AttributeSensor<T> attribute, final Predicate<? super T> predicate) { + final AtomicReference<T> result = new AtomicReference<T>(); + Asserts.succeedsEventually((Map)flags, new Runnable() { + @Override public void run() { + T val = entity.getAttribute(attribute); + Asserts.assertTrue(predicate.apply(val), "val=" + val); + result.set(val); + }}); + return result.get(); + } + + public static <T> T assertAttribute(final Entity entity, final AttributeSensor<T> attribute, final Predicate<? super T> predicate) { + T val = entity.getAttribute(attribute); + Asserts.assertTrue(predicate.apply(val), "val=" + val); + return val; + } + + + public static <T extends Entity> void assertPredicateEventuallyTrue(final T entity, final Predicate<? super T> predicate) { + assertPredicateEventuallyTrue(Maps.newLinkedHashMap(), entity, predicate); + } + + public static <T extends Entity> void assertPredicateEventuallyTrue(Map<?,?> flags, final T entity, final Predicate<? super T> predicate) { + Asserts.succeedsEventually((Map)flags, new Runnable() { + @Override public void run() { + Asserts.assertTrue(predicate.apply(entity), "predicate unsatisfied"); + }}); + } + + public static <T> void assertAttributeEqualsContinually(final Entity entity, final AttributeSensor<T> attribute, final T expected) { + assertAttributeEqualsContinually(Maps.newLinkedHashMap(), entity, attribute, expected); + } + + public static <T> void assertAttributeEqualsContinually(Map<?,?> flags, final Entity entity, final AttributeSensor<T> attribute, final T expected) { + Asserts.succeedsContinually(flags, new Runnable() { + @Override public void run() { + assertAttributeEquals(entity, attribute, expected); + }}); + } + + public static void assertGroupSizeEqualsEventually(final Group group, int expected) { + assertGroupSizeEqualsEventually(ImmutableMap.of(), group, expected); + } + + public static void assertGroupSizeEqualsEventually(Map<?,?> flags, final Group group, final int expected) { + Asserts.succeedsEventually((Map)flags, new Runnable() { + @Override public void run() { + Collection<Entity> members = group.getMembers(); + assertEquals(members.size(), expected, "members=" + members); + }}); + } + + + /** + * Asserts that the entity's value for this attribute changes, by registering a subscription and checking the value. + * + * @param entity The entity whose attribute will be checked. + * @param attribute The attribute to check on the entity. + * + * @throws AssertionError if the assertion fails. + */ + public static void assertAttributeChangesEventually(final Entity entity, final AttributeSensor<?> attribute) { + final Object origValue = entity.getAttribute(attribute); + final AtomicBoolean changed = new AtomicBoolean(); + SubscriptionHandle handle = entity.subscriptions().subscribe(entity, attribute, new SensorEventListener<Object>() { + @Override public void onEvent(SensorEvent<Object> event) { + if (!Objects.equal(origValue, event.getValue())) { + changed.set(true); + } + }}); + try { + Asserts.succeedsEventually(new Runnable() { + @Override public void run() { + Asserts.assertTrue(changed.get(), entity + " -> " + attribute + " not changed"); + }}); + } finally { + entity.subscriptions().unsubscribe(entity, handle); + } + } + + + /** + * Assert that the given attribute of an entity does not take any of the disallowed values during a given period. + * + * This method relies on {@link Asserts#succeedsContinually(Runnable)}, therefore it loops comparing the value + * of the attribute to the disallowed values, rather than setting up a subscription. It may therefore miss a + * situation where the attribute temporarily takes a disallowed value. This method is therefore suited for use + * where the attribute will take on a value permanently, which may or may not be disallowed. + * + * @param entity The entity owning the attribute to check. + * @param attribute The attribute on the entity. + * @param disallowed The disallowed values for the entity. + * @param <T> Type of the sensor. + */ + @Beta @SafeVarargs + public static <T> void assertAttributeContinuallyNotEqualTo(final Entity entity, final AttributeSensor<T> attribute, T... disallowed) { + final Set<T> reject = Sets.newHashSet(disallowed); + Asserts.succeedsContinually(new Runnable() { + @Override + public void run() { + T val = entity.getAttribute(attribute); + Asserts.assertFalse(reject.contains(val), + "Attribute " + attribute + " on " + entity + " has disallowed value " + val); + } + }); + } + - - + /** + * Assert that the given attribute of an entity does not take any of the disallowed values during a given period. + * + * This method relies on {@link Asserts#succeedsContinually(Runnable)}, therefore it loops comparing the value + * of the attribute to the disallowed values, rather than setting up a subscription. It may therefore miss a + * situation where the attribute temporarily takes a disallowed value. This method is therefore suited for use + * where the attribute will take on a value permanently, which may or may not be disallowed. + * + * @param flags Flags controlling the loop, with keys: <ul> + * <li>timeout: a {@link Duration} specification String for the duration for which to test the + * assertion. Default 1 second.</li> + * <li>period: a {@link Duration} specification String for the interval at which to perform polls + * on the attribute value. Default 10ms.</li> + * </ul> + * @param entity The entity owning the attribute to check. + * @param attribute The attribute on the entity. + * @param disallowed The disallowed values for the entity. + * @param <T> Type of the sensor. + */ + @Beta @SafeVarargs + public static <T> void assertAttributeContinuallyNotEqualTo(final Map<?, ?> flags, final Entity entity, final AttributeSensor<T> attribute, T... disallowed) { + final Set<T> reject = Sets.newHashSet(disallowed); + Asserts.succeedsContinually(flags, new Runnable() { + @Override + public void run() { + T val = entity.getAttribute(attribute); + Asserts.assertFalse(reject.contains(val), + "Attribute " + attribute + " on " + entity + " has disallowed value " + val); + } + }); + } + + }
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/entity/EntityFunctions.java ---------------------------------------------------------------------- diff --cc brooklyn-server/core/src/main/java/org/apache/brooklyn/core/entity/EntityFunctions.java index 0000000,a4459ed..c65a176 mode 000000,100644..100644 --- a/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/entity/EntityFunctions.java +++ b/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/entity/EntityFunctions.java @@@ -1,0 -1,291 +1,307 @@@ + /* + * 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 com.google.common.base.Preconditions.checkNotNull; + + import java.util.Collection; + import java.util.Map; + + import org.apache.brooklyn.api.entity.Application; + import org.apache.brooklyn.api.entity.Entity; + import org.apache.brooklyn.api.entity.EntityLocal; + import org.apache.brooklyn.api.location.Location; + import org.apache.brooklyn.api.mgmt.ManagementContext; + import org.apache.brooklyn.api.objs.Identifiable; + import org.apache.brooklyn.api.sensor.AttributeSensor; + import org.apache.brooklyn.config.ConfigKey; + import org.apache.brooklyn.core.entity.lifecycle.ServiceStateLogic; + import org.apache.brooklyn.util.core.flags.TypeCoercions; + import org.apache.brooklyn.util.guava.Functionals; + + import com.google.common.base.Function; + import com.google.common.base.Predicate; + import com.google.common.base.Supplier; + import com.google.common.base.Suppliers; + import com.google.common.collect.Iterables; + + public class EntityFunctions { + + /** @deprecated since 0.9.0 kept only to allow conversion of non-static inner classes */ + @SuppressWarnings("unused") @Deprecated + private static <T> Function<Entity, T> attributeOld(final AttributeSensor<T> attribute) { + // TODO PERSISTENCE WORKAROUND + class GetEntityAttributeFunction implements Function<Entity, T> { + @Override public T apply(Entity input) { + return (input == null) ? null : input.getAttribute(attribute); + } - }; ++ } + return new GetEntityAttributeFunction(); + } + + /** @deprecated since 0.9.0 kept only to allow conversion of non-static inner classes */ + @SuppressWarnings("unused") @Deprecated + private static <T> Function<Entity, T> configOld(final ConfigKey<T> key) { + // TODO PERSISTENCE WORKAROUND + class GetEntityConfigFunction implements Function<Entity, T> { + @Override public T apply(Entity input) { + return (input == null) ? null : input.getConfig(key); + } - }; ++ } + return new GetEntityConfigFunction(); + } + + /** @deprecated since 0.9.0 kept only to allow conversion of non-static inner classes */ + @SuppressWarnings("unused") @Deprecated + private static Function<Entity, String> displayNameOld() { + // TODO PERSISTENCE WORKAROUND + class GetEntityDisplayName implements Function<Entity, String> { + @Override public String apply(Entity input) { + return (input == null) ? null : input.getDisplayName(); + } - }; ++ } + return new GetEntityDisplayName(); + } + + /** @deprecated since 0.9.0 kept only to allow conversion of non-static inner classes */ + @SuppressWarnings("unused") @Deprecated + private static Function<Identifiable, String> idOld() { + // TODO PERSISTENCE WORKAROUND + class GetIdFunction implements Function<Identifiable, String> { + @Override public String apply(Identifiable input) { + return (input == null) ? null : input.getId(); + } - }; ++ } + return new GetIdFunction(); + } + + /** @deprecated since 0.9.0 kept only to allow conversion of non-static inner classes */ + @SuppressWarnings("unused") @Deprecated + private static Function<Entity,Void> settingSensorsConstantOld(final Map<AttributeSensor<?>,Object> values) { + // TODO PERSISTENCE WORKAROUND + class SettingSensorsConstantFunction implements Function<Entity, Void> { + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Override public Void apply(Entity input) { + for (Map.Entry<AttributeSensor<?>,Object> entry : values.entrySet()) { + AttributeSensor sensor = (AttributeSensor)entry.getKey(); + Object value = entry.getValue(); + if (value==Entities.UNCHANGED) { + // nothing + } else if (value==Entities.REMOVE) { + ((EntityInternal)input).removeAttribute(sensor); + } else { + value = TypeCoercions.coerce(value, sensor.getTypeToken()); + ((EntityInternal)input).sensors().set(sensor, value); + } + } + return null; + } + } + return new SettingSensorsConstantFunction(); + } + + /** @deprecated since 0.9.0 kept only to allow conversion of non-static inner classes */ + @SuppressWarnings("unused") @Deprecated + private static <K,V> Function<Entity, Void> updatingSensorMapEntryOld(final AttributeSensor<Map<K,V>> mapSensor, final K key, final Supplier<? extends V> valueSupplier) { + // TODO PERSISTENCE WORKAROUND + class UpdatingSensorMapEntryFunction implements Function<Entity, Void> { + @Override public Void apply(Entity input) { + ServiceStateLogic.updateMapSensorEntry((EntityLocal)input, mapSensor, key, valueSupplier.get()); + return null; + } + } + return new UpdatingSensorMapEntryFunction(); + } + + /** @deprecated since 0.9.0 kept only to allow conversion of non-static inner classes */ + @SuppressWarnings("unused") @Deprecated + private static Supplier<Collection<Application>> applicationsOld(final ManagementContext mgmt) { + // TODO PERSISTENCE WORKAROUND + class AppsSupplier implements Supplier<Collection<Application>> { + @Override + public Collection<Application> get() { + return mgmt.getApplications(); + } + } + return new AppsSupplier(); + } + + public static <T> Function<Entity, T> attribute(AttributeSensor<T> attribute) { + return new GetEntityAttributeFunction<T>(checkNotNull(attribute, "attribute")); + } + + protected static class GetEntityAttributeFunction<T> implements Function<Entity, T> { + private final AttributeSensor<T> attribute; + protected GetEntityAttributeFunction(AttributeSensor<T> attribute) { + this.attribute = attribute; + } + @Override public T apply(Entity input) { + return (input == null) ? null : input.getAttribute(attribute); + } - }; ++ } ++ ++ public static <T> Function<Object, T> attribute(Entity entity, AttributeSensor<T> attribute) { ++ return new GetFixedEntityAttributeFunction<>(entity, attribute); ++ } ++ ++ protected static class GetFixedEntityAttributeFunction<T> implements Function<Object, T> { ++ private final Entity entity; ++ private final AttributeSensor<T> attribute; ++ protected GetFixedEntityAttributeFunction(Entity entity, AttributeSensor<T> attribute) { ++ this.entity = entity; ++ this.attribute = attribute; ++ } ++ @Override public T apply(Object input) { ++ return entity.getAttribute(attribute); ++ } ++ } + + public static <T> Function<Entity, T> config(ConfigKey<T> key) { + return new GetEntityConfigFunction<T>(checkNotNull(key, "key")); + } + + protected static class GetEntityConfigFunction<T> implements Function<Entity, T> { + private final ConfigKey<T> key; + + protected GetEntityConfigFunction(ConfigKey<T> key) { + this.key = key; + } + + @Override public T apply(Entity input) { + return (input == null) ? null : input.getConfig(key); + } - }; ++ } + + public static Function<Entity, String> displayName() { + return GetEntityDisplayName.INSTANCE; + } + + protected static class GetEntityDisplayName implements Function<Entity, String> { + public static final GetEntityDisplayName INSTANCE = new GetEntityDisplayName(); + @Override public String apply(Entity input) { + return (input == null) ? null : input.getDisplayName(); + } - }; ++ } + + public static Function<Identifiable, String> id() { + return GetIdFunction.INSTANCE; + } + + protected static class GetIdFunction implements Function<Identifiable, String> { + public static final GetIdFunction INSTANCE = new GetIdFunction(); + @Override public String apply(Identifiable input) { + return (input == null) ? null : input.getId(); + } - }; ++ } + + + /** returns a function which sets the given sensors on the entity passed in, + * with {@link Entities#UNCHANGED} and {@link Entities#REMOVE} doing those actions. */ + public static Function<Entity,Void> settingSensorsConstant(final Map<AttributeSensor<?>,Object> values) { + return new SettingSensorsConstantFunction(checkNotNull(values, "values")); + } + + protected static class SettingSensorsConstantFunction implements Function<Entity, Void> { + private final Map<AttributeSensor<?>, Object> values; + + protected SettingSensorsConstantFunction(Map<AttributeSensor<?>, Object> values) { + this.values = values; + } + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Override public Void apply(Entity input) { + for (Map.Entry<AttributeSensor<?>,Object> entry : values.entrySet()) { + AttributeSensor sensor = (AttributeSensor)entry.getKey(); + Object value = entry.getValue(); + if (value==Entities.UNCHANGED) { + // nothing + } else if (value==Entities.REMOVE) { + ((EntityInternal)input).sensors().remove(sensor); + } else { + value = TypeCoercions.coerce(value, sensor.getTypeToken()); + ((EntityInternal)input).sensors().set(sensor, value); + } + } + return null; + } + } + + /** as {@link #settingSensorsConstant(Map)} but as a {@link Runnable} */ + public static Runnable settingSensorsConstant(final Entity entity, final Map<AttributeSensor<?>,Object> values) { + checkNotNull(entity, "entity"); + checkNotNull(values, "values"); + return Functionals.runnable(Suppliers.compose(settingSensorsConstant(values), Suppliers.ofInstance(entity))); + } + + public static <K,V> Function<Entity, Void> updatingSensorMapEntry(final AttributeSensor<Map<K,V>> mapSensor, final K key, final Supplier<? extends V> valueSupplier) { + return new UpdatingSensorMapEntryFunction<K,V>(mapSensor, key, valueSupplier); + } + + protected static class UpdatingSensorMapEntryFunction<K, V> implements Function<Entity, Void> { + private final AttributeSensor<Map<K, V>> mapSensor; + private final K key; + private final Supplier<? extends V> valueSupplier; + + public UpdatingSensorMapEntryFunction(AttributeSensor<Map<K, V>> mapSensor, K key, Supplier<? extends V> valueSupplier) { + this.mapSensor = mapSensor; + this.key = key; + this.valueSupplier = valueSupplier; + } + @Override public Void apply(Entity input) { + ServiceStateLogic.updateMapSensorEntry((EntityLocal)input, mapSensor, key, valueSupplier.get()); + return null; + } + } + + public static <K,V> Runnable updatingSensorMapEntry(final Entity entity, final AttributeSensor<Map<K,V>> mapSensor, final K key, final Supplier<? extends V> valueSupplier) { + return Functionals.runnable(Suppliers.compose(updatingSensorMapEntry(mapSensor, key, valueSupplier), Suppliers.ofInstance(entity))); + } + + public static Supplier<Collection<Application>> applications(ManagementContext mgmt) { + return new AppsSupplier(checkNotNull(mgmt, "mgmt")); + } + + protected static class AppsSupplier implements Supplier<Collection<Application>> { + private final ManagementContext mgmt; + + public AppsSupplier(ManagementContext mgmt) { + this.mgmt = mgmt; + } + @Override + public Collection<Application> get() { + return mgmt.getApplications(); + } + } + + public static Function<Entity, Location> locationMatching(Predicate<? super Location> filter) { + return new LocationMatching(filter); + } + + private static class LocationMatching implements Function<Entity, Location> { + private Predicate<? super Location> filter; + + private LocationMatching() { /* for xstream */ + } + public LocationMatching(Predicate<? super Location> filter) { + this.filter = filter; + } + @Override public Location apply(Entity input) { + return Iterables.find(input.getLocations(), filter); + } + } + } http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/entity/internal/EntityConfigMap.java ---------------------------------------------------------------------- diff --cc brooklyn-server/core/src/main/java/org/apache/brooklyn/core/entity/internal/EntityConfigMap.java index 0000000,111eee0..da209e1 mode 000000,100644..100644 --- a/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/entity/internal/EntityConfigMap.java +++ b/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/entity/internal/EntityConfigMap.java @@@ -1,0 -1,306 +1,319 @@@ + /* + * 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.internal; + + import static com.google.common.base.Preconditions.checkNotNull; + import static org.apache.brooklyn.util.groovy.GroovyJavaMethods.elvis; + ++import java.util.Collection; + import java.util.Collections; + import java.util.LinkedHashMap; + import java.util.Map; + import java.util.Set; + + import org.apache.brooklyn.api.mgmt.ExecutionContext; + import org.apache.brooklyn.api.mgmt.Task; + import org.apache.brooklyn.config.ConfigInheritance; + import org.apache.brooklyn.config.ConfigKey; + import org.apache.brooklyn.core.config.Sanitizer; + import org.apache.brooklyn.core.config.StructuredConfigKey; + import org.apache.brooklyn.core.config.internal.AbstractConfigMapImpl; + import org.apache.brooklyn.core.entity.AbstractEntity; + import org.apache.brooklyn.util.collections.MutableMap; + import org.apache.brooklyn.util.core.config.ConfigBag; + import org.apache.brooklyn.util.core.flags.FlagUtils; + import org.apache.brooklyn.util.core.flags.SetFromFlag; + import org.apache.brooklyn.util.core.flags.TypeCoercions; + import org.apache.brooklyn.util.core.internal.ConfigKeySelfExtracting; + import org.apache.brooklyn.util.guava.Maybe; + import org.slf4j.Logger; + import org.slf4j.LoggerFactory; + + import com.google.common.base.Predicate; + import com.google.common.collect.Maps; + import com.google.common.collect.Sets; + + public class EntityConfigMap extends AbstractConfigMapImpl { + + private static final Logger LOG = LoggerFactory.getLogger(EntityConfigMap.class); + + /** entity against which config resolution / task execution will occur */ + private final AbstractEntity entity; + + /** + * Map of configuration information that is defined at start-up time for the entity. These + * configuration parameters are shared and made accessible to the "children" of this + * entity. + */ + private final Map<ConfigKey<?>,Object> inheritedConfig = Collections.synchronizedMap(new LinkedHashMap<ConfigKey<?>, Object>()); + // TODO do we really want to have *both* bags and maps for these? danger that they get out of synch. + // have added some logic (Oct 2014) so that the same changes are applied to both, in most places at least; + // i (alex) think we should prefer ConfigBag (the input keys don't matter, it is more a question of retrieval keys), + // but first we need ConfigBag to support StructuredConfigKeys + private final ConfigBag localConfigBag; + private final ConfigBag inheritedConfigBag; + ++ public EntityConfigMap(AbstractEntity entity) { ++ // Not using ConcurrentMap, because want to (continue to) allow null values. ++ // Could use ConcurrentMapAcceptingNullVals (with the associated performance hit on entrySet() etc). ++ this(entity, Collections.synchronizedMap(Maps.<ConfigKey<?>, Object>newLinkedHashMap())); ++ } ++ + public EntityConfigMap(AbstractEntity entity, Map<ConfigKey<?>, Object> storage) { + this.entity = checkNotNull(entity, "entity must be specified"); + this.ownConfig = checkNotNull(storage, "storage map must be specified"); + + // TODO store ownUnused in backing-storage + this.localConfigBag = ConfigBag.newInstance(); + this.inheritedConfigBag = ConfigBag.newInstance(); + } + + @SuppressWarnings("unchecked") + public <T> T getConfig(ConfigKey<T> key, T defaultValue) { + // FIXME What about inherited task in config?! + // alex says: think that should work, no? + // FIXME What if someone calls getConfig on a task, before setting parent app? + // alex says: not supported (throw exception, or return the task) + + // In case this entity class has overridden the given key (e.g. to set default), then retrieve this entity's key + // TODO If ask for a config value that's not in our configKeys, should we really continue with rest of method and return key.getDefaultValue? + // e.g. SshBasedJavaAppSetup calls setAttribute(JMX_USER), which calls getConfig(JMX_USER) + // but that example doesn't have a default... + ConfigKey<T> ownKey = entity!=null ? (ConfigKey<T>)elvis(entity.getEntityType().getConfigKey(key.getName()), key) : key; + + ConfigInheritance inheritance = key.getInheritance(); + if (inheritance==null) inheritance = ownKey.getInheritance(); + if (inheritance==null) { + // TODO we could warn by introducing a temporary "ALWAYS_BUT_WARNING" instance + inheritance = getDefaultInheritance(); + } + + // TODO We're notifying of config-changed because currently persistence needs to know when the + // attributeWhenReady is complete (so it can persist the result). + // Long term, we'll just persist tasks properly so the call to onConfigChanged will go! + + // Don't use groovy truth: if the set value is e.g. 0, then would ignore set value and return default! + if (ownKey instanceof ConfigKeySelfExtracting) { + Object rawval = ownConfig.get(key); + T result = null; + boolean complete = false; + if (((ConfigKeySelfExtracting<T>)ownKey).isSet(ownConfig)) { + ExecutionContext exec = entity.getExecutionContext(); + result = ((ConfigKeySelfExtracting<T>)ownKey).extractValue(ownConfig, exec); + complete = true; + } else if (isInherited(ownKey, inheritance) && + ((ConfigKeySelfExtracting<T>)ownKey).isSet(inheritedConfig)) { + ExecutionContext exec = entity.getExecutionContext(); + result = ((ConfigKeySelfExtracting<T>)ownKey).extractValue(inheritedConfig, exec); + complete = true; + } else if (localConfigBag.containsKey(ownKey)) { + // TODO configBag.get doesn't handle tasks/attributeWhenReady - it only uses TypeCoercions + result = localConfigBag.get(ownKey); + complete = true; + } else if (isInherited(ownKey, inheritance) && + inheritedConfigBag.containsKey(ownKey)) { + result = inheritedConfigBag.get(ownKey); + complete = true; + } + + if (rawval instanceof Task) { + entity.getManagementSupport().getEntityChangeListener().onConfigChanged(key); + } + if (complete) { + return result; + } + } else { + LOG.warn("Config key {} of {} is not a ConfigKeySelfExtracting; cannot retrieve value; returning default", ownKey, this); + } + return TypeCoercions.coerce((defaultValue != null) ? defaultValue : ownKey.getDefaultValue(), key.getTypeToken()); + } + + private <T> boolean isInherited(ConfigKey<T> key) { + return isInherited(key, key.getInheritance()); + } + private <T> boolean isInherited(ConfigKey<T> key, ConfigInheritance inheritance) { + if (inheritance==null) inheritance = getDefaultInheritance(); + return inheritance.isInherited(key, entity.getParent(), entity); + } + private ConfigInheritance getDefaultInheritance() { + return ConfigInheritance.ALWAYS; + } + + @Override + public Maybe<Object> getConfigRaw(ConfigKey<?> key, boolean includeInherited) { + if (ownConfig.containsKey(key)) return Maybe.of(ownConfig.get(key)); + if (includeInherited && inheritedConfig.containsKey(key)) return Maybe.of(inheritedConfig.get(key)); + return Maybe.absent(); + } + + /** an immutable copy of the config visible at this entity, local and inherited (preferring local) */ + public Map<ConfigKey<?>,Object> getAllConfig() { + Map<ConfigKey<?>,Object> result = new LinkedHashMap<ConfigKey<?>,Object>(inheritedConfig.size()+ownConfig.size()); + result.putAll(inheritedConfig); + result.putAll(ownConfig); + return Collections.unmodifiableMap(result); + } + + /** an immutable copy of the config defined at this entity, ie not inherited */ + public Map<ConfigKey<?>,Object> getLocalConfig() { + Map<ConfigKey<?>,Object> result = new LinkedHashMap<ConfigKey<?>,Object>(ownConfig.size()); + result.putAll(ownConfig); + return Collections.unmodifiableMap(result); + } + + /** Creates an immutable copy of the config visible at this entity, local and inherited (preferring local), including those that did not match config keys */ + public ConfigBag getAllConfigBag() { + return ConfigBag.newInstanceCopying(localConfigBag) + .putAll(ownConfig) + .putIfAbsent(inheritedConfig) + .putIfAbsent(inheritedConfigBag) + .seal(); + } + + /** Creates an immutable copy of the config defined at this entity, ie not inherited, including those that did not match config keys */ + public ConfigBag getLocalConfigBag() { + return ConfigBag.newInstanceCopying(localConfigBag) + .putAll(ownConfig) + .seal(); + } + + @SuppressWarnings("unchecked") + public Object setConfig(ConfigKey<?> key, Object v) { + Object val = coerceConfigVal(key, v); + Object oldVal; + if (key instanceof StructuredConfigKey) { + oldVal = ((StructuredConfigKey)key).applyValueToMap(val, ownConfig); + // TODO ConfigBag does not handle structured config keys; quick fix is to remove (and should also remove any subkeys; + // as it stands if someone set string a.b.c in the config bag then removed structured key a.b, then got a.b.c they'd get a vale); + // long term fix is to support structured config keys in ConfigBag, at which point i think we could remove ownConfig altogether + localConfigBag.remove(key); + } else { + oldVal = ownConfig.put(key, val); + localConfigBag.put((ConfigKey<Object>)key, v); + } + entity.config().refreshInheritedConfigOfChildren(); + return oldVal; + } + + public void setLocalConfig(Map<ConfigKey<?>, ?> vals) { + ownConfig.clear(); + localConfigBag.clear(); + ownConfig.putAll(vals); + localConfigBag.putAll(vals); + } + + public void setInheritedConfig(Map<ConfigKey<?>, ?> valsO, ConfigBag configBagVals) { + Map<ConfigKey<?>, ?> vals = filterUninheritable(valsO); + + inheritedConfig.clear(); + inheritedConfig.putAll(vals); + + // The configBagVals contains all inherited, including strings that did not match a config key on the parent. + // They might match a config-key on this entity though, so need to check that: + // - if it matches one of our keys, set it in inheritedConfig + // - otherwise add it to our inheritedConfigBag + Set<String> valKeyNames = Sets.newLinkedHashSet(); + for (ConfigKey<?> key : vals.keySet()) { + valKeyNames.add(key.getName()); + } + Map<String,Object> valsUnmatched = MutableMap.<String,Object>builder() + .putAll(configBagVals.getAllConfig()) + .removeAll(valKeyNames) + .build(); + inheritedConfigBag.clear(); + Map<ConfigKey<?>, SetFromFlag> annotatedConfigKeys = FlagUtils.getAnnotatedConfigKeys(entity.getClass()); + Map<String, ConfigKey<?>> renamedConfigKeys = Maps.newLinkedHashMap(); + for (Map.Entry<ConfigKey<?>, SetFromFlag> entry: annotatedConfigKeys.entrySet()) { + String rename = entry.getValue().value(); + if (rename != null) { + renamedConfigKeys.put(rename, entry.getKey()); + } + } + for (Map.Entry<String,Object> entry : valsUnmatched.entrySet()) { + String name = entry.getKey(); + Object value = entry.getValue(); + ConfigKey<?> key = renamedConfigKeys.get(name); + if (key == null) key = entity.getEntityType().getConfigKey(name); + if (key != null) { + if (!isInherited(key)) { + // no-op + } else if (inheritedConfig.containsKey(key)) { + LOG.warn("Entity "+entity+" inherited duplicate config for key "+key+", via explicit config and string name "+name+"; using value of key"); + } else { + inheritedConfig.put(key, value); + } + } else { + // a config bag has discarded the keys, so we must assume default inheritance for things given that way + // unless we can infer a key; not a big deal, as we should have the key in inheritedConfig for everything + // which originated with a key ... but still, it would be nice to clean up the use of config bag! + inheritedConfigBag.putStringKey(name, value); + } + } + } + + private Map<ConfigKey<?>, ?> filterUninheritable(Map<ConfigKey<?>, ?> vals) { + Map<ConfigKey<?>, Object> result = Maps.newLinkedHashMap(); + for (Map.Entry<ConfigKey<?>, ?> entry : vals.entrySet()) { + if (isInherited(entry.getKey())) { + result.put(entry.getKey(), entry.getValue()); + } + } + return result; + } + + public void addToLocalBag(Map<String,?> vals) { + localConfigBag.putAll(vals); + // quick fix for problem that ownConfig can get out of synch + ownConfig.putAll(localConfigBag.getAllConfigAsConfigKeyMap()); + } + + public void removeFromLocalBag(String key) { + localConfigBag.remove(key); + ownConfig.remove(key); + } + + public void clearInheritedConfig() { + inheritedConfig.clear(); + inheritedConfigBag.clear(); + } + + @Override + public EntityConfigMap submap(Predicate<ConfigKey<?>> filter) { + EntityConfigMap m = new EntityConfigMap(entity, Maps.<ConfigKey<?>, Object>newLinkedHashMap()); + for (Map.Entry<ConfigKey<?>,Object> entry: inheritedConfig.entrySet()) + if (filter.apply(entry.getKey())) + m.inheritedConfig.put(entry.getKey(), entry.getValue()); - for (Map.Entry<ConfigKey<?>,Object> entry: ownConfig.entrySet()) - if (filter.apply(entry.getKey())) - m.ownConfig.put(entry.getKey(), entry.getValue()); ++ synchronized (ownConfig) { ++ for (Map.Entry<ConfigKey<?>,Object> entry: ownConfig.entrySet()) ++ if (filter.apply(entry.getKey())) ++ m.ownConfig.put(entry.getKey(), entry.getValue()); ++ } + return m; + } + + @Override + public String toString() { - return super.toString()+"[own="+Sanitizer.sanitize(ownConfig)+"; inherited="+Sanitizer.sanitize(inheritedConfig)+"]"; ++ Map<ConfigKey<?>, Object> sanitizeConfig; ++ synchronized (ownConfig) { ++ sanitizeConfig = Sanitizer.sanitize(ownConfig); ++ } ++ return super.toString()+"[own="+sanitizeConfig+"; inherited="+Sanitizer.sanitize(inheritedConfig)+"]"; + } + + } http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/feed/FeedConfig.java ---------------------------------------------------------------------- diff --cc brooklyn-server/core/src/main/java/org/apache/brooklyn/core/feed/FeedConfig.java index 0000000,4d06680..b9662ef mode 000000,100644..100644 --- a/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/feed/FeedConfig.java +++ b/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/feed/FeedConfig.java @@@ -1,0 -1,297 +1,307 @@@ + /* + * 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.feed; + + import static com.google.common.base.Preconditions.checkNotNull; + + import org.apache.brooklyn.api.sensor.AttributeSensor; + import org.apache.brooklyn.core.entity.Entities; + import org.apache.brooklyn.core.sensor.Sensors; + import org.apache.brooklyn.feed.http.HttpPollConfig; + import org.apache.brooklyn.util.collections.MutableList; + import org.apache.brooklyn.util.guava.Functionals; + import org.apache.brooklyn.util.javalang.JavaClassNames; + import org.apache.brooklyn.util.text.Strings; + + import com.google.common.base.Function; + import com.google.common.base.Functions; + import com.google.common.base.Objects; + import com.google.common.base.Predicate; + + /** + * Configuration for a poll, or a subscription etc, that is being added to a feed. + * + * @author aled + */ + public class FeedConfig<V, T, F extends FeedConfig<V, T, F>> { + + /** The onSuccess or onError functions can return this value to indicate that the sensor should not change. + * @deprecated since 0.7.0 use UNCHANGED */ + public static final Object UNSET = Entities.UNCHANGED; + /** The onSuccess or onError functions can return this value to indicate that the sensor should not change. */ + public static final Object UNCHANGED = Entities.UNCHANGED; + /** The onSuccess or onError functions can return this value to indicate that the sensor value should be removed + * (cf 'null', but useful in dynamic situations) */ + public static final Object REMOVE = Entities.REMOVE; + + /** Indicates that no sensor is being used here. This sensor is suppressed, + * but is useful where you want to use the feeds with custom success/exception/failure functions + * which directly set multiple sensors, e.g. dynamically based on the poll response. + * <p> + * See {@link HttpPollConfig#forMultiple()} and its usages. + * (It can work for any poll config, but conveniences have not been supplied for others.) */ + public static final AttributeSensor<Void> NO_SENSOR = Sensors.newSensor(Void.class, "brooklyn.no.sensor"); + + private final AttributeSensor<T> sensor; + private Function<? super V, T> onsuccess; + private Function<? super V, T> onfailure; + private Function<? super Exception, T> onexception; + private Predicate<? super V> checkSuccess; + private boolean suppressDuplicates; + private boolean enabled = true; + + public FeedConfig(AttributeSensor<T> sensor) { + this.sensor = checkNotNull(sensor, "sensor"); + } + + public FeedConfig(FeedConfig<V, T, F> other) { + this.sensor = other.sensor; + this.onsuccess = other.onsuccess; + this.onfailure = other.onfailure; + this.onexception = other.onexception; + this.checkSuccess = other.checkSuccess; + this.suppressDuplicates = other.suppressDuplicates; + this.enabled = other.enabled; + } + + @SuppressWarnings("unchecked") + protected F self() { + return (F) this; + } + + public AttributeSensor<T> getSensor() { + return sensor; + } + + public Predicate<? super V> getCheckSuccess() { + return checkSuccess; + } + + public Function<? super V, T> getOnSuccess() { + return onsuccess; + } + + public Function<? super V, T> getOnFailure() { + return onfailure; + } + + public Function<? super Exception, T> getOnException() { + return onexception; + } + + public boolean getSupressDuplicates() { + return suppressDuplicates; + } + + public boolean isEnabled() { + return enabled; + } + + /** sets the predicate used to check whether a feed run is successful */ + public F checkSuccess(Predicate<? super V> val) { + this.checkSuccess = checkNotNull(val, "checkSuccess"); + return self(); + } + /** as {@link #checkSuccess(Predicate)} */ + public F checkSuccess(final Function<? super V,Boolean> val) { + return checkSuccess(Functionals.predicate(val)); + } ++ + @SuppressWarnings("unused") + /** @deprecated since 0.7.0, kept for rebind */ @Deprecated + private F checkSuccessLegacy(final Function<? super V,Boolean> val) { + return checkSuccess(new Predicate<V>() { + @Override + public boolean apply(V input) { + return val.apply(input); + } + }); + } + + public F onSuccess(Function<? super V,T> val) { + this.onsuccess = checkNotNull(val, "onSuccess"); + return self(); + } - ++ + public F setOnSuccess(T val) { + return onSuccess(Functions.constant(val)); + } - - /** a failure is when the connection is fine (no exception) but the other end returns a result object V - * which the feed can tell indicates a failure (e.g. HTTP code 404) */ ++ ++ /** ++ * A failure is when the connection is fine (no exception) but the other end returns a result object V ++ * which the feed can tell indicates a failure (e.g. HTTP code 404) ++ */ + public F onFailure(Function<? super V,T> val) { + this.onfailure = checkNotNull(val, "onFailure"); + return self(); + } + ++ /** @see #onFailure(Function) */ + public F setOnFailure(T val) { + return onFailure(Functions.constant(val)); + } + - /** registers a callback to be used {@link #onSuccess(Function)} and {@link #onFailure(Function)}, - * i.e. whenever a result comes back, but not in case of exceptions being thrown (ie problems communicating) */ ++ /** ++ * Registers a callback to be used by {@link #onSuccess(Function)} and {@link #onFailure(Function)}, ++ * i.e. whenever a result comes back, but not in case of exceptions being thrown (ie problems communicating) ++ */ + public F onResult(Function<? super V, T> val) { + onSuccess(val); + return onFailure(val); + } + ++ /** @see #onResult(Function) */ + public F setOnResult(T val) { + return onResult(Functions.constant(val)); + } + - /** an exception is when there is an error in the communication */ ++ /** ++ * An exception is when there is an error in the communication ++ */ + public F onException(Function<? super Exception,T> val) { + this.onexception = checkNotNull(val, "onException"); + return self(); + } - ++ ++ /** @see #onException(Function) */ + public F setOnException(T val) { + return onException(Functions.constant(val)); + } + - /** convenience for indicating a behaviour to occur for both - * {@link #onException(Function)} - * (error connecting) and - * {@link #onFailure(Function)} - * (successful communication but failure report from remote end) */ ++ /** ++ * A convenience for indicating a behaviour to occur for both {@link #onException(Function)} ++ * (error connecting) and {@link #onFailure(Function)} (successful communication but failure ++ * report from remote end) ++ */ + public F onFailureOrException(Function<Object,T> val) { + onFailure(val); + return onException(val); + } - ++ ++ /** @see #onFailureOrException(Function) */ + public F setOnFailureOrException(T val) { + return onFailureOrException(Functions.constant(val)); + } + + public F suppressDuplicates(boolean val) { + suppressDuplicates = val; + return self(); + } - ++ + /** + * Whether this feed is enabled (defaulting to true). + */ + public F enabled(boolean val) { + enabled = val; + return self(); + } - ++ + public boolean hasSuccessHandler() { + return this.onsuccess != null; + } + + public boolean hasFailureHandler() { + return this.onfailure != null; + } + + public boolean hasExceptionHandler() { + return this.onexception != null; + } + + public boolean hasCheckSuccessHandler() { + return this.checkSuccess != null; + } - + + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + result.append(toStringBaseName()); + result.append("["); + boolean contents = false; + Object source = toStringPollSource(); + AttributeSensor<T> s = getSensor(); + if (Strings.isNonBlank(Strings.toString(source))) { + result.append(Strings.toUniqueString(source, 40)); + if (s!=null) { + result.append("->"); + result.append(s.getName()); + } + contents = true; + } else if (s!=null) { + result.append(s.getName()); + contents = true; + } + MutableList<Object> fields = toStringOtherFields(); + if (fields!=null) { + for (Object field: fields) { + if (Strings.isNonBlank(Strings.toString(field))) { + if (contents) result.append(";"); + contents = true; + result.append(field); + } + } + } + result.append("]"); + return result.toString(); + } + + /** can be overridden to supply a simpler base name than the class name */ + protected String toStringBaseName() { + return JavaClassNames.simpleClassName(this); + } + /** can be overridden to supply add'l info for the {@link #toString()}; subclasses can add to the returned value */ + protected MutableList<Object> toStringOtherFields() { + return MutableList.<Object>of(); + } + /** can be overridden to supply add'l info for the {@link #toString()}, placed before the sensor with -> */ + protected Object toStringPollSource() { + return null; + } + /** all configs should supply a unique tag element, inserted into the feed */ + protected String getUniqueTag() { + return toString(); + } + + /** returns fields which should be used for equality, including by default {@link #toStringOtherFields()} and {@link #toStringPollSource()}; + * subclasses can add to the returned value */ + protected MutableList<Object> equalsFields() { + MutableList<Object> result = MutableList.of().appendIfNotNull(getSensor()).appendIfNotNull(toStringPollSource()); + for (Object field: toStringOtherFields()) result.appendIfNotNull(field); + return result; + } + + @Override + public int hashCode() { + int hc = super.hashCode(); + for (Object f: equalsFields()) + hc = Objects.hashCode(hc, f); + return hc; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (!super.equals(obj)) return false; + PollConfig<?,?,?> other = (PollConfig<?,?,?>) obj; + if (!Objects.equal(getUniqueTag(), other.getUniqueTag())) return false; + if (!Objects.equal(equalsFields(), other.equalsFields())) return false; + return true; + } + + } http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/internal/BrooklynProperties.java ---------------------------------------------------------------------- diff --cc brooklyn-server/core/src/main/java/org/apache/brooklyn/core/internal/BrooklynProperties.java index 0000000,c1ffb42..75b7b34 mode 000000,100644..100644 --- a/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/internal/BrooklynProperties.java +++ b/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/internal/BrooklynProperties.java @@@ -1,0 -1,481 +1,305 @@@ + /* + * 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.internal; + + import static com.google.common.base.Preconditions.checkNotNull; -import groovy.lang.Closure; + + import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; + import java.io.InputStream; + import java.net.URL; -import java.util.Arrays; -import java.util.LinkedHashMap; + import java.util.Map; -import java.util.NoSuchElementException; -import java.util.Properties; + + import org.apache.brooklyn.config.ConfigKey; + import org.apache.brooklyn.config.ConfigKey.HasConfigKey; + import org.apache.brooklyn.config.StringConfigMap; -import org.apache.brooklyn.core.config.BasicConfigKey; -import org.apache.brooklyn.util.collections.MutableMap; + import org.apache.brooklyn.util.core.ResourceUtils; + import org.apache.brooklyn.util.core.config.ConfigBag; -import org.apache.brooklyn.util.core.flags.TypeCoercions; + import org.apache.brooklyn.util.guava.Maybe; + import org.apache.brooklyn.util.os.Os; -import org.apache.brooklyn.util.text.StringFunctions; -import org.apache.brooklyn.util.text.Strings; + import org.slf4j.Logger; + import org.slf4j.LoggerFactory; + + import com.google.common.annotations.Beta; -import com.google.common.base.CharMatcher; + import com.google.common.base.Objects; + import com.google.common.base.Predicate; -import com.google.common.base.Throwables; -import com.google.common.collect.Maps; + -/** utils for accessing command-line and system-env properties; ++/** ++ * Utils for accessing command-line and system-env properties; + * doesn't resolve anything (unless an execution context is supplied) + * and treats ConfigKeys as of type object when in doubt, + * or string when that is likely wanted (e.g. {@link #getFirst(Map, String...)} + * <p> - * TODO methods in this class are not thread safe. - * intention is that they are set during startup and not modified thereafter. */ ++ * Intention for normal use is that they are set during startup and not modified ++ * thereafter. ++ */ + @SuppressWarnings("rawtypes") -public class BrooklynProperties extends LinkedHashMap implements StringConfigMap { - - private static final long serialVersionUID = -945875483083108978L; - private static final Logger LOG = LoggerFactory.getLogger(BrooklynProperties.class); ++public interface BrooklynProperties extends Map, StringConfigMap { + + public static class Factory { ++ private static final Logger LOG = LoggerFactory.getLogger(BrooklynProperties.Factory.class); ++ + /** creates a new empty {@link BrooklynProperties} */ + public static BrooklynProperties newEmpty() { - return new BrooklynProperties(); ++ return new BrooklynPropertiesImpl(); + } + + /** creates a new {@link BrooklynProperties} with contents loaded + * from the usual places, including *.properties files and environment variables */ + public static BrooklynProperties newDefault() { + return new Builder(true).build(); + } + + public static Builder builderDefault() { + return new Builder(true); + } + + public static Builder builderEmpty() { + return new Builder(false); + } + + public static class Builder { + private String defaultLocationMetadataUrl; + private String globalLocationMetadataFile = null; + private String globalPropertiesFile = null; + private String localPropertiesFile = null; + private BrooklynProperties originalProperties = null; + + /** @deprecated since 0.7.0 use static methods in {@link Factory} to create */ + public Builder() { + this(true); + } + + private Builder(boolean setGlobalFileDefaults) { + resetDefaultLocationMetadataUrl(); + if (setGlobalFileDefaults) { + resetGlobalFiles(); + } + } + + public Builder resetDefaultLocationMetadataUrl() { + defaultLocationMetadataUrl = "classpath://brooklyn/location-metadata.properties"; + return this; + } + public Builder resetGlobalFiles() { + defaultLocationMetadataUrl = "classpath://brooklyn/location-metadata.properties"; + globalLocationMetadataFile = Os.mergePaths(Os.home(), ".brooklyn", "location-metadata.properties"); + globalPropertiesFile = Os.mergePaths(Os.home(), ".brooklyn", "brooklyn.properties"); + return this; + } + + /** + * Creates a Builder that when built, will return the BrooklynProperties passed to this constructor + */ + private Builder(BrooklynProperties originalProperties) { - this.originalProperties = new BrooklynProperties().addFromMap(originalProperties); ++ this.originalProperties = new BrooklynPropertiesImpl().addFromMap(originalProperties); + } + + /** + * The URL of a default location-metadata.properties (for meta-data about different locations, such as iso3166 and global lat/lon). + * Defaults to classpath://brooklyn/location-metadata.properties + */ + public Builder defaultLocationMetadataUrl(String val) { + defaultLocationMetadataUrl = checkNotNull(val, "file"); + return this; + } + + /** + * The URL of a location-metadata.properties file that appends to and overwrites values in the locationMetadataUrl. + * Defaults to ~/.brooklyn/location-metadata.properties + */ + public Builder globalLocationMetadataFile(String val) { + globalLocationMetadataFile = checkNotNull(val, "file"); + return this; + } + + /** + * The URL of a shared brooklyn.properties file. Defaults to ~/.brooklyn/brooklyn.properties. + * Can be null to disable. + */ + public Builder globalPropertiesFile(String val) { + globalPropertiesFile = val; + return this; + } + + @Beta + public boolean hasDelegateOriginalProperties() { + return this.originalProperties==null; + } + + /** + * The URL of a brooklyn.properties file specific to this launch. Appends to and overwrites values in globalPropertiesFile. + */ + public Builder localPropertiesFile(String val) { + localPropertiesFile = val; + return this; + } + + public BrooklynProperties build() { + if (originalProperties != null) - return new BrooklynProperties().addFromMap(originalProperties); ++ return new BrooklynPropertiesImpl().addFromMap(originalProperties); + - BrooklynProperties properties = new BrooklynProperties(); ++ BrooklynProperties properties = new BrooklynPropertiesImpl(); + + // TODO Could also read from http://brooklyn.io, for up-to-date values? + // But might that make unit tests run very badly when developer is offline? + addPropertiesFromUrl(properties, defaultLocationMetadataUrl, false); + + addPropertiesFromFile(properties, globalLocationMetadataFile); + addPropertiesFromFile(properties, globalPropertiesFile); + addPropertiesFromFile(properties, localPropertiesFile); + + properties.addEnvironmentVars(); + properties.addSystemProperties(); + + return properties; + } + + public static Builder fromProperties(BrooklynProperties brooklynProperties) { + return new Builder(brooklynProperties); + } + + @Override + public String toString() { + return Objects.toStringHelper(this) + .omitNullValues() + .add("originalProperties", originalProperties) + .add("defaultLocationMetadataUrl", defaultLocationMetadataUrl) + .add("globalLocationMetadataUrl", globalLocationMetadataFile) + .add("globalPropertiesFile", globalPropertiesFile) + .add("localPropertiesFile", localPropertiesFile) + .toString(); + } + } + + private static void addPropertiesFromUrl(BrooklynProperties p, String url, boolean warnIfNotFound) { + if (url==null) return; + + try { + p.addFrom(ResourceUtils.create(BrooklynProperties.class).getResourceFromUrl(url)); + } catch (Exception e) { + if (warnIfNotFound) + LOG.warn("Could not load {}; continuing", url); + if (LOG.isTraceEnabled()) LOG.trace("Could not load "+url+"; continuing", e); + } + } + + private static void addPropertiesFromFile(BrooklynProperties p, String file) { + if (file==null) return; + + String fileTidied = Os.tidyPath(file); + File f = new File(fileTidied); + + if (f.exists()) { + p.addFrom(f); + } + } + } + - protected BrooklynProperties() { - } ++ public BrooklynProperties addEnvironmentVars(); + - public BrooklynProperties addEnvironmentVars() { - addFrom(System.getenv()); - return this; - } - - public BrooklynProperties addSystemProperties() { - addFrom(System.getProperties()); - return this; - } ++ public BrooklynProperties addSystemProperties(); + - public BrooklynProperties addFrom(ConfigBag cfg) { - addFrom(cfg.getAllConfig()); - return this; - } ++ public BrooklynProperties addFrom(ConfigBag cfg); + - @SuppressWarnings("unchecked") - public BrooklynProperties addFrom(Map map) { - putAll(Maps.transformValues(map, StringFunctions.trim())); - return this; - } ++ public BrooklynProperties addFrom(Map map); + - public BrooklynProperties addFrom(InputStream i) { - // Ugly way to load them in order, but Properties is a Hashtable so loses order otherwise. - @SuppressWarnings({ "serial" }) - Properties p = new Properties() { - @Override - public synchronized Object put(Object key, Object value) { - // Trim the string values to remove leading and trailing spaces - String s = (String) value; - if (Strings.isBlank(s)) { - s = Strings.EMPTY; - } else { - s = CharMatcher.BREAKING_WHITESPACE.trimFrom(s); - } - return BrooklynProperties.this.put(key, s); - } - }; - try { - p.load(i); - } catch (IOException e) { - throw Throwables.propagate(e); - } - return this; - } ++ public BrooklynProperties addFrom(InputStream i); + - public BrooklynProperties addFrom(File f) { - if (!f.exists()) { - LOG.warn("Unable to find file '"+f.getAbsolutePath()+"' when loading properties; ignoring"); - return this; - } else { - try { - return addFrom(new FileInputStream(f)); - } catch (FileNotFoundException e) { - throw Throwables.propagate(e); - } - } - } - public BrooklynProperties addFrom(URL u) { - try { - return addFrom(u.openStream()); - } catch (IOException e) { - throw new RuntimeException("Error reading properties from "+u+": "+e, e); - } - } ++ public BrooklynProperties addFrom(File f); ++ ++ public BrooklynProperties addFrom(URL u); ++ + /** + * @see ResourceUtils#getResourceFromUrl(String) + * + * of the form form file:///home/... or http:// or classpath://xx ; + * for convenience if not starting with xxx: it is treated as a classpath reference or a file; + * throws if not found (but does nothing if argument is null) + */ - public BrooklynProperties addFromUrl(String url) { - try { - if (url==null) return this; - return addFrom(ResourceUtils.create(this).getResourceFromUrl(url)); - } catch (Exception e) { - throw new RuntimeException("Error reading properties from "+url+": "+e, e); - } - } ++ public BrooklynProperties addFromUrl(String url); + + /** expects a property already set in scope, whose value is acceptable to {@link #addFromUrl(String)}; + * if property not set, does nothing */ - public BrooklynProperties addFromUrlProperty(String urlProperty) { - String url = (String) get(urlProperty); - if (url==null) addFromUrl(url); - return this; - } ++ public BrooklynProperties addFromUrlProperty(String urlProperty); + + /** + * adds the indicated properties + */ - public BrooklynProperties addFromMap(Map properties) { - putAll(properties); - return this; - } ++ public BrooklynProperties addFromMap(Map properties); + + /** inserts the value under the given key, if it was not present */ - public boolean putIfAbsent(String key, Object value) { - if (containsKey(key)) return false; - put(key, value); - return true; - } ++ public boolean putIfAbsent(String key, Object value); + + /** @deprecated attempts to call get with this syntax are probably mistakes; get(key, defaultValue) is fine but + * Map is unlikely the key, much more likely they meant getFirst(flags, key). + */ + @Deprecated - public String get(Map flags, String key) { - LOG.warn("Discouraged use of 'BrooklynProperties.get(Map,String)' (ambiguous); use getFirst(Map,String) or get(String) -- assuming the former"); - LOG.debug("Trace for discouraged use of 'BrooklynProperties.get(Map,String)'", - new Throwable("Arguments: "+flags+" "+key)); - return getFirst(flags, key); - } ++ public String get(Map flags, String key); + + /** returns the value of the first key which is defined + * <p> + * takes the following flags: + * 'warnIfNone', 'failIfNone' (both taking a boolean (to use default message) or a string (which is the message)); + * and 'defaultIfNone' (a default value to return if there is no such property); defaults to no warning and null response */ + @Override - public String getFirst(String ...keys) { - return getFirst(MutableMap.of(), keys); - } - @Override - public String getFirst(Map flags, String ...keys) { - for (String k: keys) { - if (k!=null && containsKey(k)) return (String) get(k); - } - if (flags.get("warnIfNone")!=null && !Boolean.FALSE.equals(flags.get("warnIfNone"))) { - if (Boolean.TRUE.equals(flags.get("warnIfNone"))) - LOG.warn("Unable to find Brooklyn property "+keys); - else - LOG.warn(""+flags.get("warnIfNone")); - } - if (flags.get("failIfNone")!=null && !Boolean.FALSE.equals(flags.get("failIfNone"))) { - Object f = flags.get("failIfNone"); - if (f instanceof Closure) - ((Closure)f).call((Object[])keys); - if (Boolean.TRUE.equals(f)) - throw new NoSuchElementException("Brooklyn unable to find mandatory property "+keys[0]+ - (keys.length>1 ? " (or "+(keys.length-1)+" other possible names, full list is "+Arrays.asList(keys)+")" : "") ); - else - throw new NoSuchElementException(""+f); - } - if (flags.get("defaultIfNone")!=null) { - return (String) flags.get("defaultIfNone"); - } - return null; - } ++ public String getFirst(String ...keys); + + @Override - public String toString() { - return "BrooklynProperties["+size()+"]"; - } ++ public String getFirst(Map flags, String ...keys); + + /** like normal map.put, except config keys are dereferenced on the way in */ - @SuppressWarnings("unchecked") - public Object put(Object key, Object value) { - if (key instanceof HasConfigKey) key = ((HasConfigKey)key).getConfigKey().getName(); - if (key instanceof ConfigKey) key = ((ConfigKey)key).getName(); - return super.put(key, value); - } ++ public Object put(Object key, Object value); + + /** like normal map.putAll, except config keys are dereferenced on the way in */ + @Override - public void putAll(Map vals) { - for (Map.Entry<?,?> entry : ((Map<?,?>)vals).entrySet()) { - put(entry.getKey(), entry.getValue()); - } - } ++ public void putAll(Map vals); + - @SuppressWarnings("unchecked") - public <T> Object put(HasConfigKey<T> key, T value) { - return super.put(key.getConfigKey().getName(), value); - } ++ public <T> Object put(HasConfigKey<T> key, T value); + - @SuppressWarnings("unchecked") - public <T> Object put(ConfigKey<T> key, T value) { - return super.put(key.getName(), value); - } ++ public <T> Object put(ConfigKey<T> key, T value); + - public <T> boolean putIfAbsent(ConfigKey<T> key, T value) { - return putIfAbsent(key.getName(), value); - } ++ public <T> boolean putIfAbsent(ConfigKey<T> key, T value); + + @Override - public <T> T getConfig(ConfigKey<T> key) { - return getConfig(key, null); - } ++ public <T> T getConfig(ConfigKey<T> key); + + @Override - public <T> T getConfig(HasConfigKey<T> key) { - return getConfig(key.getConfigKey(), null); - } ++ public <T> T getConfig(HasConfigKey<T> key); + + @Override - public <T> T getConfig(HasConfigKey<T> key, T defaultValue) { - return getConfig(key.getConfigKey(), defaultValue); - } ++ public <T> T getConfig(HasConfigKey<T> key, T defaultValue); + + @Override - public <T> T getConfig(ConfigKey<T> key, T defaultValue) { - // TODO does not support MapConfigKey etc where entries use subkey notation; for now, access using submap - if (!containsKey(key.getName())) { - if (defaultValue!=null) return defaultValue; - return key.getDefaultValue(); - } - Object value = get(key.getName()); - if (value==null) return null; - // no evaluation / key extraction here - return TypeCoercions.coerce(value, key.getTypeToken()); - } ++ public <T> T getConfig(ConfigKey<T> key, T defaultValue); + + @Override - public Object getRawConfig(ConfigKey<?> key) { - return get(key.getName()); - } ++ public Object getRawConfig(ConfigKey<?> key); + + @Override - public Maybe<Object> getConfigRaw(ConfigKey<?> key, boolean includeInherited) { - if (containsKey(key.getName())) return Maybe.of(get(key.getName())); - return Maybe.absent(); - } ++ public Maybe<Object> getConfigRaw(ConfigKey<?> key, boolean includeInherited); + + @Override - public Map<ConfigKey<?>, Object> getAllConfig() { - Map<ConfigKey<?>, Object> result = new LinkedHashMap<ConfigKey<?>, Object>(); - for (Object entry: entrySet()) - result.put(new BasicConfigKey<Object>(Object.class, ""+((Map.Entry)entry).getKey()), ((Map.Entry)entry).getValue()); - return result; - } ++ public Map<ConfigKey<?>, Object> getAllConfig(); + + @Override - public BrooklynProperties submap(Predicate<ConfigKey<?>> filter) { - BrooklynProperties result = Factory.newEmpty(); - for (Object entry: entrySet()) { - ConfigKey<?> k = new BasicConfigKey<Object>(Object.class, ""+((Map.Entry)entry).getKey()); - if (filter.apply(k)) - result.put(((Map.Entry)entry).getKey(), ((Map.Entry)entry).getValue()); - } - return result; - } ++ public BrooklynProperties submap(Predicate<ConfigKey<?>> filter); + - @SuppressWarnings("unchecked") + @Override - public Map<String, Object> asMapWithStringKeys() { - return this; - } - ++ public Map<String, Object> asMapWithStringKeys(); + }
