blob: 51e3e6f6c092c28bdf05df668ae13ec3d1b52b05 [file] [log] [blame]
/*
* 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.mgmt.rebind.dto;
import static com.google.common.base.Preconditions.checkNotNull;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.util.Map;
import java.util.Set;
import org.apache.brooklyn.api.catalog.CatalogItem;
import org.apache.brooklyn.api.entity.Application;
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.mgmt.Task;
import org.apache.brooklyn.api.mgmt.rebind.mementos.CatalogItemMemento;
import org.apache.brooklyn.api.mgmt.rebind.mementos.EnricherMemento;
import org.apache.brooklyn.api.mgmt.rebind.mementos.EntityMemento;
import org.apache.brooklyn.api.mgmt.rebind.mementos.FeedMemento;
import org.apache.brooklyn.api.mgmt.rebind.mementos.LocationMemento;
import org.apache.brooklyn.api.mgmt.rebind.mementos.ManagedBundleMemento;
import org.apache.brooklyn.api.mgmt.rebind.mementos.Memento;
import org.apache.brooklyn.api.mgmt.rebind.mementos.PolicyMemento;
import org.apache.brooklyn.api.objs.BrooklynObject;
import org.apache.brooklyn.api.objs.EntityAdjunct;
import org.apache.brooklyn.api.policy.Policy;
import org.apache.brooklyn.api.relations.RelationshipType;
import org.apache.brooklyn.api.sensor.AttributeSensor;
import org.apache.brooklyn.api.sensor.AttributeSensor.SensorPersistenceMode;
import org.apache.brooklyn.api.sensor.Enricher;
import org.apache.brooklyn.api.sensor.Feed;
import org.apache.brooklyn.api.typereg.ManagedBundle;
import org.apache.brooklyn.config.ConfigKey;
import org.apache.brooklyn.core.catalog.internal.CatalogItemDo;
import org.apache.brooklyn.core.enricher.AbstractEnricher;
import org.apache.brooklyn.core.entity.EntityDynamicType;
import org.apache.brooklyn.core.entity.EntityInternal;
import org.apache.brooklyn.core.entity.EntityRelations;
import org.apache.brooklyn.core.feed.AbstractFeed;
import org.apache.brooklyn.core.location.internal.LocationInternal;
import org.apache.brooklyn.core.mgmt.persist.BrooklynPersistenceUtils;
import org.apache.brooklyn.core.mgmt.rebind.AbstractBrooklynObjectRebindSupport;
import org.apache.brooklyn.core.objs.BrooklynObjectInternal;
import org.apache.brooklyn.core.objs.BrooklynTypes;
import org.apache.brooklyn.core.policy.AbstractPolicy;
import org.apache.brooklyn.util.collections.MutableMap;
import org.apache.brooklyn.util.collections.MutableSet;
import org.apache.brooklyn.util.core.config.ConfigBag;
import org.apache.brooklyn.util.core.flags.FlagUtils;
import org.apache.brooklyn.util.core.xstream.OsgiClassPrefixer;
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.Optional;
import com.google.common.base.Predicates;
import com.google.common.collect.Sets;
public class MementosGenerators {
private MementosGenerators() {}
private static final Logger log = LoggerFactory.getLogger(MementosGenerators.class);
/**
* Inspects a brooklyn object to create a basic corresponding memento.
* <p>
* The memento is "basic" in the sense that it does not tie in to any entity-specific customization;
* the corresponding memento may subsequently be customized by the caller.
* <p>
* This method is intended for use by {@link AbstractBrooklynObjectRebindSupport#getMemento()}
* and callers wanting a memento for an object should use that, or the
* {@link BrooklynPersistenceUtils#newObjectMemento(BrooklynObject)} convenience.
*/
@Beta
public static Memento newBasicMemento(BrooklynObject instance) {
if (instance instanceof Entity) {
return newEntityMemento((Entity)instance);
} else if (instance instanceof Location) {
return newLocationMemento((Location)instance);
} else if (instance instanceof Policy) {
return newPolicyMemento((Policy)instance);
} else if (instance instanceof Enricher) {
return newEnricherMemento((Enricher) instance);
} else if (instance instanceof Feed) {
return newFeedMemento((Feed)instance);
} else if (instance instanceof CatalogItem) {
return newCatalogItemMemento((CatalogItem<?,?>) instance);
} else if (instance instanceof ManagedBundle) {
return newManagedBundleMemento((ManagedBundle) instance);
} else {
throw new IllegalArgumentException("Unexpected brooklyn type: "+(instance == null ? "null" : instance.getClass())+" ("+instance+")");
}
}
/**
* Inspects an entity to create a corresponding memento.
*/
private static EntityMemento newEntityMemento(Entity entityRaw) {
EntityInternal entity = (EntityInternal) entityRaw;
BasicEntityMemento.Builder builder = BasicEntityMemento.builder();
populateBrooklynObjectMementoBuilder(entity, builder);
EntityDynamicType definedType = BrooklynTypes.getDefinedEntityType(entity.getClass());
// TODO the dynamic attributeKeys and configKeys are computed in the BasicEntityMemento
// whereas effectors are computed here -- should be consistent!
// (probably best to compute attrKeys and configKeys here)
builder.effectors.addAll(entity.getEntityType().getEffectors());
builder.effectors.removeAll(definedType.getEffectors().values());
builder.isTopLevelApp = (entity instanceof Application && entity.getParent() == null);
builder.configKeys.addAll(entity.getEntityType().getConfigKeys());
Map<ConfigKey<?>, ?> localConfig = entity.config().getAllLocalRaw();
for (Map.Entry<ConfigKey<?>, ?> entry : localConfig.entrySet()) {
ConfigKey<?> key = checkNotNull(entry.getKey(), localConfig);
Object value = configValueToPersistable(entry.getValue(), entityRaw, key.getName());
builder.config.put(key, value);
}
Map<String, Object> localConfigUnmatched = MutableMap.copyOf(entity.config().getLocalBag().getAllConfig());
for (ConfigKey<?> key : localConfig.keySet()) {
localConfigUnmatched.remove(key.getName());
}
for (Map.Entry<String, Object> entry : localConfigUnmatched.entrySet()) {
String key = checkNotNull(entry.getKey(), localConfig);
Object value = entry.getValue();
// TODO Not transforming; that code is deleted in another pending PR anyway!
builder.configUnmatched.put(key, value);
}
Map<AttributeSensor<?>, Object> allAttributes = entity.sensors().getAll();
for (Map.Entry<AttributeSensor<?>, Object> entry : allAttributes.entrySet()) {
AttributeSensor<?> key = checkNotNull(entry.getKey(), allAttributes);
if (key.getPersistenceMode() != SensorPersistenceMode.NONE) {
Object value = entry.getValue();
builder.attributes.put(key, value);
}
}
for (Location location : entity.getLocations()) {
builder.locations.add(location.getId());
}
for (Entity child : entity.getChildren()) {
builder.children.add(child.getId());
}
for (Policy policy : entity.policies()) {
builder.policies.add(policy.getId());
}
for (Enricher enricher : entity.enrichers()) {
builder.enrichers.add(enricher.getId());
}
for (Feed feed : entity.feeds().getFeeds()) {
builder.feeds.add(feed.getId());
}
Entity parentEntity = entity.getParent();
builder.parent = (parentEntity != null) ? parentEntity.getId() : null;
if (entity instanceof Group) {
for (Entity member : ((Group)entity).getMembers()) {
builder.members.add(member.getId());
}
}
return builder.build();
}
/**
* Given a location, extracts its state for serialization.
*
* For bits of state that are references to other locations, these are treated in a special way:
* the location reference is replaced by the location id.
* TODO When we have a cleaner separation of constructor/config for entities and locations, then
* we will remove this code!
*
* @deprecated since 0.7.0, see {@link #newBasicMemento(BrooklynObject)}
*/
@Deprecated
public static LocationMemento newLocationMemento(Location location) {
return newLocationMementoBuilder(location).build();
}
/**
* @deprecated since 0.7.0; use {@link #newBasicMemento(BrooklynObject)} instead
*/
@Deprecated
public static BasicLocationMemento.Builder newLocationMementoBuilder(Location location) {
BasicLocationMemento.Builder builder = BasicLocationMemento.builder();
populateBrooklynObjectMementoBuilder(location, builder);
Set<String> nonPersistableFlagNames = MutableMap.<String,Object>builder()
.putAll(FlagUtils.getFieldsWithFlagsWithModifiers(location, Modifier.TRANSIENT))
.putAll(FlagUtils.getFieldsWithFlagsWithModifiers(location, Modifier.STATIC))
.put("id", String.class)
.filterValues(Predicates.not(Predicates.instanceOf(ConfigKey.class)))
.build()
.keySet();
Map<String, Object> persistableFlags = MutableMap.<String, Object>builder()
.putAll(FlagUtils.getFieldsWithFlagsExcludingModifiers(location, Modifier.STATIC ^ Modifier.TRANSIENT))
.removeAll(nonPersistableFlagNames)
.build();
ConfigBag persistableConfig = new ConfigBag().copy( ((LocationInternal)location).config().getLocalBag() ).removeAll(nonPersistableFlagNames);
builder.copyConfig(persistableConfig);
builder.locationConfig.putAll(persistableFlags);
Location parentLocation = location.getParent();
builder.parent = (parentLocation != null) ? parentLocation.getId() : null;
for (Location child : location.getChildren()) {
builder.children.add(child.getId());
}
return builder;
}
/**
* Given a policy, extracts its state for serialization.
*
* @deprecated since 0.7.0, see {@link #newBasicMemento(BrooklynObject)}
*/
@Deprecated
public static PolicyMemento newPolicyMemento(Policy policy) {
BasicPolicyMemento.Builder builder = BasicPolicyMemento.builder();
populateBrooklynObjectMementoBuilder(policy, builder);
// TODO persist config keys as well? Or only support those defined on policy class;
// current code will lose the ConfigKey type on rebind for anything not defined on class.
// Whereas entities support that.
// TODO Do we need the "nonPersistableFlagNames" that locations use?
Map<ConfigKey<?>, Object> config = ((AbstractPolicy)policy).config().getInternalConfigMap().getAllConfigLocalRaw();
for (Map.Entry<ConfigKey<?>, Object> entry : config.entrySet()) {
ConfigKey<?> key = checkNotNull(entry.getKey(), "config=%s", config);
Object value = configValueToPersistable(entry.getValue(), policy, key.getName());
builder.config.put(key.getName(), value);
}
builder.highlights(policy.getHighlights());
Map<String, Object> persistableFlags = MutableMap.<String, Object>builder()
.putAll(FlagUtils.getFieldsWithFlagsExcludingModifiers(policy, Modifier.STATIC ^ Modifier.TRANSIENT))
.remove("id")
.remove("name")
.build();
builder.config.putAll(persistableFlags);
return builder.build();
}
/**
* Given an enricher, extracts its state for serialization.
* @deprecated since 0.7.0, see {@link #newBasicMemento(BrooklynObject)}
*/
@Deprecated
public static EnricherMemento newEnricherMemento(Enricher enricher) {
BasicEnricherMemento.Builder builder = BasicEnricherMemento.builder();
populateBrooklynObjectMementoBuilder(enricher, builder);
// TODO persist config keys as well? Or only support those defined on policy class;
// current code will lose the ConfigKey type on rebind for anything not defined on class.
// Whereas entities support that.
// TODO Do we need the "nonPersistableFlagNames" that locations use?
Map<ConfigKey<?>, Object> config = ((AbstractEnricher)enricher).config().getInternalConfigMap().getAllConfigLocalRaw();
for (Map.Entry<ConfigKey<?>, Object> entry : config.entrySet()) {
ConfigKey<?> key = checkNotNull(entry.getKey(), "config=%s", config);
Object value = configValueToPersistable(entry.getValue(), enricher, key.getName());
builder.config.put(key.getName(), value);
}
Map<String, Object> persistableFlags = MutableMap.<String, Object>builder()
.putAll(FlagUtils.getFieldsWithFlagsExcludingModifiers(enricher, Modifier.STATIC ^ Modifier.TRANSIENT))
.remove("id")
.remove("name")
.build();
builder.config.putAll(persistableFlags);
return builder.build();
}
/**
* Given a feed, extracts its state for serialization.
* @deprecated since 0.7.0, see {@link #newBasicMemento(BrooklynObject)}
*/
@Deprecated
public static FeedMemento newFeedMemento(Feed feed) {
BasicFeedMemento.Builder builder = BasicFeedMemento.builder();
populateBrooklynObjectMementoBuilder(feed, builder);
// TODO persist config keys as well? Or only support those defined on policy class;
// current code will lose the ConfigKey type on rebind for anything not defined on class.
// Whereas entities support that.
// TODO Do we need the "nonPersistableFlagNames" that locations use?
Map<ConfigKey<?>, Object> config = ((AbstractFeed)feed).config().getInternalConfigMap().getAllConfigLocalRaw();
for (Map.Entry<ConfigKey<?>, Object> entry : config.entrySet()) {
ConfigKey<?> key = checkNotNull(entry.getKey(), "config=%s", config);
Object value = configValueToPersistable(entry.getValue(), feed, key.getName());
builder.config.put(key.getName(), value);
}
return builder.build();
}
@SuppressWarnings("deprecation")
private static CatalogItemMemento newCatalogItemMemento(CatalogItem<?, ?> catalogItem) {
if (catalogItem instanceof CatalogItemDo<?,?>) {
catalogItem = ((CatalogItemDo<?,?>)catalogItem).getDto();
}
BasicCatalogItemMemento.Builder builder = BasicCatalogItemMemento.builder();
populateBrooklynObjectMementoBuilder(catalogItem, builder);
builder.catalogItemJavaType(catalogItem.getCatalogItemJavaType())
.catalogItemType(catalogItem.getCatalogItemType())
.containingBundle(catalogItem.getContainingBundle())
.description(catalogItem.getDescription())
.iconUrl(catalogItem.getIconUrl())
.javaType(catalogItem.getJavaType())
.libraries(catalogItem.getLibraries())
.symbolicName(catalogItem.getSymbolicName())
.specType(catalogItem.getSpecType())
.version(catalogItem.getVersion())
.planYaml(catalogItem.getPlanYaml())
.deprecated(catalogItem.isDeprecated())
.disabled(catalogItem.isDisabled());
return builder.build();
}
private static ManagedBundleMemento newManagedBundleMemento(ManagedBundle bundle) {
BasicManagedBundleMemento.Builder builder = BasicManagedBundleMemento.builder();
populateBrooklynObjectMementoBuilder(bundle, builder);
builder.url(bundle.getUrl())
.symbolicName(bundle.getSymbolicName())
.version(bundle.getSuppliedVersionString())
.format(bundle.getFormat());
return builder.build();
}
private static void populateBrooklynObjectMementoBuilder(BrooklynObject instance, AbstractMemento.Builder<?> builder) {
if (Proxy.isProxyClass(instance.getClass())) {
throw new IllegalStateException("Attempt to create memento from proxy "+instance+" (would fail with wrong type)");
}
OsgiClassPrefixer prefixer = new OsgiClassPrefixer();
Optional<String> typePrefix = prefixer.getPrefix(instance.getClass());
builder.id = instance.getId();
builder.displayName = instance.getDisplayName();
builder.catalogItemId = instance.getCatalogItemId();
builder.searchPath = instance.getCatalogItemIdSearchPath();
builder.type = (typePrefix.isPresent() ? typePrefix.get() : "") + instance.getClass().getName();
builder.typeClass = instance.getClass();
if (instance instanceof EntityAdjunct) {
builder.uniqueTag = ((EntityAdjunct)instance).getUniqueTag();
}
for (Object tag : instance.tags().getTags()) {
builder.tags.add(tag);
}
// CatalogItems return empty support, so this is safe even through they don't support relations
for (RelationshipType<?,? extends BrooklynObject> relationship: instance.relations().getRelationshipTypes()) {
@SuppressWarnings({ "unchecked", "rawtypes" })
Set relations = instance.relations().getRelations((RelationshipType)relationship);
Set<String> relationIds = Sets.newLinkedHashSet();
for (Object r: relations) relationIds.add( ((BrooklynObject)r).getId() );
// key is string name if known relationship type, otherwise the relationship type object
Object relTest = EntityRelations.lookup( ((BrooklynObjectInternal)instance).getManagementContext(), relationship.getRelationshipTypeName() );
Object rKey = relationship.equals(relTest) ? relationship.getRelationshipTypeName() : relationship;
builder.relations.put(rKey, relationIds);
}
}
/** @deprecated since 0.10.0; use {@link #configValueToPersistable(Object, BrooklynObject, String)} */ @Deprecated
protected static Object configValueToPersistable(Object value) {
return configValueToPersistable(value, null, null);
}
private static Set<String> WARNED_ON_PERSISTING_TASK_CONFIG = MutableSet.of();
protected static Object configValueToPersistable(Object value, BrooklynObject obj, String keyName) {
// TODO Swapping an attributeWhenReady task for the actual value, if completed.
// Long-term, want to just handle task-persistence properly.
if (value instanceof Task) {
Task<?> task = (Task<?>) value;
String contextName = "";
if (obj!=null) {
contextName = obj.getCatalogItemId();
if (Strings.isBlank(contextName)) contextName= obj.getDisplayName();
}
if (keyName!=null) {
if (Strings.isNonBlank(contextName)) contextName += ":";
contextName += keyName;
}
String message = "Persisting "+contextName+" - encountered task "+value;
Object result = null;
if (task.isDone() && !task.isError()) {
result = task.getUnchecked();
message += "; persisting result "+result;
} else {
// TODO how to record a completed but errored task?
message += "; persisting as null";
result = null;
}
if (WARNED_ON_PERSISTING_TASK_CONFIG.add(contextName)) {
log.warn(message+" (subsequent values for this key will be at null)");
} else {
log.debug(message);
}
return result;
}
return value;
}
}