All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.brooklyn.api.internal.AbstractBrooklynObjectSpec Maven / Gradle / Ivy

/*
 * 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.api.internal;

import static com.google.common.base.Preconditions.checkNotNull;

import java.io.Serializable;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.brooklyn.api.mgmt.EntityManager;
import org.apache.brooklyn.api.mgmt.Task;
import org.apache.brooklyn.api.objs.BrooklynObject;
import org.apache.brooklyn.api.objs.SpecParameter;
import org.apache.brooklyn.config.ConfigKey;
import org.apache.brooklyn.config.ConfigKey.HasConfigKey;
import org.apache.brooklyn.util.collections.MutableList;
import org.apache.brooklyn.util.collections.MutableSet;
import org.apache.brooklyn.util.exceptions.Exceptions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.annotations.Beta;
import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;

/**
 * Defines a spec for creating a {@link BrooklynObject}.
 * 

* In addition to the contract defined by the code, * subclasses should provide a public static create(Class) * method to create an instance of the spec for the target type indicated by the argument. *

* The spec is then passed to type-specific methods, * e.g. {@link EntityManager#createEntity(org.apache.brooklyn.api.entity.EntitySpec)} * to create a managed instance of the target type. */ public abstract class AbstractBrooklynObjectSpec> implements Serializable { private static final long serialVersionUID = 3010955277740333030L; private static final Logger log = LoggerFactory.getLogger(AbstractBrooklynObjectSpec.class); private final Class type; private String displayName; private String catalogItemId; private Collection catalogItemIdSearchPath = MutableSet.of(); private Set tags = MutableSet.of(); private List> parameters = ImmutableList.of(); protected final Map flags = Maps.newLinkedHashMap(); protected final Map, Object> config = Maps.newLinkedHashMap(); protected AbstractBrooklynObjectSpec(Class type) { checkValidType(type); this.type = type; } @SuppressWarnings("unchecked") protected SpecT self() { return (SpecT) this; } @Override public String toString() { return MoreObjects.toStringHelper(this).omitNullValues() .add("type", type) .add("displayName", displayName) .toString(); } protected abstract void checkValidType(Class type); public SpecT displayName(String val) { displayName = val; return self(); } /** * Set the catalog item ID that defined this object; * since https://issues.apache.org/jira/browse/BROOKLYN-445 this must no longer be used to indicate * a caller-context catalog item that should be used for search purposes; * instead use {@link #catalogItemIdAndSearchPath}. */ public SpecT catalogItemId(String val) { catalogItemId = val; return self(); } /** * Set the immediate catalog item ID of this object, and the search path of other catalog items used to define it. */ public synchronized SpecT catalogItemIdAndSearchPath(String catalogItemId, Collection searchPath) { // TODO if ID is null should we really ignore search path? if (catalogItemId != null) { catalogItemId(catalogItemId); synchronized (catalogItemIdSearchPath) { catalogItemIdSearchPath.clear(); if (searchPath!=null) { catalogItemIdSearchPath.addAll(searchPath); } } } return self(); } public synchronized SpecT addSearchPath(List searchPath) { if (searchPath!=null) { synchronized (catalogItemIdSearchPath) { catalogItemIdSearchPath.addAll(searchPath); } } return self(); } /** * @deprecated since 0.11.0, most callers would want {@link #stackCatalogItemId(String)} instead, though semantics are different */ @Deprecated @Beta public SpecT catalogItemIdIfNotNull(String val) { if (val!=null) { catalogItemId(val); } return self(); } protected Object readResolve() { if (catalogItemIdSearchPath == null) { catalogItemIdSearchPath = MutableList.of(); } return this; } /** * Adds (stacks) the catalog item id of a wrapping specification. * Does nothing if the value is null. * If the value is not null, and is not the same as the current * catalogItemId, then the *current* catalogItemId will * be moved to the start of the search path, and the value supplied * as the parameter here will be set as the new catalogItemId. *

* Used when we want to collect IDs of items that extend other items, so that *all* can be searched. * e.g. if R3 extends R2 which extends R1 any one of these might supply config keys * referencing resources or types in their local bundles. */ @Beta public SpecT stackCatalogItemId(String val) { if (null != val) { if (null != catalogItemId && !catalogItemId.equals(val)) { synchronized (catalogItemIdSearchPath) { Set newPath = MutableSet.of(); newPath.add(catalogItemId); newPath.addAll(catalogItemIdSearchPath); catalogItemIdSearchPath.clear(); catalogItemIdSearchPath.addAll(newPath); } } catalogItemId(val); } return self(); } public SpecT tag(Object tag) { tags.add(tag); return self(); } /** * adds the given tags */ public SpecT tags(Iterable tagsToAdd) { return tagsAdd(tagsToAdd); } /** * adds the given tags */ public SpecT tagsAdd(Iterable tagsToAdd) { Iterables.addAll(this.tags, tagsToAdd); return self(); } /** * replaces tags with the given */ public SpecT tagsReplace(Iterable tagsToReplace) { this.tags.clear(); Iterables.addAll(this.tags, tagsToReplace); return self(); } // TODO which semantics are correct? replace has been the behaviour; // add breaks tests and adds unwanted parameters, // but replacing will cause some desired parameters to be lost. // i (AH) think ideally the caller should remove any parameters which // have been defined as config keys, and then add the others; // or actually we should always add, since this is really defining the config keys, // and maybe extend the SpecParameter object to be able to advertise whether // it is a CatalogConfig or merely a config key, maybe introducing displayable, or even priority // (but note part of the reason for CatalogConfig.priority is that java reflection doesn't preserve field order) . // see also comments on the camp SpecParameterResolver. // probably the thing to do is deprecate the ambiguous method in favour of an explicit @Beta public SpecT parameters(Iterable> parameters) { return parametersReplace(parameters); } /** * adds the given parameters, new ones first so they dominate subsequent ones */ @Beta public SpecT parametersAdd(Iterable> parameters) { // parameters follows immutable pattern, unlike the other fields Set> params = MutableSet.>copyOf(parameters); Set> current = MutableSet.>copyOf(this.parameters); current.removeAll(params); return parametersReplace(ImmutableList.>builder() .addAll(params) .addAll(current) .build()); } /** * replaces parameters with the given */ @Beta public SpecT parametersReplace(Iterable> parameters) { this.parameters = ImmutableList.copyOf(checkNotNull(parameters, "parameters")); return self(); } /** * @return The type (often an interface) this spec represents and which will be instantiated from it */ public Class getType() { return type; } /** * @return The display name of the object */ public final String getDisplayName() { return displayName; } /** Same as {@link BrooklynObject#getCatalogItemId()}. */ public final String getCatalogItemId() { return catalogItemId; } /** Same as {@link BrooklynObject#getCatalogItemIdSearchPath()}. */ public final List getCatalogItemIdSearchPath() { synchronized (catalogItemIdSearchPath) { return ImmutableList.copyOf(catalogItemIdSearchPath); } } public final Set getTags() { return ImmutableSet.copyOf(tags); } /** * A list of configuration options that the entity supports. */ public final List> getParameters() { //Could be null after rebind if (parameters != null) { return ImmutableList.copyOf(parameters); } else { return ImmutableList.of(); } } // TODO Duplicates method in BasicEntityTypeRegistry and InternalEntityFactory.isNewStyleEntity protected final void checkIsNewStyleImplementation(Class implClazz) { try { implClazz.getConstructor(new Class[0]); } catch (NoSuchMethodException e) { throw new IllegalStateException("Implementation " + implClazz + " must have a no-argument constructor"); } catch (SecurityException e) { throw Exceptions.propagate(e); } if (implClazz.isInterface()) throw new IllegalStateException("Implementation " + implClazz + " is an interface, but must be a non-abstract class"); if (Modifier.isAbstract(implClazz.getModifiers())) throw new IllegalStateException("Implementation " + implClazz + " is abstract, but must be a non-abstract class"); } // TODO Duplicates method in BasicEntityTypeRegistry protected final void checkIsImplementation(Class val, Class requiredInterface) { if (!requiredInterface.isAssignableFrom(val)) throw new IllegalStateException("Implementation " + val + " does not implement " + requiredInterface.getName()); if (val.isInterface()) throw new IllegalStateException("Implementation " + val + " is an interface, but must be a non-abstract class"); if (Modifier.isAbstract(val.getModifiers())) throw new IllegalStateException("Implementation " + val + " is abstract, but must be a non-abstract class"); } protected SpecT copyFrom(SpecT otherSpec) { return displayName(otherSpec.getDisplayName()) .configure(otherSpec.getConfig()) .configure(otherSpec.getFlags()) .tags(otherSpec.getTags()) .catalogItemIdAndSearchPath(otherSpec.getCatalogItemId(), otherSpec.getCatalogItemIdSearchPath()) .parameters(otherSpec.getParameters()); } @Override public boolean equals(Object obj) { if (obj == null) return false; if (!obj.getClass().equals(getClass())) return false; AbstractBrooklynObjectSpec other = (AbstractBrooklynObjectSpec) obj; if (!Objects.equal(getDisplayName(), other.getDisplayName())) return false; if (!Objects.equal(getCatalogItemId(), other.getCatalogItemId())) return false; if (!Objects.equal(getCatalogItemIdSearchPath(), other.getCatalogItemIdSearchPath())) return false; if (!Objects.equal(getType(), other.getType())) return false; if (!Objects.equal(getTags(), other.getTags())) return false; if (!Objects.equal(getConfig(), other.getConfig())) return false; if (!Objects.equal(getFlags(), other.getFlags())) return false; if (!Objects.equal(getParameters(), other.getParameters())) return false; return true; } @Override public int hashCode() { return Objects.hashCode(getCatalogItemIdSearchPath(), getDisplayName(), getType(), getTags()); } /** * strings inserted as flags, config keys inserted as config keys; * if you want to force one or the other, create a ConfigBag and convert to the appropriate map type */ public SpecT configure(Map val) { if (val == null) { log.warn("Null supplied when configuring " + this); log.debug("Source for null supplied when configuring " + this, new Throwable("Source for null supplied when configuring " + this)); return self(); } for (Map.Entry entry : val.entrySet()) { if (entry.getKey() == null) throw new NullPointerException("Null key not permitted"); if (entry.getKey() instanceof CharSequence) flags.put(entry.getKey().toString(), entry.getValue()); else if (entry.getKey() instanceof ConfigKey) config.put((ConfigKey) entry.getKey(), entry.getValue()); else if (entry.getKey() instanceof HasConfigKey) config.put(((HasConfigKey) entry.getKey()).getConfigKey(), entry.getValue()); else { log.warn("Spec " + this + " ignoring unknown config key " + entry.getKey()); } } return self(); } public SpecT configure(CharSequence key, Object val) { flags.put(checkNotNull(key, "key").toString(), val); return self(); } public SpecT configure(ConfigKey key, V val) { config.put(checkNotNull(key, "key"), val); return self(); } public SpecT configureIfNotNull(ConfigKey key, V val) { return (val != null) ? configure(key, val) : self(); } public SpecT configure(ConfigKey key, Task val) { config.put(checkNotNull(key, "key"), val); return self(); } public SpecT configure(HasConfigKey key, V val) { config.put(checkNotNull(key, "key").getConfigKey(), val); return self(); } public SpecT configure(HasConfigKey key, Task val) { config.put(checkNotNull(key, "key").getConfigKey(), val); return self(); } public SpecT removeConfig(ConfigKey key) { config.remove(checkNotNull(key, "key")); return self(); } public SpecT removeFlag(String key) { flags.remove(checkNotNull(key, "key")); return self(); } /** * Clears the config map, removing any config previously set. */ public void clearConfig() { config.clear(); } /** * @return Read-only construction flags * @see SetFromFlag declarations on the policy type */ public Map getFlags() { return Collections.unmodifiableMap(flags); } /** * @return Read-only configuration values */ public Map, Object> getConfig() { return Collections.unmodifiableMap(config); } }