/* * 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 javax.annotation.Nullable; import org.apache.brooklyn.api.catalog.BrooklynCatalog; import org.apache.brooklyn.api.catalog.CatalogItem; import org.apache.brooklyn.api.catalog.CatalogItem.CatalogBundle; import org.apache.brooklyn.api.entity.Entity; import org.apache.brooklyn.api.mgmt.ManagementContext; import org.apache.brooklyn.api.mgmt.classloading.BrooklynClassLoadingContext; import org.apache.brooklyn.api.objs.BrooklynObject; import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry; import org.apache.brooklyn.api.typereg.OsgiBundleWithUrl; import org.apache.brooklyn.api.typereg.RegisteredType; import org.apache.brooklyn.core.BrooklynLogging; import org.apache.brooklyn.core.catalog.internal.BasicBrooklynCatalog.BrooklynLoaderTracker; import org.apache.brooklyn.core.entity.EntityInternal; import org.apache.brooklyn.core.mgmt.classloading.BrooklynClassLoadingContextSequential; import org.apache.brooklyn.core.mgmt.classloading.JavaBrooklynClassLoadingContext; import org.apache.brooklyn.core.mgmt.classloading.OsgiBrooklynClassLoadingContext; import org.apache.brooklyn.core.mgmt.ha.OsgiManager; import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal; import org.apache.brooklyn.core.mgmt.rebind.RebindManagerImpl.RebindTracker; import org.apache.brooklyn.core.objs.BrooklynObjectInternal; import org.apache.brooklyn.core.typereg.RegisteredTypeLoadingContexts; import org.apache.brooklyn.core.typereg.RegisteredTypePredicates; import org.apache.brooklyn.core.typereg.RegisteredTypes; import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.text.Strings; import org.apache.brooklyn.util.time.Time; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.annotations.Beta; import com.google.common.base.Joiner; import com.google.common.base.Preconditions; import com.google.common.base.Stopwatch; public class CatalogUtils { private static final Logger log = LoggerFactory.getLogger(CatalogUtils.class); public static final char VERSION_DELIMITER = ':'; public static BrooklynClassLoadingContext newClassLoadingContext(ManagementContext mgmt, CatalogItem<?, ?> item) { // TODO getLibraries() should never be null but sometimes it is still // e.g. run CatalogResourceTest without the above check if (item.getLibraries() == null) { log.debug("CatalogItemDtoAbstract.getLibraries() is null.", new Exception("Trace for null CatalogItemDtoAbstract.getLibraries()")); } return newClassLoadingContext(mgmt, item.getId(), item.getLibraries()); } public static BrooklynClassLoadingContext newClassLoadingContext(ManagementContext mgmt, RegisteredType item) { return newClassLoadingContext(mgmt, item.getId(), item.getLibraries(), null); } /** made @Beta in 0.9.0 because we're not sure to what extent to support stacking loaders; * only a couple places currently rely on such stacking, in general the item and the bundles *are* the context, * and life gets hard if we support complex stacking! */ @Beta public static BrooklynClassLoadingContext newClassLoadingContext(ManagementContext mgmt, RegisteredType item, BrooklynClassLoadingContext loader) { return newClassLoadingContext(mgmt, item.getId(), item.getLibraries(), loader); } public static BrooklynClassLoadingContext getClassLoadingContext(Entity entity) { ManagementContext mgmt = ((EntityInternal)entity).getManagementContext(); String catId = entity.getCatalogItemId(); if (Strings.isBlank(catId)) return JavaBrooklynClassLoadingContext.create(mgmt); Maybe<RegisteredType> cat = RegisteredTypes.tryValidate(mgmt.getTypeRegistry().get(catId), RegisteredTypeLoadingContexts.spec(Entity.class)); if (cat.isNull()) { log.warn("Cannot load "+catId+" to get classloader for "+entity+"; will try with standard loader, but might fail subsequently"); return JavaBrooklynClassLoadingContext.create(mgmt); } return newClassLoadingContext(mgmt, cat.get()); } public static BrooklynClassLoadingContext newClassLoadingContext(@Nullable ManagementContext mgmt, String catalogItemId, Collection<? extends OsgiBundleWithUrl> libraries) { return newClassLoadingContext(mgmt, catalogItemId, libraries, null); } @Deprecated /** @deprecated since 0.9.0; becoming private because we should now always have a registered type callers can pass instead of the catalog item id */ public static BrooklynClassLoadingContext newClassLoadingContext(@Nullable ManagementContext mgmt, String catalogItemId, Collection<? extends OsgiBundleWithUrl> libraries, BrooklynClassLoadingContext loader) { BrooklynClassLoadingContextSequential result = new BrooklynClassLoadingContextSequential(mgmt); if (libraries!=null && !libraries.isEmpty()) { result.add(new OsgiBrooklynClassLoadingContext(mgmt, catalogItemId, libraries)); } if (loader !=null) { // TODO determine whether to support stacking result.add(loader); } BrooklynClassLoadingContext threadLocalLoader = BrooklynLoaderTracker.getLoader(); if (threadLocalLoader != null) { // TODO and determine if this is needed/wanted result.add(threadLocalLoader); } result.addSecondary(JavaBrooklynClassLoadingContext.create(mgmt)); return result; } /** * @deprecated since 0.7.0 only for legacy catalog items which provide a non-osgi loader; see {@link #newDefault(ManagementContext)} */ @Deprecated public static BrooklynClassLoadingContext newClassLoadingContext(@Nullable ManagementContext mgmt, String catalogItemId, Collection<CatalogBundle> libraries, ClassLoader customClassLoader) { BrooklynClassLoadingContextSequential result = new BrooklynClassLoadingContextSequential(mgmt); if (libraries!=null && !libraries.isEmpty()) { result.add(new OsgiBrooklynClassLoadingContext(mgmt, catalogItemId, libraries)); } BrooklynClassLoadingContext loader = BrooklynLoaderTracker.getLoader(); if (loader != null) { result.add(loader); } result.addSecondary(JavaBrooklynClassLoadingContext.create(mgmt, customClassLoader)); return result; } /** * Registers all bundles with the management context's OSGi framework. */ public static void installLibraries(ManagementContext managementContext, @Nullable Collection<CatalogBundle> libraries) { if (libraries == null) return; ManagementContextInternal mgmt = (ManagementContextInternal) managementContext; if (!libraries.isEmpty()) { Maybe<OsgiManager> osgi = mgmt.getOsgiManager(); if (osgi.isAbsent()) { throw new IllegalStateException("Unable to load bundles "+libraries+" because OSGi is not running."); } if (log.isDebugEnabled()) logDebugOrTraceIfRebinding(log, "Loading bundles in {}: {}", new Object[] {managementContext, Joiner.on(", ").join(libraries)}); Stopwatch timer = Stopwatch.createStarted(); for (CatalogBundle bundleUrl : libraries) { osgi.get().registerBundle(bundleUrl); } if (log.isDebugEnabled()) logDebugOrTraceIfRebinding(log, "Registered {} bundles in {}", new Object[]{libraries.size(), Time.makeTimeStringRounded(timer)}); } } /** Scans the given {@link BrooklynClassLoadingContext} to detect what catalog item id is in effect. */ public static String getCatalogItemIdFromLoader(BrooklynClassLoadingContext loader) { if (loader instanceof OsgiBrooklynClassLoadingContext) { return ((OsgiBrooklynClassLoadingContext)loader).getCatalogItemId(); } else { return null; } } public static void setCatalogItemIdOnAddition(Entity entity, BrooklynObject itemBeingAdded) { if (entity.getCatalogItemId()!=null) { if (itemBeingAdded.getCatalogItemId()==null) { if (log.isDebugEnabled()) BrooklynLogging.log(log, BrooklynLogging.levelDebugOrTraceIfReadOnly(entity), "Catalog item addition: "+entity+" from "+entity.getCatalogItemId()+" applying its catalog item ID to "+itemBeingAdded); ((BrooklynObjectInternal)itemBeingAdded).setCatalogItemId(entity.getCatalogItemId()); } else { if (!itemBeingAdded.getCatalogItemId().equals(entity.getCatalogItemId())) { // not a problem, but something to watch out for log.debug("Cross-catalog item detected: "+entity+" from "+entity.getCatalogItemId()+" has "+itemBeingAdded+" from "+itemBeingAdded.getCatalogItemId()); } } } else if (itemBeingAdded.getCatalogItemId()!=null) { if (log.isDebugEnabled()) BrooklynLogging.log(log, BrooklynLogging.levelDebugOrTraceIfReadOnly(entity), "Catalog item addition: "+entity+" without catalog item ID has "+itemBeingAdded+" from "+itemBeingAdded.getCatalogItemId()); } } @Beta public static void logDebugOrTraceIfRebinding(Logger log, String message, Object ...args) { if (RebindTracker.isRebinding()) log.trace(message, args); else log.debug(message, args); } public static boolean looksLikeVersionedId(String versionedId) { if (versionedId==null) return false; int fi = versionedId.indexOf(VERSION_DELIMITER); if (fi<0) return false; int li = versionedId.lastIndexOf(VERSION_DELIMITER); if (li!=fi) { // if multiple colons, we say it isn't a versioned reference; the prefix in that case must understand any embedded versioning scheme // this fixes the case of: http://localhost:8080 return false; } String candidateVersion = versionedId.substring(li+1); if (!candidateVersion.matches("[0-9]+(|(\\.|_).*)")) { // version must start with a number, followed if by anything with full stop or underscore before any other characters // e.g. foo:1 or foo:1.1 or foo:1_SNAPSHOT all supported, but not e.g. foo:bar (or chef:cookbook or docker:my/image) return false; } return true; } /** @deprecated since 0.9.0 use {@link #getSymbolicNameFromVersionedId(String)} */ // all uses removed @Deprecated public static String getIdFromVersionedId(String versionedId) { return getSymbolicNameFromVersionedId(versionedId); } public static String getSymbolicNameFromVersionedId(String versionedId) { if (versionedId == null) return null; int versionDelimiterPos = versionedId.lastIndexOf(VERSION_DELIMITER); if (versionDelimiterPos != -1) { return versionedId.substring(0, versionDelimiterPos); } else { return null; } } public static String getVersionFromVersionedId(String versionedId) { if (versionedId == null) return null; int versionDelimiterPos = versionedId.lastIndexOf(VERSION_DELIMITER); if (versionDelimiterPos != -1) { return versionedId.substring(versionDelimiterPos+1); } else { return null; } } public static String getVersionedId(String id, String version) { // TODO null checks return id + VERSION_DELIMITER + version; } /** @deprecated since 0.9.0 use {@link BrooklynTypeRegistry#get(String, org.apache.brooklyn.api.typereg.BrooklynTypeRegistry.RegisteredTypeKind, Class)} */ // only a handful of items remaining, and those require a CatalogItem public static CatalogItem<?, ?> getCatalogItemOptionalVersion(ManagementContext mgmt, String versionedId) { if (versionedId == null) return null; if (looksLikeVersionedId(versionedId)) { String id = getSymbolicNameFromVersionedId(versionedId); String version = getVersionFromVersionedId(versionedId); return mgmt.getCatalog().getCatalogItem(id, version); } else { return mgmt.getCatalog().getCatalogItem(versionedId, BrooklynCatalog.DEFAULT_VERSION); } } public static boolean isBestVersion(ManagementContext mgmt, CatalogItem<?,?> item) { RegisteredType best = RegisteredTypes.getBestVersion(mgmt.getTypeRegistry().getMatching( RegisteredTypePredicates.symbolicName(item.getSymbolicName()))); if (best==null) return false; return (best.getVersion().equals(item.getVersion())); } /** @deprecated since 0.9.0 use {@link BrooklynTypeRegistry#get(String, org.apache.brooklyn.api.typereg.BrooklynTypeRegistry.RegisteredTypeKind, Class)} */ // only a handful of items remaining, and those require a CatalogItem public static <T,SpecT> CatalogItem<T, SpecT> getCatalogItemOptionalVersion(ManagementContext mgmt, Class<T> type, String versionedId) { if (looksLikeVersionedId(versionedId)) { String id = getSymbolicNameFromVersionedId(versionedId); String version = getVersionFromVersionedId(versionedId); return mgmt.getCatalog().getCatalogItem(type, id, version); } else { return mgmt.getCatalog().getCatalogItem(type, versionedId, BrooklynCatalog.DEFAULT_VERSION); } } /** @deprecated since it was introduced in 0.9.0; TBD where this should live */ public static void setDeprecated(ManagementContext mgmt, String symbolicNameAndOptionalVersion, boolean newValue) { RegisteredType item = mgmt.getTypeRegistry().get(symbolicNameAndOptionalVersion); Preconditions.checkNotNull(item, "No such item: " + symbolicNameAndOptionalVersion); setDeprecated(mgmt, item.getSymbolicName(), item.getVersion(), newValue); } /** @deprecated since it was introduced in 0.9.0; TBD where this should live */ public static void setDisabled(ManagementContext mgmt, String symbolicNameAndOptionalVersion, boolean newValue) { RegisteredType item = mgmt.getTypeRegistry().get(symbolicNameAndOptionalVersion); Preconditions.checkNotNull(item, "No such item: "+symbolicNameAndOptionalVersion); setDisabled(mgmt, item.getSymbolicName(), item.getVersion(), newValue); } /** @deprecated since it was introduced in 0.9.0; TBD where this should live */ @Deprecated public static void setDeprecated(ManagementContext mgmt, String symbolicName, String version, boolean newValue) { CatalogItem<?, ?> item = mgmt.getCatalog().getCatalogItem(symbolicName, version); Preconditions.checkNotNull(item, "No such item: "+symbolicName+" v "+version); item.setDeprecated(newValue); mgmt.getCatalog().persist(item); } /** @deprecated since it was introduced in 0.9.0; TBD where this should live */ @Deprecated public static void setDisabled(ManagementContext mgmt, String symbolicName, String version, boolean newValue) { CatalogItem<?, ?> item = mgmt.getCatalog().getCatalogItem(symbolicName, version); Preconditions.checkNotNull(item, "No such item: "+symbolicName+" v "+version); item.setDisabled(newValue); mgmt.getCatalog().persist(item); } }