http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/main/java/org/apache/brooklyn/core/entity/EntityFunctions.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/entity/EntityFunctions.java b/core/src/main/java/org/apache/brooklyn/core/entity/EntityFunctions.java new file mode 100644 index 0000000..7ec70a1 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/entity/EntityFunctions.java @@ -0,0 +1,153 @@ +/* + * 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 { + + public static <T> Function<Entity, T> attribute(final AttributeSensor<T> attribute) { + class GetEntityAttributeFunction implements Function<Entity, T> { + @Override public T apply(Entity input) { + return (input == null) ? null : input.getAttribute(attribute); + } + }; + return new GetEntityAttributeFunction(); + } + + public static <T> Function<Entity, T> config(final ConfigKey<T> key) { + class GetEntityConfigFunction implements Function<Entity, T> { + @Override public T apply(Entity input) { + return (input == null) ? null : input.getConfig(key); + } + }; + return new GetEntityConfigFunction(); + } + + public static Function<Entity, String> displayName() { + class GetEntityDisplayName implements Function<Entity, String> { + @Override public String apply(Entity input) { + return (input == null) ? null : input.getDisplayName(); + } + }; + return new GetEntityDisplayName(); + } + + public static Function<Identifiable, String> id() { + class GetIdFunction implements Function<Identifiable, String> { + @Override public String apply(Identifiable input) { + return (input == null) ? null : input.getId(); + } + }; + return new GetIdFunction(); + } + + /** 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) { + checkNotNull(values, "values"); + 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).setAttribute(sensor, value); + } + } + return null; + } + } + return new SettingSensorsConstantFunction(); + } + + /** 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) { + 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(); + } + 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(final ManagementContext mgmt) { + class AppsSupplier implements Supplier<Collection<Application>> { + @Override + public Collection<Application> get() { + return mgmt.getApplications(); + } + } + return new AppsSupplier(); + } + + 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/c27cf1d0/core/src/main/java/org/apache/brooklyn/core/entity/EntityInitializers.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/entity/EntityInitializers.java b/core/src/main/java/org/apache/brooklyn/core/entity/EntityInitializers.java new file mode 100644 index 0000000..a258007 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/entity/EntityInitializers.java @@ -0,0 +1,49 @@ +/* + * 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 java.util.List; + +import org.apache.brooklyn.api.entity.EntityInitializer; +import org.apache.brooklyn.api.entity.EntityLocal; + +import com.google.common.collect.ImmutableList; + +public class EntityInitializers { + + public static class AddTags implements EntityInitializer { + public final List<Object> tags; + + public AddTags(Object... tags) { + this.tags = ImmutableList.copyOf(tags); + } + + @Override + public void apply(EntityLocal entity) { + for (Object tag: tags) + entity.tags().addTag(tag); + } + } + + + public static EntityInitializer addingTags(Object... tags) { + return new AddTags(tags); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/main/java/org/apache/brooklyn/core/entity/EntityInternal.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/entity/EntityInternal.java b/core/src/main/java/org/apache/brooklyn/core/entity/EntityInternal.java new file mode 100644 index 0000000..0147170 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/entity/EntityInternal.java @@ -0,0 +1,201 @@ +/* + * 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 java.util.Collection; +import java.util.Map; + +import org.apache.brooklyn.api.effector.Effector; +import org.apache.brooklyn.api.entity.EntityLocal; +import org.apache.brooklyn.api.location.Location; +import org.apache.brooklyn.api.mgmt.ExecutionContext; +import org.apache.brooklyn.api.mgmt.ManagementContext; +import org.apache.brooklyn.api.mgmt.SubscriptionContext; +import org.apache.brooklyn.api.mgmt.rebind.RebindSupport; +import org.apache.brooklyn.api.mgmt.rebind.Rebindable; +import org.apache.brooklyn.api.mgmt.rebind.mementos.EntityMemento; +import org.apache.brooklyn.api.sensor.AttributeSensor; +import org.apache.brooklyn.api.sensor.Feed; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.core.entity.internal.EntityConfigMap; +import org.apache.brooklyn.core.mgmt.internal.EntityManagementSupport; +import org.apache.brooklyn.core.objs.BrooklynObjectInternal; +import org.apache.brooklyn.util.core.config.ConfigBag; + +import com.google.common.annotations.Beta; + +/** + * Extended Entity interface with additional functionality that is purely-internal (i.e. intended + * for the brooklyn framework only). + */ +@Beta +public interface EntityInternal extends BrooklynObjectInternal, EntityLocal, Rebindable { + + void addLocations(Collection<? extends Location> locations); + + void removeLocations(Collection<? extends Location> locations); + + void clearLocations(); + + /** + * + * Like {@link EntityLocal#setAttribute(AttributeSensor, Object)}, except does not publish an attribute-change event. + */ + <T> T setAttributeWithoutPublishing(AttributeSensor<T> sensor, T val); + + /** + * @deprecated since 0.7.0; instead just use methods on {@link ConfigurationSupportInternal} returned by {@link #config()} + */ + @Deprecated + EntityConfigMap getConfigMap(); + + /** + * @return a read-only copy of all the config key/value pairs on this entity. + * + * @deprecated since 0.7.0; instead just use methods on {@link ConfigurationSupportInternal} returned by {@link #config()}, + * e.g. getBag().getAllConfigAsConfigKeyMap(). + */ + @Deprecated + @Beta + Map<ConfigKey<?>,Object> getAllConfig(); + + /** + * Returns a read-only view of all the config key/value pairs on this entity, backed by a string-based map, + * including config names that did not match anything on this entity. + * + * @deprecated since 0.7.0; use {@link #config()}, such as {@code entity.config().getBag()} + */ + @Deprecated + ConfigBag getAllConfigBag(); + + /** + * Returns a read-only view of the local (i.e. not inherited) config key/value pairs on this entity, + * backed by a string-based map, including config names that did not match anything on this entity. + * + * @deprecated since 0.7.0; use {@link #config()}, such as {@code entity.config().getLocalBag()} + */ + @Deprecated + ConfigBag getLocalConfigBag(); + + @Beta + Map<AttributeSensor, Object> getAllAttributes(); + + @Beta + void removeAttribute(AttributeSensor<?> attribute); + + /** + * + * @deprecated since 0.7.0; use {@link #config()}, such as {@code entity.config().refreshInheritedConfig()} + */ + @Deprecated + void refreshInheritedConfig(); + + /** + * Must be called before the entity is started. + * + * @return this entity (i.e. itself) + */ + @Beta // for internal use only + EntityInternal configure(Map flags); + + /** + * @return Routings for accessing and inspecting the management context of the entity + */ + EntityManagementSupport getManagementSupport(); + + /** + * Should be invoked at end-of-life to clean up the item. + */ + @Beta + void destroy(); + + /** + * Returns the management context for the entity. If the entity is not yet managed, some + * operations on the management context will fail. + * + * Do not cache this object; instead call getManagementContext() each time you need to use it. + */ + ManagementContext getManagementContext(); + + /** + * Returns the task execution context for the entity. If the entity is not yet managed, some + * operations on the management context will fail. + * + * Do not cache this object; instead call getExecutionContext() each time you need to use it. + */ + ExecutionContext getExecutionContext(); + + SubscriptionContext getSubscriptionContext(); + + /** returns the dynamic type corresponding to the type of this entity instance */ + @Beta + EntityDynamicType getMutableEntityType(); + + /** returns the effector registered against a given name */ + @Beta + Effector<?> getEffector(String effectorName); + + FeedSupport feeds(); + + /** + * @since 0.7.0-M2 + * @deprecated since 0.7.0-M2; use {@link #feeds()} + */ + @Deprecated + FeedSupport getFeedSupport(); + + Map<String, String> toMetadataRecord(); + + /** + * Users are strongly discouraged from calling or overriding this method. + * It is for internal calls only, relating to persisting/rebinding entities. + * This method may change (or be removed) in a future release without notice. + */ + @Override + @Beta + RebindSupport<EntityMemento> getRebindSupport(); + + /** + * Can be called to request that the entity be persisted. + * This persistence may happen asynchronously, or may not happen at all if persistence is disabled. + */ + void requestPersist(); + + public interface FeedSupport { + Collection<Feed> getFeeds(); + + /** + * Adds the given feed to this entity. The feed will automatically be re-added on brooklyn restart. + */ + <T extends Feed> T addFeed(T feed); + + /** + * Removes the given feed from this entity. + * @return True if the feed existed at this entity; false otherwise + */ + boolean removeFeed(Feed feed); + + /** + * Removes all feeds from this entity. + * Use with caution as some entities automatically register feeds; this will remove those feeds as well. + * @return True if any feeds existed at this entity; false otherwise + */ + boolean removeAllFeeds(); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/main/java/org/apache/brooklyn/core/entity/EntityPredicates.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/entity/EntityPredicates.java b/core/src/main/java/org/apache/brooklyn/core/entity/EntityPredicates.java new file mode 100644 index 0000000..a618784 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/entity/EntityPredicates.java @@ -0,0 +1,451 @@ +/* + * 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 java.util.Collection; +import java.util.regex.Pattern; + +import javax.annotation.Nullable; + +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.entity.Group; +import org.apache.brooklyn.api.location.Location; +import org.apache.brooklyn.api.sensor.AttributeSensor; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.config.ConfigKey.HasConfigKey; +import org.apache.brooklyn.util.collections.CollectionFunctionals; +import org.apache.brooklyn.util.guava.SerializablePredicate; +import org.apache.brooklyn.util.javalang.Reflections; +import org.apache.brooklyn.util.text.StringPredicates; + +import com.google.common.base.Objects; +import com.google.common.base.Predicate; +import com.google.common.base.Predicates; + +@SuppressWarnings("serial") +public class EntityPredicates { + + public static Predicate<Entity> idEqualTo(final String val) { + return idSatisfies(Predicates.equalTo(val)); + } + + public static Predicate<Entity> idSatisfies(final Predicate<? super String> condition) { + return new IdSatisfies(condition); + } + + protected static class IdSatisfies implements SerializablePredicate<Entity> { + protected final Predicate<? super String> condition; + protected IdSatisfies(Predicate<? super String> condition) { + this.condition = condition; + } + @Override + public boolean apply(@Nullable Entity input) { + return (input != null) && condition.apply(input.getId()); + } + @Override + public String toString() { + return "idSatisfies("+condition+")"; + } + } + + /** @deprecated since 0.7.0 kept only to allow conversion of anonymous inner classes */ + @SuppressWarnings("unused") @Deprecated + private static <T> Predicate<Entity> idEqualToOld(final T val) { + return new SerializablePredicate<Entity>() { + @Override + public boolean apply(@Nullable Entity input) { + return (input != null) && Objects.equal(input.getId(), val); + } + }; + } + + // --------------------------- + + public static Predicate<Entity> displayNameEqualTo(final String val) { + return displayNameSatisfies(Predicates.equalTo(val)); + } + + public static Predicate<Entity> displayNameSatisfies(final Predicate<? super String> condition) { + return new DisplayNameSatisfies(condition); + } + + protected static class DisplayNameSatisfies implements SerializablePredicate<Entity> { + protected final Predicate<? super String> condition; + protected DisplayNameSatisfies(Predicate<? super String> condition) { + this.condition = condition; + } + @Override + public boolean apply(@Nullable Entity input) { + return (input != null) && condition.apply(input.getDisplayName()); + } + @Override + public String toString() { + return "displayNameSatisfies("+condition+")"; + } + } + + /** @deprecated since 0.7.0 kept only to allow conversion of anonymous inner classes */ + @SuppressWarnings("unused") @Deprecated + private static <T> Predicate<Entity> displayNameEqualToOld(final T val) { + return new SerializablePredicate<Entity>() { + @Override + public boolean apply(@Nullable Entity input) { + return (input != null) && Objects.equal(input.getDisplayName(), val); + } + }; + } + + /** @deprecated since 0.7.0 use {@link #displayNameSatisfies(Predicate)} to clarify this is *regex* matching + * (passing {@link StringPredicates#matchesRegex(String)} as the predicate) */ + public static Predicate<Entity> displayNameMatches(final String regex) { + return displayNameSatisfies(StringPredicates.matchesRegex(regex)); + } + + /** @deprecated since 0.7.0 kept only to allow conversion of anonymous inner classes */ + @SuppressWarnings("unused") @Deprecated + private static class DisplayNameMatches implements SerializablePredicate<Entity> { + private final String regex; + DisplayNameMatches(String regex) { + this.regex = regex; + } + @Override + public boolean apply(@Nullable Entity input) { + return (input != null && input.getDisplayName() != null) && input.getDisplayName().matches(regex); + } + @Override + public String toString() { + return "DisplayNameMatches("+regex+")"; + } + }; + + // --------------------------- + + public static Predicate<Entity> applicationIdEqualTo(final String val) { + return applicationIdSatisfies(Predicates.equalTo(val)); + } + + public static Predicate<Entity> applicationIdSatisfies(final Predicate<? super String> condition) { + return new ApplicationIdSatisfies(condition); + } + + protected static class ApplicationIdSatisfies implements SerializablePredicate<Entity> { + protected final Predicate<? super String> condition; + protected ApplicationIdSatisfies(Predicate<? super String> condition) { + this.condition = condition; + } + @Override + public boolean apply(@Nullable Entity input) { + return (input != null) && condition.apply(input.getApplicationId()); + } + @Override + public String toString() { + return "applicationIdSatisfies("+condition+")"; + } + } + + /** @deprecated since 0.7.0 kept only to allow conversion of anonymous inner classes */ + @SuppressWarnings("unused") @Deprecated + private static Predicate<Entity> applicationIdEqualToOld(final String val) { + return new SerializablePredicate<Entity>() { + @Override + public boolean apply(@Nullable Entity input) { + return (input != null) && val.equals(input.getApplicationId()); + } + }; + } + + // --------------------------- + + public static <T> Predicate<Entity> attributeEqualTo(final AttributeSensor<T> attribute, final T val) { + return attributeSatisfies(attribute, Predicates.equalTo(val)); + } + + public static <T> Predicate<Entity> attributeSatisfies(final AttributeSensor<T> attribute, final Predicate<T> condition) { + return new AttributeSatisfies<T>(attribute, condition); + } + + protected static class AttributeSatisfies<T> implements SerializablePredicate<Entity> { + protected final AttributeSensor<T> attribute; + protected final Predicate<T> condition; + private AttributeSatisfies(AttributeSensor<T> attribute, Predicate<T> condition) { + this.attribute = attribute; + this.condition = condition; + } + @Override + public boolean apply(@Nullable Entity input) { + return (input != null) && condition.apply(input.getAttribute(attribute)); + } + @Override + public String toString() { + return "attributeSatisfies("+attribute.getName()+","+condition+")"; + } + } + + /** @deprecated since 0.7.0 kept only to allow conversion of anonymous inner classes */ + @SuppressWarnings("unused") @Deprecated + private static <T> Predicate<Entity> attributeEqualToOld(final AttributeSensor<T> attribute, final T val) { + return new SerializablePredicate<Entity>() { + @Override + public boolean apply(@Nullable Entity input) { + return (input != null) && Objects.equal(input.getAttribute(attribute), val); + } + }; + } + + public static <T> Predicate<Entity> attributeNotEqualTo(final AttributeSensor<T> attribute, final T val) { + return attributeSatisfies(attribute, Predicates.not(Predicates.equalTo(val))); + } + + // --------------------------- + + public static <T> Predicate<Entity> configEqualTo(final ConfigKey<T> configKey, final T val) { + return configSatisfies(configKey, Predicates.equalTo(val)); + } + + public static <T> Predicate<Entity> configSatisfies(final ConfigKey<T> configKey, final Predicate<T> condition) { + return new ConfigKeySatisfies<T>(configKey, condition); + } + + public static <T> Predicate<Entity> configEqualTo(final HasConfigKey<T> configKey, final T val) { + return configEqualTo(configKey.getConfigKey(), val); + } + + public static <T> Predicate<Entity> configSatisfies(final HasConfigKey<T> configKey, final Predicate<T> condition) { + return new ConfigKeySatisfies<T>(configKey.getConfigKey(), condition); + } + + protected static class ConfigKeySatisfies<T> implements SerializablePredicate<Entity> { + protected final ConfigKey<T> configKey; + protected final Predicate<T> condition; + private ConfigKeySatisfies(ConfigKey<T> configKey, Predicate<T> condition) { + this.configKey = configKey; + this.condition = condition; + } + @Override + public boolean apply(@Nullable Entity input) { + return (input != null) && condition.apply(input.getConfig(configKey)); + } + @Override + public String toString() { + return "configKeySatisfies("+configKey.getName()+","+condition+")"; + } + } + + + /** @deprecated since 0.7.0 kept only to allow conversion of anonymous inner classes */ + @SuppressWarnings("unused") @Deprecated + private static <T> Predicate<Entity> configEqualToOld(final ConfigKey<T> configKey, final T val) { + return new SerializablePredicate<Entity>() { + @Override + public boolean apply(@Nullable Entity input) { + return (input != null) && Objects.equal(input.getConfig(configKey), val); + } + }; + } + + /** @deprecated since 0.7.0 kept only to allow conversion of anonymous inner classes */ + @SuppressWarnings("unused") @Deprecated + private static <T> Predicate<Entity> configEqualToOld(final HasConfigKey<T> configKey, final T val) { + return new SerializablePredicate<Entity>() { + @Override + public boolean apply(@Nullable Entity input) { + return (input != null) && Objects.equal(input.getConfig(configKey), val); + } + }; + } + + // --------------------------- + + /** + * @param typeRegex a regular expression + * @return true if any of the interfaces implemented by the entity (including those derived) match typeRegex. + */ + public static Predicate<Entity> hasInterfaceMatching(String typeRegex) { + return new ImplementsInterface(typeRegex); + } + + protected static class ImplementsInterface implements SerializablePredicate<Entity> { + protected final Pattern pattern; + + public ImplementsInterface(String typeRegex) { + this.pattern = Pattern.compile(typeRegex); + } + + @Override + public boolean apply(@Nullable Entity input) { + if (input == null) return false; + for (Class<?> cls : Reflections.getAllInterfaces(input.getClass())) { + if (pattern.matcher(cls.getName()).matches()) { + return true; + } + } + return false; + } + } + + // --------------------------- + + /** + * Returns a predicate that determines if a given entity is a direct child of this {@code parent}. + */ + public static Predicate<Entity> isChildOf(final Entity parent) { + return new IsChildOf(parent); + } + + // if needed, could add parentSatisfies(...) + + protected static class IsChildOf implements SerializablePredicate<Entity> { + protected final Entity parent; + protected IsChildOf(Entity parent) { + this.parent = parent; + } + @Override + public boolean apply(@Nullable Entity input) { + return (input != null) && Objects.equal(input.getParent(), parent); + } + @Override + public String toString() { + return "isChildOf("+parent+")"; + } + } + + /** @deprecated since 0.7.0 kept only to allow conversion of anonymous inner classes */ + @SuppressWarnings("unused") @Deprecated + private static <T> Predicate<Entity> isChildOfOld(final Entity parent) { + return new SerializablePredicate<Entity>() { + @Override + public boolean apply(@Nullable Entity input) { + return (input != null) && Objects.equal(input.getParent(), parent); + } + }; + } + + // --------------------------- + + public static Predicate<Entity> isMemberOf(final Group group) { + return new IsMemberOf(group); + } + + protected static class IsMemberOf implements SerializablePredicate<Entity> { + protected final Group group; + protected IsMemberOf(Group group) { + this.group = group; + } + @Override + public boolean apply(@Nullable Entity input) { + return (group != null) && (input != null) && group.hasMember(input); + } + @Override + public String toString() { + return "isMemberOf("+group+")"; + } + } + + /** @deprecated since 0.7.0 kept only to allow conversion of anonymous inner classes */ + @SuppressWarnings("unused") @Deprecated + private static <T> Predicate<Entity> isMemberOfOld(final Group group) { + return new SerializablePredicate<Entity>() { + @Override + public boolean apply(@Nullable Entity input) { + return (input != null) && group.hasMember(input); + } + }; + } + + // --------------------------- + + /** + * Create a predicate that matches any entity who has an exact match for the given location + * (i.e. {@code entity.getLocations().contains(location)}). + */ + public static <T> Predicate<Entity> locationsIncludes(Location location) { + return locationsSatisfy(CollectionFunctionals.contains(location)); + + } + + public static <T> Predicate<Entity> locationsSatisfy(final Predicate<Collection<Location>> condition) { + return new LocationsSatisfy(condition); + } + + protected static class LocationsSatisfy implements SerializablePredicate<Entity> { + protected final Predicate<Collection<Location>> condition; + protected LocationsSatisfy(Predicate<Collection<Location>> condition) { + this.condition = condition; + } + @Override + public boolean apply(@Nullable Entity input) { + return (input != null) && condition.apply(input.getLocations()); + } + @Override + public String toString() { + return "locationsSatisfy("+condition+")"; + } + } + + /** @deprecated since 0.7.0 use {@link #locationsIncludes(Location)} */ + @Deprecated + public static <T> Predicate<Entity> withLocation(final Location location) { + return locationsIncludes(location); + } + + /** @deprecated since 0.7.0 use {@link #locationsIncludes(Location)}, introduced to allow deserialization of anonymous inner class */ + @SuppressWarnings("unused") @Deprecated + private static <T> Predicate<Entity> withLocationOld(final Location location) { + return new SerializablePredicate<Entity>() { + @Override + public boolean apply(@Nullable Entity input) { + return (input != null) && input.getLocations().contains(location); + } + }; + } + + // --------------------------- + + public static <T> Predicate<Entity> isManaged() { + return new IsManaged(); + } + + protected static class IsManaged implements SerializablePredicate<Entity> { + @Override + public boolean apply(@Nullable Entity input) { + return (input != null) && Entities.isManaged(input); + } + @Override + public String toString() { + return "isManaged()"; + } + } + + /** @deprecated since 0.7.0 use {@link #isManaged()} */ @Deprecated + public static <T> Predicate<Entity> managed() { + return isManaged(); + } + + /** @deprecated since 0.7.0 use {@link #isManaged()}, introduced to allow deserialization of anonymous inner class */ + @SuppressWarnings("unused") @Deprecated + private static <T> Predicate<Entity> managedOld() { + return new SerializablePredicate<Entity>() { + @Override + public boolean apply(@Nullable Entity input) { + return (input != null) && Entities.isManaged(input); + } + }; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/main/java/org/apache/brooklyn/core/entity/EntitySuppliers.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/entity/EntitySuppliers.java b/core/src/main/java/org/apache/brooklyn/core/entity/EntitySuppliers.java new file mode 100644 index 0000000..0e99f27 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/entity/EntitySuppliers.java @@ -0,0 +1,47 @@ +/* + * 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 org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.location.core.Machines; +import org.apache.brooklyn.location.ssh.SshMachineLocation; + +import com.google.common.base.Supplier; + +public class EntitySuppliers { + + public static Supplier<SshMachineLocation> uniqueSshMachineLocation(Entity entity) { + return new UniqueSshMchineLocation(entity); + } + + private static class UniqueSshMchineLocation implements Supplier<SshMachineLocation> { + private Entity entity; + + private UniqueSshMchineLocation() { /* for xstream */ + } + + private UniqueSshMchineLocation(Entity entity) { + this.entity = entity; + } + + @Override public SshMachineLocation get() { + return Machines.findUniqueSshMachineLocation(entity.getLocations()).get(); + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/main/java/org/apache/brooklyn/core/entity/EntityTasks.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/entity/EntityTasks.java b/core/src/main/java/org/apache/brooklyn/core/entity/EntityTasks.java new file mode 100644 index 0000000..ba2992f --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/entity/EntityTasks.java @@ -0,0 +1,81 @@ +/* + * 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 org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.mgmt.Task; +import org.apache.brooklyn.api.sensor.AttributeSensor; +import org.apache.brooklyn.sensor.core.DependentConfiguration; +import org.apache.brooklyn.util.collections.CollectionFunctionals; +import org.apache.brooklyn.util.time.Duration; + +import com.google.common.base.Functions; +import com.google.common.base.Predicate; +import com.google.common.base.Predicates; + +/** Generally useful tasks related to entities */ +public class EntityTasks { + + /** creates an (unsubmitted) task which waits for the attribute to satisfy the given predicate, + * returning false if it times out or becomes unmanaged */ + public static <T> Task<Boolean> testingAttributeEventually(Entity entity, AttributeSensor<T> sensor, Predicate<T> condition, Duration timeout) { + return DependentConfiguration.builder().attributeWhenReady(entity, sensor) + .readiness(condition) + .postProcess(Functions.constant(true)) + .timeout(timeout) + .onTimeoutReturn(false) + .onUnmanagedReturn(false) + .build(); + } + + /** creates an (unsubmitted) task which waits for the attribute to satisfy the given predicate, + * throwing if it times out or becomes unmanaged */ + public static <T> Task<Boolean> requiringAttributeEventually(Entity entity, AttributeSensor<T> sensor, Predicate<T> condition, Duration timeout) { + return DependentConfiguration.builder().attributeWhenReady(entity, sensor) + .readiness(condition) + .postProcess(Functions.constant(true)) + .timeout(timeout) + .onTimeoutThrow() + .onUnmanagedThrow() + .build(); + } + + /** as {@link #testingAttributeEventually(Entity, AttributeSensor, Predicate, Duration) for multiple entities */ + public static <T> Task<Boolean> testingAttributeEventually(Iterable<Entity> entities, AttributeSensor<T> sensor, Predicate<T> condition, Duration timeout) { + return DependentConfiguration.builder().attributeWhenReadyFromMultiple(entities, sensor, condition) + .postProcess(Functions.constant(true)) + .timeout(timeout) + .onTimeoutReturn(false) + .onUnmanagedReturn(false) + .postProcessFromMultiple(CollectionFunctionals.all(Predicates.equalTo(true))) + .build(); + } + + /** as {@link #requiringAttributeEventually(Entity, AttributeSensor, Predicate, Duration) for multiple entities */ + public static <T> Task<Boolean> requiringAttributeEventually(Iterable<Entity> entities, AttributeSensor<T> sensor, Predicate<T> condition, Duration timeout) { + return DependentConfiguration.builder().attributeWhenReadyFromMultiple(entities, sensor, condition) + .postProcess(Functions.constant(true)) + .timeout(timeout) + .onTimeoutThrow() + .onUnmanagedThrow() + .postProcessFromMultiple(CollectionFunctionals.all(Predicates.equalTo(true))) + .build(); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/main/java/org/apache/brooklyn/core/entity/EntityTypeSnapshot.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/entity/EntityTypeSnapshot.java b/core/src/main/java/org/apache/brooklyn/core/entity/EntityTypeSnapshot.java new file mode 100644 index 0000000..ef5c710 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/entity/EntityTypeSnapshot.java @@ -0,0 +1,126 @@ +/* + * 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 java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; + +import org.apache.brooklyn.api.effector.Effector; +import org.apache.brooklyn.api.effector.ParameterType; +import org.apache.brooklyn.api.entity.EntityType; +import org.apache.brooklyn.api.sensor.Sensor; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.core.objs.BrooklynTypeSnapshot; +import org.apache.brooklyn.util.guava.Maybe; + +import com.google.common.base.Joiner; +import com.google.common.base.Objects; +import com.google.common.base.Objects.ToStringHelper; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; + +public class EntityTypeSnapshot extends BrooklynTypeSnapshot implements EntityType { + private static final long serialVersionUID = 4670930188951106009L; + + private final Map<String, Sensor<?>> sensors; + private final Set<Effector<?>> effectors; + private final Set<Sensor<?>> sensorsSet; + + EntityTypeSnapshot(String name, Map<String, ConfigKey<?>> configKeys, Map<String, Sensor<?>> sensors, Collection<Effector<?>> effectors) { + super(name, configKeys); + this.sensors = ImmutableMap.copyOf(sensors); + this.effectors = ImmutableSet.copyOf(effectors); + this.sensorsSet = ImmutableSet.copyOf(this.sensors.values()); + } + + @Override + public Set<Sensor<?>> getSensors() { + return sensorsSet; + } + + @Override + public Set<Effector<?>> getEffectors() { + return effectors; + } + + @Override + public Maybe<Effector<?>> getEffectorByName(String name) { + for (Effector<?> contender : effectors) { + if (name.equals(contender.getName())) + return Maybe.<Effector<?>>of(contender); + } + return Maybe.<Effector<?>>absent("No effector matching '"+name+"'"); + } + + @Override + public Effector<?> getEffector(String name, Class<?>... parameterTypes) { + // TODO Could index for more efficient lookup (e.g. by name in a MultiMap, or using name+parameterTypes as a key) + // TODO Looks for exact match; could go for what would be valid to call (i.e. if parameterType is sub-class of ParameterType.getParameterClass then ok) + // TODO Could take into account ParameterType.getDefaultValue() for what can be omitted + + effectorLoop : for (Effector<?> contender : effectors) { + if (name.equals(contender.getName())) { + List<ParameterType<?>> contenderParameters = contender.getParameters(); + if (parameterTypes.length == contenderParameters.size()) { + for (int i = 0; i < parameterTypes.length; i++) { + if (parameterTypes[i] != contenderParameters.get(i).getParameterClass()) { + continue effectorLoop; + } + } + return contender; + } + } + } + throw new NoSuchElementException("No matching effector "+name+"("+Joiner.on(", ").join(parameterTypes)+") on entity "+getName()); + } + + @Override + public Sensor<?> getSensor(String name) { + return sensors.get(name); + } + + @Override + public boolean hasSensor(String name) { + return sensors.containsKey(name); + } + + @Override + public int hashCode() { + return Objects.hashCode(super.hashCode(), sensors, effectors); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (!(obj instanceof EntityTypeSnapshot)) return false; + EntityTypeSnapshot o = (EntityTypeSnapshot) obj; + + return super.equals(obj) && Objects.equal(sensors, o.sensors) && Objects.equal(effectors, o.effectors); + } + + @Override + protected ToStringHelper toStringHelper() { + return super.toStringHelper() + .add("sensors", sensors) + .add("effectors", effectors); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/main/java/org/apache/brooklyn/core/entity/EntityTypes.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/entity/EntityTypes.java b/core/src/main/java/org/apache/brooklyn/core/entity/EntityTypes.java new file mode 100644 index 0000000..ebedb65 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/entity/EntityTypes.java @@ -0,0 +1,28 @@ +/* + * 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 org.apache.brooklyn.core.objs.BrooklynTypes; + +/** + * @deprecated since 0.7.0; use {@link BrooklynTypes} + */ +@Deprecated +public class EntityTypes extends BrooklynTypes { +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/main/java/org/apache/brooklyn/core/entity/StartableApplication.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/entity/StartableApplication.java b/core/src/main/java/org/apache/brooklyn/core/entity/StartableApplication.java new file mode 100644 index 0000000..c0e27c0 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/entity/StartableApplication.java @@ -0,0 +1,25 @@ +/* + * 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 org.apache.brooklyn.api.entity.Application; +import org.apache.brooklyn.core.entity.trait.Startable; + +public interface StartableApplication extends Application, Startable { +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/main/java/org/apache/brooklyn/core/entity/drivers/BasicEntityDriverManager.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/entity/drivers/BasicEntityDriverManager.java b/core/src/main/java/org/apache/brooklyn/core/entity/drivers/BasicEntityDriverManager.java new file mode 100644 index 0000000..b7c3bd5 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/entity/drivers/BasicEntityDriverManager.java @@ -0,0 +1,56 @@ +/* + * 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.drivers; + +import org.apache.brooklyn.api.entity.drivers.DriverDependentEntity; +import org.apache.brooklyn.api.entity.drivers.EntityDriver; +import org.apache.brooklyn.api.entity.drivers.EntityDriverManager; +import org.apache.brooklyn.api.location.Location; + +import com.google.common.annotations.Beta; + +public class BasicEntityDriverManager implements EntityDriverManager { + + private final RegistryEntityDriverFactory registry; + private final ReflectiveEntityDriverFactory reflective; + + public BasicEntityDriverManager() { + registry = new RegistryEntityDriverFactory(); + reflective = new ReflectiveEntityDriverFactory(); + } + + /** driver override mechanism; experimental @since 0.7.0 */ + @Beta + public ReflectiveEntityDriverFactory getReflectiveDriverFactory() { + return reflective; + } + + public <D extends EntityDriver> void registerDriver(Class<D> driverInterface, Class<? extends Location> locationClazz, Class<? extends D> driverClazz) { + registry.registerDriver(driverInterface, locationClazz, driverClazz); + } + + @Override + public <D extends EntityDriver> D build(DriverDependentEntity<D> entity, Location location){ + if (registry.hasDriver(entity, location)) { + return registry.build(entity, location); + } else { + return reflective.build(entity, location); + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/main/java/org/apache/brooklyn/core/entity/drivers/ReflectiveEntityDriverFactory.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/entity/drivers/ReflectiveEntityDriverFactory.java b/core/src/main/java/org/apache/brooklyn/core/entity/drivers/ReflectiveEntityDriverFactory.java new file mode 100644 index 0000000..19973f2 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/entity/drivers/ReflectiveEntityDriverFactory.java @@ -0,0 +1,277 @@ +/* + * 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.drivers; + +import java.lang.reflect.Constructor; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.apache.brooklyn.api.entity.drivers.DriverDependentEntity; +import org.apache.brooklyn.api.entity.drivers.EntityDriver; +import org.apache.brooklyn.api.location.Location; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.apache.brooklyn.location.paas.PaasLocation; +import org.apache.brooklyn.location.ssh.SshMachineLocation; +import org.apache.brooklyn.location.winrm.WinRmMachineLocation; +import org.apache.brooklyn.util.collections.MutableList; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.exceptions.Exceptions; +import org.apache.brooklyn.util.exceptions.ReferenceWithError; +import org.apache.brooklyn.util.text.Strings; + +/** + * Follows a class naming convention: the driver interface typically ends in "Driver", and the implementation + * must match the driver interface name but with a suffix like "SshDriver" instead of "Driver". + * Other rules can be added using {@link #addRule(String, DriverInferenceRule)} or + * {@link #addClassFullNameMapping(String, String)}. + * <p> + * Reflectively instantiates and returns the driver, based on the location passed in, + * in {@link #build(DriverDependentEntity, Location)}. + * + * @author Peter Veentjer, Alex Heneveld + */ +public class ReflectiveEntityDriverFactory { + + private static final Logger LOG = LoggerFactory.getLogger(ReflectiveEntityDriverFactory.class); + + /** Rules, keyed by a unique identifier. Executed in order of most-recently added first. */ + protected final Map<String,DriverInferenceRule> rules = MutableMap.of(); + + public ReflectiveEntityDriverFactory() { + addRule(DriverInferenceForSshLocation.DEFAULT_IDENTIFIER, new DriverInferenceForSshLocation()); + addRule(DriverInferenceForPaasLocation.DEFAULT_IDENTIFIER, new DriverInferenceForPaasLocation()); + addRule(DriverInferenceForWinRmLocation.DEFAULT_IDENTIFIER, new DriverInferenceForWinRmLocation()); + } + + public interface DriverInferenceRule { + public <D extends EntityDriver> ReferenceWithError<Class<? extends D>> resolve(DriverDependentEntity<D> entity, Class<D> driverInterface, Location location); + } + + public static abstract class AbstractDriverInferenceRule implements DriverInferenceRule { + + @Override + public <D extends EntityDriver> ReferenceWithError<Class<? extends D>> resolve(DriverDependentEntity<D> entity, Class<D> driverInterface, Location location) { + try { + String newName = inferDriverClassName(entity, driverInterface, location); + if (newName==null) return null; + + return loadDriverClass(newName, entity, driverInterface); + + } catch (Exception e) { + Exceptions.propagateIfFatal(e); + return ReferenceWithError.newInstanceThrowingError(null, e); + } + } + + public abstract <D extends EntityDriver> String inferDriverClassName(DriverDependentEntity<D> entity, Class<D> driverInterface, Location location); + + protected <D extends EntityDriver> ReferenceWithError<Class<? extends D>> loadDriverClass(String className, DriverDependentEntity<D> entity, Class<D> driverInterface) { + ReferenceWithError<Class<? extends D>> r1 = loadClass(className, entity.getClass().getClassLoader()); + if (!r1.hasError()) return r1; + ReferenceWithError<Class<? extends D>> r2 = loadClass(className, driverInterface.getClass().getClassLoader()); + if (!r2.hasError()) return r2; + return r1; + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + protected <D extends EntityDriver> ReferenceWithError<Class<? extends D>> loadClass(String className, ClassLoader classLoader) { + try { + return (ReferenceWithError<Class<? extends D>>)(ReferenceWithError) ReferenceWithError.newInstanceWithoutError((Class<? extends EntityDriver>)classLoader.loadClass(className)); + } catch (Exception e) { + Exceptions.propagateIfFatal(e); + return ReferenceWithError.newInstanceThrowingError(null, e); + } + } + } + + public static abstract class AbstractDriverInferenceRenamingInferenceRule extends AbstractDriverInferenceRule { + + protected final String expectedPattern; + protected final String replacement; + + public AbstractDriverInferenceRenamingInferenceRule(String expectedPattern, String replacement) { + this.expectedPattern = expectedPattern; + this.replacement = replacement; + } + + public String getIdentifier() { + return getClass().getName()+"["+expectedPattern+"]"; + } + + @Override + public String toString() { + return getClass().getName()+"["+expectedPattern+"->"+replacement+"]"; + } + } + + public static class DriverInferenceByRenamingClassFullName extends AbstractDriverInferenceRenamingInferenceRule { + + public DriverInferenceByRenamingClassFullName(String expectedClassFullName, String newClassFullName) { + super(expectedClassFullName, newClassFullName); + } + + @Override + public <D extends EntityDriver> String inferDriverClassName(DriverDependentEntity<D> entity, Class<D> driverInterface, Location location) { + if (driverInterface.getName().equals(expectedPattern)) { + return replacement; + } + return null; + } + } + + public static class DriverInferenceByRenamingClassSimpleName extends AbstractDriverInferenceRenamingInferenceRule { + + public DriverInferenceByRenamingClassSimpleName(String expectedClassSimpleName, String newClassSimpleName) { + super(expectedClassSimpleName, newClassSimpleName); + } + + @Override + public <D extends EntityDriver> String inferDriverClassName(DriverDependentEntity<D> entity, Class<D> driverInterface, Location location) { + if (driverInterface.getSimpleName().equals(expectedPattern)) { + // i'd like to do away with drivers altogether, but if people *really* need to use this and suppress the warning, + // they can use the full class rename + LOG.warn("Using discouraged driver simple class rename to find "+replacement+" for "+expectedPattern+"; it is recommended to set getDriverInterface() or newDriver() appropriately"); + return Strings.removeFromEnd(driverInterface.getName(), expectedPattern)+replacement; + } + return null; + } + } + + public static class DriverInferenceForSshLocation extends AbstractDriverInferenceRule { + + public static final String DEFAULT_IDENTIFIER = "ssh-location-driver-inference-rule"; + + @Override + public <D extends EntityDriver> String inferDriverClassName(DriverDependentEntity<D> entity, Class<D> driverInterface, Location location) { + String driverInterfaceName = driverInterface.getName(); + if (!(location instanceof SshMachineLocation)) return null; + if (!driverInterfaceName.endsWith("Driver")) { + throw new IllegalArgumentException(String.format("Driver name [%s] doesn't end with 'Driver'; cannot auto-detect SshDriver class name", driverInterfaceName)); + } + return Strings.removeFromEnd(driverInterfaceName, "Driver")+"SshDriver"; + } + } + + public static class DriverInferenceForPaasLocation extends AbstractDriverInferenceRule { + + public static final String DEFAULT_IDENTIFIER = "paas-location-driver-inference-rule"; + + @Override + public <D extends EntityDriver> String inferDriverClassName(DriverDependentEntity<D> entity, Class<D> driverInterface, Location location) { + String driverInterfaceName = driverInterface.getName(); + if (!(location instanceof PaasLocation)) return null; + if (!driverInterfaceName.endsWith("Driver")) { + throw new IllegalArgumentException(String.format("Driver name [%s] doesn't end with 'Driver'; cannot auto-detect PaasDriver class name", driverInterfaceName)); + } + return Strings.removeFromEnd(driverInterfaceName, "Driver") + ((PaasLocation) location).getPaasProviderName() + "Driver"; + } + } + + public static class DriverInferenceForWinRmLocation extends AbstractDriverInferenceRule { + + public static final String DEFAULT_IDENTIFIER = "winrm-location-driver-inference-rule"; + + @Override + public <D extends EntityDriver> String inferDriverClassName(DriverDependentEntity<D> entity, Class<D> driverInterface, Location location) { + String driverInterfaceName = driverInterface.getName(); + if (!(location instanceof WinRmMachineLocation)) return null; + if (!driverInterfaceName.endsWith("Driver")) { + throw new IllegalArgumentException(String.format("Driver name [%s] doesn't end with 'Driver'; cannot auto-detect WinRmDriver class name", driverInterfaceName)); + } + return Strings.removeFromEnd(driverInterfaceName, "Driver")+"WinRmDriver"; + } + } + + /** adds a rule; possibly replacing an old one if one exists with the given identifier. the new rule is added after all previous ones. + * @return the replaced rule, or null if there was no old rule */ + public DriverInferenceRule addRule(String identifier, DriverInferenceRule rule) { + DriverInferenceRule oldRule = rules.remove(identifier); + rules.put(identifier, rule); + LOG.debug("Added driver mapping rule "+rule); + return oldRule; + } + + public DriverInferenceRule addClassFullNameMapping(String expectedClassFullName, String newClassFullName) { + DriverInferenceByRenamingClassFullName rule = new DriverInferenceByRenamingClassFullName(expectedClassFullName, newClassFullName); + return addRule(rule.getIdentifier(), rule); + } + + public DriverInferenceRule addClassSimpleNameMapping(String expectedClassSimpleName, String newClassSimpleName) { + DriverInferenceByRenamingClassSimpleName rule = new DriverInferenceByRenamingClassSimpleName(expectedClassSimpleName, newClassSimpleName); + return addRule(rule.getIdentifier(), rule); + } + + public <D extends EntityDriver> D build(DriverDependentEntity<D> entity, Location location){ + Class<D> driverInterface = entity.getDriverInterface(); + Class<? extends D> driverClass = null; + List<Throwable> exceptions = MutableList.of(); + if (driverInterface.isInterface()) { + List<DriverInferenceRule> ruleListInExecutionOrder = MutableList.copyOf(rules.values()); + Collections.reverse(ruleListInExecutionOrder); + // above puts rules in order with most recently added first + for (DriverInferenceRule rule: ruleListInExecutionOrder) { + ReferenceWithError<Class<? extends D>> clazzR = rule.resolve(entity, driverInterface, location); + if (clazzR!=null) { + if (!clazzR.hasError()) { + Class<? extends D> clazz = clazzR.get(); + if (clazz!=null) { + driverClass = clazz; + break; + } + } else { + exceptions.add(clazzR.getError()); + } + } + } + } else { + driverClass = driverInterface; + } + LOG.debug("Driver for "+driverInterface.getName()+" in "+location+" is: "+driverClass); + + if (driverClass==null) { + if (exceptions.isEmpty()) + throw new RuntimeException("No drivers could be found for "+driverInterface.getName()+"; " + + "currently only SshMachineLocation is supported for autodetection (location "+location+")"); + else throw Exceptions.create("No drivers could be loaded for "+driverInterface.getName()+" in "+location, exceptions); + } + + try { + Constructor<? extends D> constructor = getConstructor(driverClass); + constructor.setAccessible(true); + return constructor.newInstance(entity, location); + } catch (Exception e) { + LOG.warn("Unable to instantiate "+driverClass+" (rethrowing): "+e); + throw Exceptions.propagate(e); + } + } + + @SuppressWarnings("unchecked") + private <D extends EntityDriver> Constructor<D> getConstructor(Class<D> driverClass) { + for (Constructor<?> constructor : driverClass.getConstructors()) { + if (constructor.getParameterTypes().length == 2) { + return (Constructor<D>) constructor; + } + } + + throw new RuntimeException(String.format("Class [%s] has no constructor with 2 arguments", driverClass.getName())); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/main/java/org/apache/brooklyn/core/entity/drivers/RegistryEntityDriverFactory.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/entity/drivers/RegistryEntityDriverFactory.java b/core/src/main/java/org/apache/brooklyn/core/entity/drivers/RegistryEntityDriverFactory.java new file mode 100644 index 0000000..d2dd125 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/entity/drivers/RegistryEntityDriverFactory.java @@ -0,0 +1,127 @@ +/* + * 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.drivers; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.entity.drivers.DriverDependentEntity; +import org.apache.brooklyn.api.entity.drivers.EntityDriver; +import org.apache.brooklyn.api.entity.drivers.EntityDriverManager; +import org.apache.brooklyn.api.location.Location; + +import com.google.common.base.Objects; +import com.google.common.base.Throwables; + +/** + * A registry of driver classes, keyed off the driver-interface + location type it is for. + * + * @author Aled Sage + */ +public class RegistryEntityDriverFactory implements EntityDriverManager { + + private final Map<DriverLocationTuple, Class<? extends EntityDriver>> registry = new LinkedHashMap<DriverLocationTuple, Class<? extends EntityDriver>>(); + + @Override + public <D extends EntityDriver> D build(DriverDependentEntity<D> entity, Location location) { + Class<? extends D> driverClass = lookupDriver(entity.getDriverInterface(), location); + return newDriverInstance(driverClass, entity, location); + } + + public boolean hasDriver(DriverDependentEntity<?> entity, Location location) { + return lookupDriver(entity.getDriverInterface(), location) != null; + } + + public <D extends EntityDriver> void registerDriver(Class<D> driverInterface, Class<? extends Location> locationClazz, Class<? extends D> driverClazz) { + synchronized (registry) { + registry.put(new DriverLocationTuple(driverInterface, locationClazz), driverClazz); + } + } + + @SuppressWarnings("unchecked") + private <D extends EntityDriver> Class<? extends D> lookupDriver(Class<D> driverInterface, Location location) { + synchronized (registry) { + for (DriverLocationTuple contender : registry.keySet()) { + if (contender.matches(driverInterface, location)) { + return (Class<? extends D>) registry.get(contender); + } + } + } + return null; + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private <D> Constructor<D> getConstructor(Class<? extends D> driverClass) { + for (Constructor constructor : driverClass.getConstructors()) { + if (constructor.getParameterTypes().length == 2) { + return constructor; + } + } + + //TODO: + throw new RuntimeException(String.format("Class [%s] has no constructor with 2 arguments",driverClass.getName())); + } + + private <D> D newDriverInstance(Class<D> driverClass, Entity entity, Location location) { + Constructor<D> constructor = getConstructor(driverClass); + try { + constructor.setAccessible(true); + return constructor.newInstance(entity, location); + } catch (InstantiationException e) { + throw Throwables.propagate(e); + } catch (IllegalAccessException e) { + throw Throwables.propagate(e); + } catch (InvocationTargetException e) { + throw Throwables.propagate(e); + } + } + + private static class DriverLocationTuple { + private final Class<? extends EntityDriver> driverInterface; + private final Class<? extends Location> locationClazz; + + public DriverLocationTuple(Class<? extends EntityDriver> driverInterface, Class<? extends Location> locationClazz) { + this.driverInterface = checkNotNull(driverInterface, "driver interface"); + this.locationClazz = checkNotNull(locationClazz, "location class"); + } + + public boolean matches(Class<? extends EntityDriver> driver, Location location) { + return driverInterface.isAssignableFrom(driver) && locationClazz.isInstance(location); + } + + @Override + public int hashCode() { + return Objects.hashCode(driverInterface, locationClazz); + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof DriverLocationTuple)) { + return false; + } + DriverLocationTuple o = (DriverLocationTuple) other; + return driverInterface.equals(o.driverInterface) && locationClazz.equals(o.locationClazz); + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/main/java/org/apache/brooklyn/core/entity/drivers/downloads/BasicDownloadRequirement.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/entity/drivers/downloads/BasicDownloadRequirement.java b/core/src/main/java/org/apache/brooklyn/core/entity/drivers/downloads/BasicDownloadRequirement.java new file mode 100644 index 0000000..0a1106b --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/entity/drivers/downloads/BasicDownloadRequirement.java @@ -0,0 +1,85 @@ +/* + * 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.drivers.downloads; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.Map; + +import org.apache.brooklyn.api.entity.drivers.EntityDriver; +import org.apache.brooklyn.api.entity.drivers.downloads.DownloadResolverManager.DownloadRequirement; +import org.apache.brooklyn.util.collections.MutableMap; + +import com.google.common.base.Objects; +import com.google.common.collect.ImmutableMap; + +public class BasicDownloadRequirement implements DownloadRequirement { + + private final EntityDriver entityDriver; + private final String addonName; + private final Map<String, ?> properties; + + /** + * Copies the given DownloadRequirement, but overriding the original properties with the given additional properties. + */ + public static BasicDownloadRequirement copy(DownloadRequirement req, Map<String,?> additionalProperties) { + Map<String,?> props = MutableMap.<String,Object>builder().putAll(req.getProperties()).putAll(additionalProperties).build(); + if (req.getAddonName() == null) { + return new BasicDownloadRequirement(req.getEntityDriver(), props); + } else { + return new BasicDownloadRequirement(req.getEntityDriver(), req.getAddonName(), props); + } + } + + public BasicDownloadRequirement(EntityDriver driver) { + this(driver, ImmutableMap.<String,Object>of()); + } + + public BasicDownloadRequirement(EntityDriver driver, Map<String, ?> properties) { + this.entityDriver = checkNotNull(driver, "entityDriver"); + this.addonName = null; + this.properties = checkNotNull(properties, "properties"); + } + + public BasicDownloadRequirement(EntityDriver entityDriver, String addonName, Map<String, ?> properties) { + this.entityDriver = checkNotNull(entityDriver, "entityDriver"); + this.addonName = checkNotNull(addonName, "addonName"); + this.properties = checkNotNull(properties, "properties"); + } + + @Override + public EntityDriver getEntityDriver() { + return entityDriver; + } + + @Override + public String getAddonName() { + return addonName; + } + + @Override + public Map<String, ?> getProperties() { + return properties; + } + + @Override + public String toString() { + return Objects.toStringHelper(this).add("driver", entityDriver).add("addon", addonName).omitNullValues().toString(); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/main/java/org/apache/brooklyn/core/entity/drivers/downloads/BasicDownloadResolver.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/entity/drivers/downloads/BasicDownloadResolver.java b/core/src/main/java/org/apache/brooklyn/core/entity/drivers/downloads/BasicDownloadResolver.java new file mode 100644 index 0000000..aed8f0b --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/entity/drivers/downloads/BasicDownloadResolver.java @@ -0,0 +1,66 @@ +/* + * 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.drivers.downloads; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.List; + +import org.apache.brooklyn.api.entity.drivers.downloads.DownloadResolver; + +import com.google.common.base.Objects; +import com.google.common.collect.ImmutableList; + +public class BasicDownloadResolver implements DownloadResolver { + + private final List<String> targets; + private final String filename; + private final String unpackDirectoryName; + + public BasicDownloadResolver(Iterable<String> targets, String filename) { + this(targets, filename, null); + } + + public BasicDownloadResolver(Iterable<String> targets, String filename, String unpackDirectoryName) { + this.targets = ImmutableList.copyOf(checkNotNull(targets, "targets")); + this.filename = checkNotNull(filename, "filename"); + this.unpackDirectoryName = unpackDirectoryName; + } + + @Override + public List<String> getTargets() { + return targets; + } + + @Override + public String getFilename() { + return filename; + } + + @Override + public String getUnpackedDirectoryName(String defaultVal) { + return unpackDirectoryName == null ? defaultVal : unpackDirectoryName; + } + + @Override + public String toString() { + return Objects.toStringHelper(this).add("targets", targets).add("filename", filename) + .add("unpackDirName", unpackDirectoryName).omitNullValues().toString(); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/main/java/org/apache/brooklyn/core/entity/drivers/downloads/BasicDownloadTargets.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/entity/drivers/downloads/BasicDownloadTargets.java b/core/src/main/java/org/apache/brooklyn/core/entity/drivers/downloads/BasicDownloadTargets.java new file mode 100644 index 0000000..d8e6599 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/entity/drivers/downloads/BasicDownloadTargets.java @@ -0,0 +1,121 @@ +/* + * 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.drivers.downloads; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.List; + +import org.apache.brooklyn.api.entity.drivers.downloads.DownloadResolverManager.DownloadTargets; +import org.apache.brooklyn.util.collections.MutableList; + +import com.google.common.base.Objects; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; + +public class BasicDownloadTargets implements DownloadTargets { + + private static final DownloadTargets EMPTY = builder().build(); + + public static DownloadTargets empty() { + return EMPTY; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private List<String> primaries = Lists.newArrayList(); + private List<String> fallbacks = Lists.newArrayList(); + private boolean canContinueResolving = true; + + public Builder addAll(DownloadTargets other) { + addPrimaries(other.getPrimaryLocations()); + addFallbacks(other.getFallbackLocations()); + return this; + } + + public Builder addPrimary(String val) { + checkNotNull(val, "val"); + if (!primaries.contains(val)) primaries.add(val); + return this; + } + + public Builder addPrimaries(Iterable<String> vals) { + for (String val : checkNotNull(vals, "vals")) { + addPrimary(val); + } + return this; + } + + public Builder addFallback(String val) { + checkNotNull(val, "val"); + if (!fallbacks.contains(val)) fallbacks.add(val); + return this; + } + + public Builder addFallbacks(Iterable<String> vals) { + for (String val : checkNotNull(vals, "vals")) { + addFallback(val); + } + return this; + } + + public Builder canContinueResolving(boolean val) { + canContinueResolving = val; + return this; + } + + public BasicDownloadTargets build() { + return new BasicDownloadTargets(this); + } + } + + private final List<String> primaries; + private final List<String> fallbacks; + private final boolean canContinueResolving; + + protected BasicDownloadTargets(Builder builder) { + primaries = ImmutableList.copyOf(builder.primaries); + fallbacks = MutableList.<String>builder().addAll(builder.fallbacks).removeAll(builder.primaries).build().asUnmodifiable(); + canContinueResolving = builder.canContinueResolving; + } + + @Override + public List<String> getPrimaryLocations() { + return primaries; + } + + @Override + public List<String> getFallbackLocations() { + return fallbacks; + } + + @Override + public boolean canContinueResolving() { + return canContinueResolving; + } + + @Override + public String toString() { + return Objects.toStringHelper(this).add("primaries", primaries).add("fallbacks", fallbacks) + .add("canContinueResolving", canContinueResolving).toString(); + } +}
