/* * 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.catalog.internal; import java.util.Collection; import java.util.Collections; import java.util.Map; import java.util.Set; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.apache.brooklyn.api.catalog.CatalogItem; import org.apache.brooklyn.api.mgmt.rebind.RebindSupport; import org.apache.brooklyn.api.mgmt.rebind.mementos.CatalogItemMemento; import org.apache.brooklyn.config.ConfigKey; import org.apache.brooklyn.core.mgmt.rebind.BasicCatalogItemRebindSupport; import org.apache.brooklyn.core.objs.AbstractBrooklynObject; import org.apache.brooklyn.core.relations.EmptyRelationSupport; import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.core.flags.FlagUtils; import org.apache.brooklyn.util.core.flags.SetFromFlag; import org.slf4j.Logger; import org.slf4j.LoggerFactory; 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.Sets; public abstract class CatalogItemDtoAbstract<T, SpecT> extends AbstractBrooklynObject implements CatalogItem<T, SpecT> { private static Logger LOG = LoggerFactory.getLogger(CatalogItemDtoAbstract.class); private @SetFromFlag String symbolicName; private @SetFromFlag String version = BasicBrooklynCatalog.NO_VERSION; private @SetFromFlag String displayName; private @SetFromFlag String description; private @SetFromFlag String iconUrl; private @SetFromFlag String javaType; /**@deprecated since 0.7.0, left for deserialization backwards compatibility (including xml based catalog format) */ private @Deprecated @SetFromFlag String type; private @SetFromFlag String planYaml; private @SetFromFlag Collection<CatalogBundle> libraries; private @SetFromFlag Set<Object> tags = Sets.newLinkedHashSet(); private @SetFromFlag boolean deprecated; private @SetFromFlag boolean disabled; /** * @throws UnsupportedOperationException; Config not supported for catalog item. See {@link #getPlanYaml()}. */ @Override public ConfigurationSupportInternal config() { throw new UnsupportedOperationException(); } /** * @throws UnsupportedOperationException; subscriptions are not supported for catalog items */ @Override public SubscriptionSupportInternal subscriptions() { throw new UnsupportedOperationException(); } @Override public <U> U getConfig(ConfigKey<U> key) { return config().get(key); } @Override public <U> U setConfig(ConfigKey<U> key, U val) { return config().set(key, val); } @Override public String getId() { return getCatalogItemId(); } @Override public String getCatalogItemId() { return CatalogUtils.getVersionedId(getSymbolicName(), getVersion()); } @Override public String getJavaType() { if (javaType != null) return javaType; return type; } @Override @Deprecated public String getName() { return getDisplayName(); } @Override @Deprecated public String getRegisteredTypeName() { return getSymbolicName(); } @Override public String getDisplayName() { return displayName; } @Override public String getDescription() { return description; } @Override public String getIconUrl() { return iconUrl; } @Override public String getSymbolicName() { if (symbolicName != null) return symbolicName; return getJavaType(); } @Override public String getVersion() { // The property is set to NO_VERSION when the object is initialized so it's not supposed to be null ever. // But xstream doesn't call constructors when reading from the catalog.xml file which results in null value // for the version property. That's why we have to fix it in the getter. if (version != null) { return version; } else { return BasicBrooklynCatalog.NO_VERSION; } } @Override public boolean isDeprecated() { return deprecated; } @Override public void setDeprecated(boolean deprecated) { this.deprecated = deprecated; } @Override public boolean isDisabled() { return disabled; } @Override public void setDisabled(boolean disabled) { this.disabled = disabled; } @Nonnull @Override public Collection<CatalogBundle> getLibraries() { if (libraries != null) { return ImmutableList.copyOf(libraries); } else { return Collections.emptyList(); } } @Nullable @Override public String getPlanYaml() { return planYaml; } @Override public int hashCode() { return Objects.hashCode(symbolicName, planYaml, javaType, nullIfEmpty(libraries), version, getCatalogItemId()); } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; CatalogItemDtoAbstract<?,?> other = (CatalogItemDtoAbstract<?,?>) obj; if (!Objects.equal(symbolicName, other.symbolicName)) return false; if (!Objects.equal(planYaml, other.planYaml)) return false; if (!Objects.equal(javaType, other.javaType)) return false; if (!Objects.equal(nullIfEmpty(libraries), nullIfEmpty(other.libraries))) return false; if (!Objects.equal(getCatalogItemId(), other.getCatalogItemId())) return false; if (!Objects.equal(version, other.version)) return false; if (!Objects.equal(deprecated, other.deprecated)) return false; if (!Objects.equal(description, other.description)) return false; if (!Objects.equal(displayName, other.displayName)) return false; if (!Objects.equal(iconUrl, other.iconUrl)) return false; if (!Objects.equal(tags, other.tags)) return false; // 'type' not checked, because deprecated, // and in future we might want to allow it to be removed/blanked in some impls without affecting equality // (in most cases it is the same as symbolicName so doesn't matter) return true; } private static <T> Collection<T> nullIfEmpty(Collection<T> coll) { if (coll==null || coll.isEmpty()) return null; return coll; } @Override public String toString() { return getClass().getSimpleName()+"["+getId()+"/"+getDisplayName()+"]"; } @Override public abstract Class<SpecT> getSpecType(); transient CatalogXmlSerializer serializer; @Override public String toXmlString() { if (serializer==null) loadSerializer(); return serializer.toString(this); } private synchronized void loadSerializer() { if (serializer == null) { serializer = new CatalogXmlSerializer(); } } @Override public RebindSupport<CatalogItemMemento> getRebindSupport() { return new BasicCatalogItemRebindSupport(this); } /** * Overrides the parent so that relations are not visible. * @return an immutable empty relation support object; relations are not supported, * but we do not throw on access to enable reads in a consistent manner */ @Override public RelationSupportInternal<CatalogItem<T,SpecT>> relations() { return new EmptyRelationSupport<CatalogItem<T,SpecT>>(this); } @Override public void setDisplayName(String newName) { this.displayName = newName; } @Override protected CatalogItemDtoAbstract<T, SpecT> configure(Map<?, ?> flags) { FlagUtils.setFieldsFromFlags(flags, this); return this; } @Override public TagSupport tags() { return new BasicTagSupport(); } /* * Using a custom tag support class rather than the one in AbstractBrooklynObject because * when XStream unmarshals a catalog item with no tags (e.g. from any catalog.xml file) * super.tags will be null, and any call to getTags throws a NullPointerException on the * synchronized (tags) statement. It can't just be initialised here because super.tags is * final. */ private class BasicTagSupport implements TagSupport { private void setTagsIfNull() { // Possible if the class was unmarshalled by Xstream with no tags synchronized (CatalogItemDtoAbstract.this) { if (tags == null) { tags = Sets.newLinkedHashSet(); } } } @Nonnull @Override public Set<Object> getTags() { synchronized (CatalogItemDtoAbstract.this) { setTagsIfNull(); return ImmutableSet.copyOf(tags); } } @Override public boolean containsTag(Object tag) { synchronized (CatalogItemDtoAbstract.this) { setTagsIfNull(); return tags.contains(tag); } } @Override public boolean addTag(Object tag) { boolean result; synchronized (CatalogItemDtoAbstract.this) { setTagsIfNull(); result = tags.add(tag); } onTagsChanged(); return result; } @Override public boolean addTags(Iterable<?> newTags) { boolean result; synchronized (CatalogItemDtoAbstract.this) { setTagsIfNull(); result = Iterables.addAll(tags, newTags); } onTagsChanged(); return result; } @Override public boolean removeTag(Object tag) { boolean result; synchronized (CatalogItemDtoAbstract.this) { setTagsIfNull(); result = tags.remove(tag); } onTagsChanged(); return result; } } @Override @Deprecated public void setCatalogItemId(String id) { //no op, should be used by rebind code only } protected void setSymbolicName(String symbolicName) { this.symbolicName = symbolicName; } protected void setVersion(String version) { this.version = version; } protected void setDescription(String description) { this.description = description; } protected void setIconUrl(String iconUrl) { this.iconUrl = iconUrl; } protected void setJavaType(String javaType) { this.javaType = javaType; this.type = null; } protected void setPlanYaml(String planYaml) { this.planYaml = planYaml; } protected void setLibraries(Collection<CatalogBundle> libraries) { this.libraries = libraries; } protected void setTags(Set<Object> tags) { this.tags = tags; } protected void setSerializer(CatalogXmlSerializer serializer) { this.serializer = serializer; } /** * Parses an instance of CatalogLibrariesDto from the given List. Expects the list entries * to be either Strings or Maps of String -> String. Will skip items that are not. */ public static Collection<CatalogBundle> parseLibraries(Collection<?> possibleLibraries) { Collection<CatalogBundle> dto = MutableList.of(); for (Object object : possibleLibraries) { if (object instanceof Map) { Map<?, ?> entry = (Map<?, ?>) object; String name = stringValOrNull(entry, "name"); String version = stringValOrNull(entry, "version"); String url = stringValOrNull(entry, "url"); dto.add(new CatalogBundleDto(name, version, url)); } else if (object instanceof String) { String inlineRef = (String) object; final String name; final String version; final String url; //Infer reference type (heuristically) if (inlineRef.contains("/") || inlineRef.contains("\\")) { //looks like an url/file path name = null; version = null; url = inlineRef; } else if (CatalogUtils.looksLikeVersionedId(inlineRef)) { //looks like a name+version ref name = CatalogUtils.getSymbolicNameFromVersionedId(inlineRef); version = CatalogUtils.getVersionFromVersionedId(inlineRef); url = null; } else { //assume it to be relative url name = null; version = null; url = inlineRef; } dto.add(new CatalogBundleDto(name, version, url)); } else { LOG.debug("Unexpected entry in libraries list neither string nor map: " + object); } } return dto; } private static String stringValOrNull(Map<?, ?> map, String key) { Object val = map.get(key); return val != null ? String.valueOf(val) : null; } }