| /* |
| * 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; |
| |
| import static com.google.common.base.Preconditions.checkNotNull; |
| |
| import java.util.*; |
| import java.util.function.Supplier; |
| import static org.apache.brooklyn.core.BrooklynFeatureEnablement.FEATURE_AUTO_FIX_CATALOG_REF_ON_REBIND; |
| import static org.apache.brooklyn.core.BrooklynFeatureEnablement.FEATURE_BACKWARDS_COMPATIBILITY_INFER_CATALOG_ITEM_ON_REBIND; |
| import static org.apache.brooklyn.core.catalog.internal.CatalogUtils.newClassLoadingContextForCatalogItems; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.util.concurrent.Semaphore; |
| import java.util.concurrent.atomic.AtomicBoolean; |
| import java.util.concurrent.atomic.AtomicInteger; |
| import java.util.stream.Collectors; |
| |
| import com.google.common.annotations.Beta; |
| import org.apache.brooklyn.api.catalog.BrooklynCatalog; |
| 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.location.Location; |
| import org.apache.brooklyn.api.mgmt.classloading.BrooklynClassLoadingContext; |
| import org.apache.brooklyn.api.mgmt.ha.ManagementNodeState; |
| import org.apache.brooklyn.api.mgmt.rebind.RebindContext; |
| import org.apache.brooklyn.api.mgmt.rebind.RebindExceptionHandler; |
| import org.apache.brooklyn.api.mgmt.rebind.RebindSupport; |
| import org.apache.brooklyn.api.mgmt.rebind.mementos.BrooklynMemento; |
| import org.apache.brooklyn.api.mgmt.rebind.mementos.BrooklynMementoManifest; |
| import org.apache.brooklyn.api.mgmt.rebind.mementos.BrooklynMementoManifest.EntityMementoManifest; |
| import org.apache.brooklyn.api.mgmt.rebind.mementos.BrooklynMementoPersister; |
| import org.apache.brooklyn.api.mgmt.rebind.mementos.BrooklynMementoRawData; |
| 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.mgmt.rebind.mementos.TreeNode; |
| import org.apache.brooklyn.api.objs.BrooklynObject; |
| import org.apache.brooklyn.api.objs.BrooklynObjectType; |
| import org.apache.brooklyn.api.objs.EntityAdjunct; |
| import org.apache.brooklyn.api.policy.Policy; |
| import org.apache.brooklyn.api.sensor.Enricher; |
| import org.apache.brooklyn.api.sensor.Feed; |
| import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry; |
| import org.apache.brooklyn.api.typereg.ManagedBundle; |
| import org.apache.brooklyn.api.typereg.RegisteredType; |
| import org.apache.brooklyn.core.BrooklynFeatureEnablement; |
| import org.apache.brooklyn.core.BrooklynLogging; |
| import org.apache.brooklyn.core.BrooklynLogging.LoggingLevel; |
| import org.apache.brooklyn.core.catalog.internal.CatalogInitialization; |
| import org.apache.brooklyn.core.catalog.internal.CatalogInitialization.InstallableManagedBundle; |
| import org.apache.brooklyn.core.catalog.internal.CatalogUtils; |
| import org.apache.brooklyn.core.enricher.AbstractEnricher; |
| import org.apache.brooklyn.core.entity.AbstractApplication; |
| import org.apache.brooklyn.core.entity.AbstractEntity; |
| import org.apache.brooklyn.core.entity.EntityAdjuncts; |
| import org.apache.brooklyn.core.entity.EntityInternal; |
| import org.apache.brooklyn.core.feed.AbstractFeed; |
| import org.apache.brooklyn.core.location.AbstractLocation; |
| import org.apache.brooklyn.core.location.internal.LocationInternal; |
| import org.apache.brooklyn.core.mgmt.BrooklynTaskTags; |
| import org.apache.brooklyn.core.mgmt.classloading.BrooklynClassLoadingContextSequential; |
| import org.apache.brooklyn.core.mgmt.classloading.JavaBrooklynClassLoadingContext; |
| import org.apache.brooklyn.core.mgmt.ha.OsgiManager; |
| import org.apache.brooklyn.core.mgmt.internal.BrooklynObjectManagementMode; |
| import org.apache.brooklyn.core.mgmt.internal.BrooklynObjectManagerInternal; |
| import org.apache.brooklyn.core.mgmt.internal.EntityManagerInternal; |
| import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext; |
| import org.apache.brooklyn.core.mgmt.internal.LocationManagerInternal; |
| import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal; |
| import org.apache.brooklyn.core.mgmt.internal.ManagementTransitionMode; |
| import org.apache.brooklyn.core.mgmt.persist.DeserializingClassRenamesProvider; |
| import org.apache.brooklyn.core.mgmt.persist.PersistenceActivityMetrics; |
| import org.apache.brooklyn.core.mgmt.rebind.RebindManagerImpl.RebindTracker; |
| import org.apache.brooklyn.core.objs.AbstractBrooklynObject; |
| import org.apache.brooklyn.core.objs.BrooklynObjectInternal; |
| import org.apache.brooklyn.core.objs.proxy.*; |
| import org.apache.brooklyn.core.policy.AbstractPolicy; |
| import org.apache.brooklyn.core.typereg.BasicManagedBundle; |
| import org.apache.brooklyn.core.typereg.BundleUpgradeParser.CatalogUpgrades; |
| import org.apache.brooklyn.core.typereg.RegisteredTypeNaming; |
| import org.apache.brooklyn.util.collections.MutableList; |
| import org.apache.brooklyn.util.collections.MutableMap; |
| import org.apache.brooklyn.util.core.ClassLoaderUtils; |
| import org.apache.brooklyn.util.core.flags.FlagUtils; |
| import org.apache.brooklyn.util.core.task.Tasks; |
| import org.apache.brooklyn.util.exceptions.Exceptions; |
| import org.apache.brooklyn.util.guava.Maybe; |
| import org.apache.brooklyn.util.javalang.Reflections; |
| import org.apache.brooklyn.util.osgi.VersionedName; |
| import org.apache.brooklyn.util.stream.InputStreamSource; |
| import org.apache.brooklyn.util.text.Strings; |
| import org.apache.brooklyn.util.time.Duration; |
| import org.apache.brooklyn.util.time.Time; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import com.google.common.base.Preconditions; |
| import com.google.common.base.Stopwatch; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.Lists; |
| import com.google.common.collect.Maps; |
| import com.google.common.collect.Sets; |
| |
| /** |
| * Multi-phase deserialization: |
| * |
| * <ul> |
| * <li> 1. load the manifest files and populate the summaries (ID+type) in {@link BrooklynMementoManifest} |
| * <li> 2. install bundles, instantiate and reconstruct catalog items |
| * <li> 3. instantiate entities+locations -- so that inter-entity references can subsequently |
| * be set during deserialize (and entity config/state is set). |
| * <li> 4. deserialize the manifests to instantiate the mementos |
| * <li> 5. instantiate policies+enrichers+feeds |
| * (could probably merge this with (3), depending how they are implemented) |
| * <li> 6. reconstruct the locations, policies, etc, then finally entities -- setting all fields and then calling |
| * {@link RebindSupport#reconstruct(RebindContext, Memento)} |
| * <li> 7. associate policies+enrichers+feeds to all the entities |
| * <li> 8. manage the entities |
| * </ul> |
| * <p> |
| * If underlying data-store is changed between first and second manifest read (e.g. to add an |
| * entity), then second phase might try to reconstitute an entity that has not been put in |
| * the rebindContext. This should not affect normal production usage, because rebind is run |
| * against a data-store that is not being written to by other brooklyn instance(s). |
| * But clearly it would be desirable to have better locking possible against the backing store. |
| * |
| * <p> |
| * When rebinding to code in OSGi bundles, thecatalog item id context is inferred as follows: |
| * most of the time the creator will be passing "my catalog item id" |
| * (or API could also take "BrooklynObject me" as a creation context and the |
| * receiver query the creator's catalog item id) |
| * look at the context entity of Tasks.current() (if set) |
| * propagate the catalog item id when doing setEntity, addChild |
| * when none of the above work (or they are wrong) let the user specify the catalog item |
| * <p> |
| * Precedence of setting the catalog item ID: |
| * 1. User-supplied catalog item ID. |
| * 2. Creating from a catalog item - all items resolved during the creation of a spec |
| * from a catalog item receive the catalog item's ID as context. |
| * 3. When using the Java API for creating specs get the catalog item ID from the |
| * context entity of the Tasks.current() task. |
| * 4. Propagate the context catalog item ID to children, adjuncts if they don't have one already. |
| */ |
| public abstract class RebindIteration { |
| |
| private static final Logger LOG = LoggerFactory.getLogger(RebindIteration.class); |
| |
| protected final RebindManagerImpl rebindManager; |
| |
| protected final ClassLoader classLoader; |
| protected final RebindExceptionHandler exceptionHandler; |
| protected final ManagementNodeState mode; |
| protected final ManagementContextInternal managementContext; |
| |
| protected final Semaphore rebindActive; |
| protected final AtomicInteger readOnlyRebindCount; |
| protected final PersistenceActivityMetrics rebindMetrics; |
| protected final BrooklynMementoPersister persistenceStoreAccess; |
| |
| protected final AtomicBoolean iterationStarted = new AtomicBoolean(); |
| protected final RebindContextImpl rebindContext; |
| protected final Reflections reflections; |
| protected final BrooklynObjectInstantiator instantiator; |
| |
| // populated in the course of a run |
| |
| // set on run start |
| |
| protected Stopwatch timer; |
| /** |
| * phase is used to ensure our steps are run as we've expected, and documented (in javadoc at top). |
| * it's worth the extra effort due to the complication and the subtleties. |
| */ |
| protected int phase = 0; |
| |
| // set in first phase |
| |
| protected BrooklynMementoRawData mementoRawData; |
| protected BrooklynMementoManifest mementoManifest; |
| protected Boolean overwritingMaster; |
| protected Boolean isEmpty; |
| |
| // set later on |
| |
| protected BrooklynMemento memento; |
| |
| // set near the end |
| |
| protected List<Application> applications; |
| |
| public RebindIteration(RebindManagerImpl rebindManager, |
| ManagementNodeState mode, |
| ClassLoader classLoader, RebindExceptionHandler exceptionHandler, |
| Semaphore rebindActive, AtomicInteger readOnlyRebindCount, PersistenceActivityMetrics rebindMetrics, BrooklynMementoPersister persistenceStoreAccess |
| ) { |
| // NB: there is no particularly deep meaning in what is passed in vs what is looked up from the RebindManager which calls us |
| // (this is simply a refactoring of previous code to a new class) |
| |
| this.rebindManager = rebindManager; |
| |
| this.mode = mode; |
| this.classLoader = checkNotNull(classLoader, "classLoader"); |
| this.exceptionHandler = checkNotNull(exceptionHandler, "exceptionHandler"); |
| |
| this.rebindActive = rebindActive; |
| this.readOnlyRebindCount = readOnlyRebindCount; |
| this.rebindMetrics = rebindMetrics; |
| this.persistenceStoreAccess = persistenceStoreAccess; |
| |
| managementContext = rebindManager.getManagementContext(); |
| rebindContext = new RebindContextImpl(managementContext, exceptionHandler, classLoader); |
| reflections = new Reflections(classLoader).applyClassRenames(DeserializingClassRenamesProvider.INSTANCE.loadDeserializingMapping()); |
| instantiator = new BrooklynObjectInstantiator(classLoader, rebindContext, reflections); |
| |
| if (mode == ManagementNodeState.HOT_STANDBY || mode == ManagementNodeState.HOT_BACKUP) { |
| rebindContext.setAllReadOnly(); |
| } else { |
| Preconditions.checkState(mode == ManagementNodeState.MASTER, "Must be either master or read only to rebind (mode " + mode + ")"); |
| } |
| } |
| |
| public List<Application> getApplications() { |
| return applications; |
| } |
| |
| RebindContextImpl getRebindContext() { |
| return rebindContext; |
| } |
| |
| protected void doRun() throws Exception { |
| if (readOnlyRebindCount.get() > 1) { |
| // prevent leaking |
| rebindManager.stopEntityTasksAndCleanUp("before next read-only rebind", Duration.seconds(10), Duration.seconds(20)); |
| } |
| |
| loadManifestFiles(); |
| initPlaneId(); |
| installBundlesAndRebuildCatalog(); |
| instantiateLocationsAndEntities(); |
| instantiateMementos(); |
| // adjuncts depend on actual mementos; whereas entity works off special memento manifest, |
| // and location, bundles etc just take type and id |
| instantiateAdjuncts(instantiator); |
| reconstructEverything(); |
| associateAdjunctsWithEntities(); |
| manageTheObjects(); |
| finishingUp(); |
| } |
| |
| protected abstract void loadManifestFiles() throws Exception; |
| |
| public void run() { |
| if (iterationStarted.getAndSet(true)) { |
| throw new IllegalStateException("Iteration " + this + " has already run; create a new instance for another rebind pass."); |
| } |
| try { |
| rebindActive.acquire(); |
| } catch (InterruptedException e) { |
| Exceptions.propagate(e); |
| } |
| try { |
| RebindTracker.setRebinding(); |
| if (ManagementNodeState.isHotProxy(mode)) { |
| readOnlyRebindCount.incrementAndGet(); |
| } |
| |
| timer = Stopwatch.createStarted(); |
| exceptionHandler.onStart(rebindContext); |
| |
| doRun(); |
| |
| exceptionHandler.onDone(); |
| |
| rebindMetrics.noteSuccess(Duration.of(timer)); |
| noteErrors(exceptionHandler, null); |
| |
| } catch (Exception e) { |
| rebindMetrics.noteFailure(Duration.of(timer)); |
| |
| Exceptions.propagateIfFatal(e); |
| noteErrors(exceptionHandler, e); |
| throw exceptionHandler.onFailed(e); |
| |
| } finally { |
| rebindActive.release(); |
| RebindTracker.reset(); |
| } |
| } |
| |
| protected void checkEnteringPhase(int targetPhase) { |
| phase++; |
| checkContinuingPhase(targetPhase); |
| } |
| |
| protected void checkContinuingPhase(int targetPhase) { |
| if (targetPhase != phase) |
| throw new IllegalStateException("Phase mismatch: should be phase " + targetPhase + " but is currently " + phase); |
| } |
| |
| protected void preprocessManifestFiles() throws Exception { |
| checkContinuingPhase(1); |
| |
| Preconditions.checkState(mementoRawData != null, "Memento raw data should be set when calling this"); |
| Preconditions.checkState(mementoManifest == null, "Memento data should not yet be set when calling this"); |
| |
| // TODO building the manifests should be part of this class (or parent) |
| // it does not have anything to do with the persistence store! |
| mementoManifest = persistenceStoreAccess.loadMementoManifest(mementoRawData, exceptionHandler); |
| |
| overwritingMaster = false; |
| isEmpty = mementoManifest.isEmpty(); |
| } |
| |
| @Beta |
| public static class InstallableManagedBundleImpl implements CatalogInitialization.InstallableManagedBundle { |
| private final ManagedBundleMemento memento; |
| private final ManagedBundle managedBundle; |
| |
| public InstallableManagedBundleImpl(ManagedBundleMemento memento, ManagedBundle managedBundle) { |
| this.memento = memento; |
| this.managedBundle = managedBundle; |
| } |
| |
| @Override |
| public ManagedBundle getManagedBundle() { |
| return managedBundle; |
| } |
| |
| @Override |
| public Supplier<InputStream> getInputStreamSource() throws IOException { |
| return InputStreamSource.ofRenewableSupplier("JAR for " + memento, () -> { |
| try { |
| return memento.getJarContent().openStream(); |
| } catch (IOException e) { |
| throw Exceptions.propagate(e); |
| } |
| }); |
| } |
| |
| @Override |
| public String toString() { |
| return "InstallableManagedBundleImpl{" + |
| "memento=" + memento + |
| ", managedBundle=" + managedBundle + |
| '}'; |
| } |
| } |
| |
| protected void installBundlesAndRebuildCatalog() { |
| // Build catalog early so we can load other things. |
| // Reads the persisted catalog contents, and passes it all to CatalogInitialization, which decides what to do with it. |
| checkEnteringPhase(2); |
| |
| CatalogInitialization.RebindLogger rebindLogger = new CatalogInitialization.RebindLogger() { |
| @Override |
| public void debug(String message, Object... args) { |
| logRebindingDebug(message, args); |
| } |
| |
| @Override |
| public void info(String message, Object... args) { |
| logRebindingInfo(message, args); |
| } |
| }; |
| |
| Map<VersionedName, InstallableManagedBundle> bundles = new LinkedHashMap<>(); |
| Collection<CatalogItem<?, ?>> legacyCatalogItems = new ArrayList<>(); |
| |
| // Find the bundles |
| if (rebindManager.persistBundlesEnabled) { |
| for (ManagedBundleMemento bundleMemento : mementoManifest.getBundles().values()) { |
| ManagedBundle managedBundle = instantiator.newManagedBundle(bundleMemento); |
| bundles.put(managedBundle.getVersionedName(), new InstallableManagedBundleImpl(bundleMemento, managedBundle)); |
| logRebindingDebug("Registering bundle "+bundleMemento.getId()+": "+managedBundle); |
| rebindContext.registerBundle(bundleMemento.getId(), managedBundle); |
| } |
| } else { |
| logRebindingDebug("Not rebinding bundles; feature disabled: {}", mementoManifest.getBundleIds()); |
| } |
| |
| // Construct the legacy catalog items; don't add them to the catalog here, |
| // but instead pass them to catalogInitialization.populateCatalog. |
| |
| if (rebindManager.persistCatalogItemsEnabled) { |
| // Instantiate catalog items |
| logRebindingDebug("RebindManager instantiating catalog items: {}", mementoManifest.getCatalogItemIds()); |
| for (CatalogItemMemento catalogItemMemento : mementoManifest.getCatalogItemMementos().values()) { |
| logRebindingDebug("RebindManager instantiating catalog item {}", catalogItemMemento); |
| try { |
| CatalogItem<?, ?> catalogItem = instantiator.newCatalogItem(catalogItemMemento); |
| rebindContext.registerCatalogItem(catalogItemMemento.getId(), catalogItem); |
| legacyCatalogItems.add(catalogItem); |
| } catch (Exception e) { |
| exceptionHandler.onCreateFailed(BrooklynObjectType.CATALOG_ITEM, catalogItemMemento.getId(), catalogItemMemento.getType(), e); |
| } |
| } |
| |
| // Reconstruct catalog entries |
| logRebindingDebug("RebindManager reconstructing catalog items"); |
| for (CatalogItemMemento catalogItemMemento : mementoManifest.getCatalogItemMementos().values()) { |
| CatalogItem<?, ?> item = rebindContext.getCatalogItem(catalogItemMemento.getId()); |
| logRebindingDebug("RebindManager reconstructing catalog item {}", catalogItemMemento); |
| if (item == null) { |
| exceptionHandler.onNotFound(BrooklynObjectType.CATALOG_ITEM, catalogItemMemento.getId()); |
| } else { |
| try { |
| item.getRebindSupport().reconstruct(rebindContext, catalogItemMemento); |
| if (item instanceof AbstractBrooklynObject) { |
| AbstractBrooklynObject.class.cast(item).setManagementContext(managementContext); |
| } |
| } catch (Exception e) { |
| exceptionHandler.onRebindFailed(BrooklynObjectType.CATALOG_ITEM, item, e); |
| } |
| } |
| } |
| |
| } else { |
| logRebindingDebug("Not rebinding catalog; feature disabled: {}", mementoManifest.getCatalogItemIds()); |
| } |
| |
| |
| // Delegates to CatalogInitialization; see notes there. |
| CatalogInitialization.PersistedCatalogState persistedCatalogState = new CatalogInitialization.PersistedCatalogState(bundles, legacyCatalogItems); |
| |
| CatalogInitialization catInit = managementContext.getCatalogInitialization(); |
| catInit.clearForSubsequentCatalogInit(); |
| catInit.populateInitialAndPersistedCatalog(mode, persistedCatalogState, exceptionHandler, rebindLogger); |
| } |
| |
| protected void instantiateLocationsAndEntities() { |
| |
| checkEnteringPhase(3); |
| |
| // Instantiate locations |
| logRebindingDebug("RebindManager instantiating locations: {}", mementoManifest.getLocationIdToType().keySet()); |
| for (Map.Entry<String, String> entry : mementoManifest.getLocationIdToType().entrySet()) { |
| String locId = entry.getKey(); |
| String locType = entry.getValue(); |
| if (LOG.isTraceEnabled()) LOG.trace("RebindManager instantiating location {}", locId); |
| |
| try { |
| Location location = instantiator.newLocation(locId, locType); |
| rebindContext.registerLocation(locId, location); |
| } catch (Exception e) { |
| exceptionHandler.onCreateFailed(BrooklynObjectType.LOCATION, locId, locType, e); |
| } |
| } |
| |
| // Instantiate entities |
| logRebindingDebug("RebindManager instantiating entities: {}", mementoManifest.getEntityIdToManifest().keySet()); |
| for (Map.Entry<String, EntityMementoManifest> entry : mementoManifest.getEntityIdToManifest().entrySet()) { |
| String entityId = entry.getKey(); |
| EntityMementoManifest entityManifest = entry.getValue(); |
| |
| if (LOG.isTraceEnabled()) LOG.trace("RebindManager instantiating entity {}", entityId); |
| |
| try { |
| Entity entity = instantiator.newEntity(entityManifest); |
| ((EntityInternal) entity).getManagementSupport().setReadOnly(rebindContext.isReadOnly(entity)); |
| rebindContext.registerEntity(entityId, entity); |
| |
| } catch (Exception e) { |
| exceptionHandler.onCreateFailed(BrooklynObjectType.ENTITY, entityId, entityManifest.getType(), e); |
| } |
| } |
| } |
| |
| // creation of adjuncts can be called from different threads; it should be rare however, so easiest to synchronize |
| protected Map<String,EntityAdjunct> adjunctProxies = Collections.synchronizedMap(MutableMap.of()); |
| protected <T extends EntityAdjunct> T createAdjunctProxy(Class<T> adjunctType, String id) { |
| return (T) adjunctProxies.computeIfAbsent(id, (id2) -> EntityAdjuncts.createProxyForId(adjunctType, id)); |
| } |
| |
| protected void instantiateMementos() throws IOException { |
| |
| checkEnteringPhase(4); |
| |
| if (!adjunctProxies.isEmpty()) { |
| LOG.warn("Had stale adjunct information when rebinding; ignoring: "+adjunctProxies); |
| } |
| adjunctProxies.clear(); |
| |
| ((RebindExceptionHandlerImpl)exceptionHandler).setAdjunctProxyCreator(this::createAdjunctProxy); |
| memento = persistenceStoreAccess.loadMemento(mementoRawData, rebindContext.lookup(), exceptionHandler); |
| ((RebindExceptionHandlerImpl)exceptionHandler).setAdjunctProxyCreator(null); |
| } |
| |
| protected void initPlaneId() { |
| String persistedPlaneId = mementoRawData.getPlaneId(); |
| if (persistedPlaneId == null) { |
| if (!mementoRawData.isEmpty()) { |
| LOG.warn("Rebinding against existing persisted state, but no planeId found. Will generate a new one. " + |
| "Expected if this is the first rebind after upgrading to Brooklyn 0.12.0+"); |
| } |
| if (managementContext.getManagementPlaneIdMaybe().isAbsent()) { |
| ((LocalManagementContext) managementContext).generateManagementPlaneId(); |
| } |
| } else { |
| ((LocalManagementContext) managementContext).setManagementPlaneId(persistedPlaneId); |
| } |
| } |
| |
| protected void instantiateAdjuncts(BrooklynObjectInstantiator instantiator) { |
| |
| checkEnteringPhase(5); |
| |
| // Instantiate policies |
| if (rebindManager.persistPoliciesEnabled) { |
| logRebindingDebug("RebindManager instantiating policies: {}", memento.getPolicyIds()); |
| for (PolicyMemento policyMemento : memento.getPolicyMementos().values()) { |
| logRebindingDebug("RebindManager instantiating policy {}", policyMemento); |
| |
| try { |
| Policy policy = instantiator.newPolicy(policyMemento); |
| |
| EntityAdjunctProxyImpl.resetDelegate( adjunctProxies.remove(policy.getId()) , policy); |
| |
| rebindContext.registerPolicy(policyMemento.getId(), policy); |
| } catch (Exception e) { |
| exceptionHandler.onCreateFailed(BrooklynObjectType.POLICY, policyMemento.getId(), policyMemento.getType(), e); |
| } |
| } |
| } else { |
| logRebindingDebug("Not rebinding policies; feature disabled: {}", memento.getPolicyIds()); |
| } |
| |
| // Instantiate enrichers |
| if (rebindManager.persistEnrichersEnabled) { |
| logRebindingDebug("RebindManager instantiating enrichers: {}", memento.getEnricherIds()); |
| for (EnricherMemento enricherMemento : memento.getEnricherMementos().values()) { |
| logRebindingDebug("RebindManager instantiating enricher {}", enricherMemento); |
| |
| try { |
| Enricher enricher = instantiator.newEnricher(enricherMemento); |
| EntityAdjunctProxyImpl.resetDelegate( adjunctProxies.remove(enricher.getId()) , enricher); |
| rebindContext.registerEnricher(enricherMemento.getId(), enricher); |
| } catch (Exception e) { |
| exceptionHandler.onCreateFailed(BrooklynObjectType.ENRICHER, enricherMemento.getId(), enricherMemento.getType(), e); |
| } |
| } |
| } else { |
| logRebindingDebug("Not rebinding enrichers; feature disabled: {}", memento.getEnricherIds()); |
| } |
| |
| |
| // Instantiate feeds |
| if (rebindManager.persistFeedsEnabled) { |
| logRebindingDebug("RebindManager instantiating feeds: {}", memento.getFeedIds()); |
| for (FeedMemento feedMemento : memento.getFeedMementos().values()) { |
| if (LOG.isDebugEnabled()) LOG.debug("RebindManager instantiating feed {}", feedMemento); |
| |
| try { |
| Feed feed = instantiator.newFeed(feedMemento); |
| EntityAdjunctProxyImpl.resetDelegate( adjunctProxies.remove(feed.getId()) , feed); |
| rebindContext.registerFeed(feedMemento.getId(), feed); |
| // started during associateAdjunctsWithEntities by RebindAdjuncts |
| } catch (Exception e) { |
| exceptionHandler.onCreateFailed(BrooklynObjectType.FEED, feedMemento.getId(), feedMemento.getType(), e); |
| } |
| } |
| } else { |
| logRebindingDebug("Not rebinding feeds; feature disabled: {}", memento.getFeedIds()); |
| } |
| |
| if (!adjunctProxies.isEmpty()) { |
| LOG.warn("Adjunct proxies not empty, likely indicating dangling references: "+adjunctProxies); |
| adjunctProxies.entrySet().forEach(entry -> { |
| if (entry.getValue() instanceof Policy) exceptionHandler.onDanglingPolicyRef(entry.getKey()); |
| else if (entry.getValue() instanceof Enricher) exceptionHandler.onDanglingEnricherRef(entry.getKey()); |
| else if (entry.getValue() instanceof Feed) exceptionHandler.onDanglingFeedRef(entry.getKey()); |
| else { |
| LOG.warn("Adjunct proxy for "+entry.getKey()+" is of unexpected type; "+entry.getValue()+"; reporting as dangling of unknown type"); |
| exceptionHandler.onDanglingUntypedItemRef(entry.getKey()); |
| } |
| }); |
| adjunctProxies.clear(); |
| } |
| |
| } |
| |
| protected void reconstructEverything() { |
| |
| checkEnteringPhase(6); |
| |
| // Reconstruct locations |
| logRebindingDebug("RebindManager reconstructing locations"); |
| for (LocationMemento locMemento : sortParentFirst(memento.getLocationMementos()).values()) { |
| Location location = rebindContext.getLocation(locMemento.getId()); |
| logRebindingDebug("RebindManager reconstructing location {}", locMemento); |
| if (location == null) { |
| // usually because of creation-failure, when not using fail-fast |
| exceptionHandler.onNotFound(BrooklynObjectType.LOCATION, locMemento.getId()); |
| } else { |
| try { |
| ((LocationInternal) location).getRebindSupport().reconstruct(rebindContext, locMemento); |
| } catch (Exception e) { |
| exceptionHandler.onRebindFailed(BrooklynObjectType.LOCATION, location, e); |
| } |
| } |
| } |
| |
| // Reconstruct policies |
| if (rebindManager.persistPoliciesEnabled) { |
| logRebindingDebug("RebindManager reconstructing policies"); |
| for (PolicyMemento policyMemento : memento.getPolicyMementos().values()) { |
| Policy policy = rebindContext.getPolicy(policyMemento.getId()); |
| logRebindingDebug("RebindManager reconstructing policy {}", policyMemento); |
| |
| if (policy == null) { |
| // usually because of creation-failure, when not using fail-fast |
| exceptionHandler.onNotFound(BrooklynObjectType.POLICY, policyMemento.getId()); |
| } else { |
| try { |
| policy.getRebindSupport().reconstruct(rebindContext, policyMemento); |
| } catch (Exception e) { |
| exceptionHandler.onRebindFailed(BrooklynObjectType.POLICY, policy, e); |
| rebindContext.unregisterPolicy(policy); |
| } |
| } |
| } |
| } |
| |
| // Reconstruct enrichers |
| if (rebindManager.persistEnrichersEnabled) { |
| logRebindingDebug("RebindManager reconstructing enrichers"); |
| for (EnricherMemento enricherMemento : memento.getEnricherMementos().values()) { |
| Enricher enricher = rebindContext.getEnricher(enricherMemento.getId()); |
| logRebindingDebug("RebindManager reconstructing enricher {}", enricherMemento); |
| |
| if (enricher == null) { |
| // usually because of creation-failure, when not using fail-fast |
| exceptionHandler.onNotFound(BrooklynObjectType.ENRICHER, enricherMemento.getId()); |
| } else { |
| try { |
| enricher.getRebindSupport().reconstruct(rebindContext, enricherMemento); |
| } catch (Exception e) { |
| exceptionHandler.onRebindFailed(BrooklynObjectType.ENRICHER, enricher, e); |
| rebindContext.unregisterEnricher(enricher); |
| } |
| } |
| } |
| } |
| |
| // Reconstruct feeds |
| if (rebindManager.persistFeedsEnabled) { |
| logRebindingDebug("RebindManager reconstructing feeds"); |
| for (FeedMemento feedMemento : memento.getFeedMementos().values()) { |
| Feed feed = rebindContext.getFeed(feedMemento.getId()); |
| logRebindingDebug("RebindManager reconstructing feed {}", feedMemento); |
| |
| if (feed == null) { |
| // usually because of creation-failure, when not using fail-fast |
| exceptionHandler.onNotFound(BrooklynObjectType.FEED, feedMemento.getId()); |
| } else { |
| try { |
| feed.getRebindSupport().reconstruct(rebindContext, feedMemento); |
| } catch (Exception e) { |
| exceptionHandler.onRebindFailed(BrooklynObjectType.FEED, feed, e); |
| rebindContext.unregisterFeed(feed); |
| } |
| } |
| |
| } |
| } |
| |
| // Reconstruct entities |
| logRebindingDebug("RebindManager reconstructing entities"); |
| for (EntityMemento entityMemento : sortParentFirst(memento.getEntityMementos()).values()) { |
| Entity entity = rebindContext.lookup().lookupEntity(entityMemento.getId()); |
| logRebindingDebug("RebindManager reconstructing entity {}", entityMemento); |
| |
| if (entity == null) { |
| // usually because of creation-failure, when not using fail-fast |
| exceptionHandler.onNotFound(BrooklynObjectType.ENTITY, entityMemento.getId()); |
| } else { |
| try { |
| entityMemento.injectTypeClass(entity.getClass()); |
| ((EntityInternal) entity).getRebindSupport().reconstruct(rebindContext, entityMemento); |
| } catch (Exception e) { |
| exceptionHandler.onRebindFailed(BrooklynObjectType.ENTITY, entity, e); |
| } |
| } |
| } |
| } |
| |
| protected void associateAdjunctsWithEntities() { |
| checkEnteringPhase(7); |
| |
| logRebindingDebug("RebindManager associating adjuncts to entities"); |
| for (EntityMemento entityMemento : sortParentFirst(memento.getEntityMementos()).values()) { |
| Entity entity = rebindContext.getEntity(entityMemento.getId()); |
| logRebindingDebug("RebindManager associating adjuncts to entity {}", entityMemento); |
| |
| if (entity == null) { |
| // usually because of creation-failure, when not using fail-fast |
| exceptionHandler.onNotFound(BrooklynObjectType.ENTITY, entityMemento.getId()); |
| } else { |
| // Must execute in entity's context, so policy.setEntity can resolve config (BROOKLYN-549). |
| Runnable body = new Runnable() { |
| public void run() { |
| try { |
| entityMemento.injectTypeClass(entity.getClass()); |
| // TODO these call to the entity which in turn sets the entity on the underlying feeds and enrichers; |
| // that is taken as the cue to start, but it should not be. start should be a separate call. |
| ((EntityInternal) entity).getRebindSupport().addPolicies(rebindContext, entityMemento); |
| ((EntityInternal) entity).getRebindSupport().addEnrichers(rebindContext, entityMemento); |
| ((EntityInternal) entity).getRebindSupport().addFeeds(rebindContext, entityMemento); |
| } catch (Exception e) { |
| exceptionHandler.onRebindFailed(BrooklynObjectType.ENTITY, entity, e); |
| } |
| } |
| }; |
| ((EntityInternal) entity).getExecutionContext().get(Tasks.<Void>builder() |
| .displayName("Rebind adjuncts for " + entity.getId()) |
| .tag(BrooklynTaskTags.ENTITY_INITIALIZATION) |
| .dynamic(false) |
| .body(new RebindAdjuncts(entityMemento, entity, rebindContext, exceptionHandler)) |
| .build()); |
| } |
| } |
| } |
| |
| protected static class RebindAdjuncts implements Runnable { |
| private EntityMemento entityMemento; |
| private Entity entity; |
| private RebindContextImpl rebindContext; |
| private RebindExceptionHandler exceptionHandler; |
| |
| public RebindAdjuncts(EntityMemento entityMemento, Entity entity, RebindContextImpl rebindContext, RebindExceptionHandler exceptionHandler) { |
| this.entityMemento = entityMemento; |
| this.entity = entity; |
| this.rebindContext = rebindContext; |
| this.exceptionHandler = exceptionHandler; |
| } |
| |
| @Override |
| public void run() { |
| try { |
| entityMemento.injectTypeClass(entity.getClass()); |
| // TODO these call to the entity which in turn sets the entity on the underlying feeds and enrichers; |
| // that is taken as the cue to start, but it should not be. start should be a separate call. |
| ((EntityInternal) entity).getRebindSupport().addPolicies(rebindContext, entityMemento); |
| ((EntityInternal) entity).getRebindSupport().addEnrichers(rebindContext, entityMemento); |
| ((EntityInternal) entity).getRebindSupport().addFeeds(rebindContext, entityMemento); |
| |
| entityMemento = null; |
| entity = null; |
| } catch (Exception e) { |
| exceptionHandler.onRebindFailed(BrooklynObjectType.ENTITY, entity, e); |
| } |
| } |
| } |
| |
| protected void manageTheObjects() { |
| |
| checkEnteringPhase(8); |
| |
| logRebindingDebug("RebindManager managing locations"); |
| LocationManagerInternal locationManager = (LocationManagerInternal) managementContext.getLocationManager(); |
| Set<String> oldLocations = Sets.newLinkedHashSet(locationManager.getLocationIds()); |
| for (Location location : rebindContext.getLocations()) { |
| ManagementTransitionMode oldMode = updateTransitionMode(locationManager, location); |
| if (oldMode != null) |
| oldLocations.remove(location.getId()); |
| } |
| for (Location location : rebindContext.getLocations()) { |
| if (location.getParent() == null) { |
| // manage all root locations |
| try { |
| ((LocationManagerInternal) managementContext.getLocationManager()).manageRebindedRoot(location); |
| } catch (Exception e) { |
| exceptionHandler.onManageFailed(BrooklynObjectType.LOCATION, location, e); |
| } |
| } |
| } |
| // TODO could also see about purging unreferenced locations |
| cleanupOldLocations(oldLocations); |
| |
| // Manage the top-level apps (causing everything under them to become managed) |
| logRebindingDebug("RebindManager managing entities"); |
| EntityManagerInternal entityManager = (EntityManagerInternal) managementContext.getEntityManager(); |
| Set<String> oldEntities = Sets.newLinkedHashSet(entityManager.getEntityIds()); |
| for (Entity entity : rebindContext.getEntities()) { |
| ManagementTransitionMode oldMode = updateTransitionMode(entityManager, entity); |
| if (oldMode != null) |
| oldEntities.remove(entity.getId()); |
| } |
| List<Application> apps = Lists.newArrayList(); |
| for (String rootId : getMementoRootEntities()) { |
| Entity entity = rebindContext.getEntity(rootId); |
| if (entity == null) { |
| // usually because of creation-failure, when not using fail-fast |
| exceptionHandler.onNotFound(BrooklynObjectType.ENTITY, rootId); |
| } else { |
| try { |
| entityManager.manageRebindedRoot(entity); |
| } catch (Exception e) { |
| exceptionHandler.onManageFailed(BrooklynObjectType.ENTITY, entity, e); |
| } |
| if (entity instanceof Application) |
| apps.add((Application) entity); |
| } |
| } |
| cleanupOldEntities(oldEntities); |
| |
| this.applications = apps; |
| } |
| |
| private <T extends BrooklynObject> ManagementTransitionMode updateTransitionMode(BrooklynObjectManagerInternal<T> boManager, T bo) { |
| ManagementTransitionMode oldTransitionMode = boManager.getLastManagementTransitionMode(bo.getId()); |
| |
| Boolean isNowReadOnly = rebindContext.isReadOnly(bo); |
| BrooklynObjectManagementMode modeBefore, modeAfter; |
| if (oldTransitionMode == null) { |
| modeBefore = BrooklynObjectManagementMode.UNMANAGED_PERSISTED; |
| } else { |
| modeBefore = oldTransitionMode.getModeAfter(); |
| } |
| |
| if (isRebindingActiveAgain()) { |
| Preconditions.checkState(!Boolean.TRUE.equals(isNowReadOnly)); |
| Preconditions.checkState(modeBefore == BrooklynObjectManagementMode.MANAGED_PRIMARY); |
| modeAfter = BrooklynObjectManagementMode.MANAGED_PRIMARY; |
| } else if (isNowReadOnly) { |
| modeAfter = BrooklynObjectManagementMode.LOADED_READ_ONLY; |
| } else { |
| modeAfter = BrooklynObjectManagementMode.MANAGED_PRIMARY; |
| } |
| |
| ManagementTransitionMode newTransitionMode = ManagementTransitionMode.transitioning(modeBefore, modeAfter); |
| boManager.setManagementTransitionMode(bo, newTransitionMode); |
| return oldTransitionMode; |
| } |
| |
| protected abstract boolean isRebindingActiveAgain(); |
| |
| protected Collection<String> getMementoRootEntities() { |
| return memento.getApplicationIds(); |
| } |
| |
| protected abstract void cleanupOldLocations(Set<String> oldLocations); |
| |
| protected abstract void cleanupOldEntities(Set<String> oldEntities); |
| |
| protected void finishingUp() { |
| |
| checkContinuingPhase(8); |
| |
| if (!isEmpty) { |
| BrooklynLogging.log(LOG, shouldLogRebinding() ? LoggingLevel.INFO : LoggingLevel.DEBUG, |
| "Rebind complete" + |
| (!exceptionHandler.getExceptions().isEmpty() ? ", with errors" : |
| !exceptionHandler.getWarnings().isEmpty() ? ", with warnings" : "") + |
| " (" + mode + (readOnlyRebindCount.get() >= 0 ? ", iteration " + readOnlyRebindCount : "") + ")" + |
| " in {}: {} app{}, {} entit{}, {} location{}, {} polic{}, {} enricher{}, {} feed{}, {} catalog item{}, {} catalog bundle{}{}{}", |
| Time.makeTimeStringRounded(timer), applications.size(), Strings.s(applications), |
| rebindContext.getEntities().size(), Strings.ies(rebindContext.getEntities()), |
| rebindContext.getLocations().size(), Strings.s(rebindContext.getLocations()), |
| rebindContext.getPolicies().size(), Strings.ies(rebindContext.getPolicies()), |
| rebindContext.getEnrichers().size(), Strings.s(rebindContext.getEnrichers()), |
| rebindContext.getFeeds().size(), Strings.s(rebindContext.getFeeds()), |
| rebindContext.getCatalogItems().size(), Strings.s(rebindContext.getCatalogItems()), |
| rebindContext.getBundles().size(), Strings.s(rebindContext.getBundles()), |
| (!exceptionHandler.getExceptions().isEmpty() ? "; errors="+exceptionHandler.getExceptions() : ""), |
| (!exceptionHandler.getWarnings().isEmpty() ? "; warnings="+exceptionHandler.getWarnings() : "") |
| ); |
| } |
| |
| // Return the top-level applications |
| logRebindingDebug("RebindManager complete; apps: {}", getMementoRootEntities()); |
| } |
| |
| protected void noteErrors(final RebindExceptionHandler exceptionHandler, Exception primaryException) { |
| List<Exception> exceptions = exceptionHandler.getExceptions(); |
| List<String> warnings = exceptionHandler.getWarnings(); |
| if (primaryException != null || !exceptions.isEmpty() || !warnings.isEmpty()) { |
| List<String> messages = MutableList.<String>of(); |
| if (primaryException != null) messages.add(primaryException.toString()); |
| for (Exception e : exceptions) messages.add(e.toString()); |
| for (String w : warnings) messages.add(w); |
| rebindMetrics.noteError(messages); |
| } |
| } |
| |
| protected class CatalogItemIdAndSearchPath { |
| private String catalogItemId; |
| private List<String> searchPath; |
| |
| public CatalogItemIdAndSearchPath(String catalogItemId, List<String> searchPath) { |
| this.catalogItemId = catalogItemId; |
| this.searchPath = searchPath; |
| } |
| |
| public String getCatalogItemId() { |
| return catalogItemId; |
| } |
| |
| public List<String> getSearchPath() { |
| return searchPath; |
| } |
| } |
| |
| protected CatalogItemIdAndSearchPath findCatalogItemIds(ClassLoader cl, Map<String, |
| EntityMementoManifest> entityIdToManifest, EntityMementoManifest entityManifest) { |
| List<String> searchPath = MutableList.copyOf(entityManifest.getCatalogItemIdSearchPath()); |
| |
| if (entityManifest.getCatalogItemId() != null) { |
| return new CatalogItemIdAndSearchPath(entityManifest.getCatalogItemId(), searchPath); |
| } |
| |
| if (BrooklynFeatureEnablement.isEnabled(FEATURE_BACKWARDS_COMPATIBILITY_INFER_CATALOG_ITEM_ON_REBIND)) { |
| String typeId = null; |
| //First check if any of the parent entities has a catalogItemId set. |
| EntityMementoManifest ptr = entityManifest; |
| while (ptr != null) { |
| final String pId = ptr.getCatalogItemId(); |
| if (pId != null) { |
| RegisteredType type = managementContext.getTypeRegistry().get(pId); |
| if (type != null) { |
| typeId = type.getId(); |
| } |
| for (String id : ptr.getCatalogItemIdSearchPath()) { |
| type = managementContext.getTypeRegistry().get(id); |
| if (type != null) { |
| searchPath.add(type.getId()); |
| } else { |
| //Couldn't find a catalog item with this id, but add it anyway and |
| //let the caller deal with the error. |
| //TODO under what circumstances is this permitted? |
| searchPath.add(id); |
| } |
| } |
| return new CatalogItemIdAndSearchPath(typeId, searchPath); |
| } |
| if (ptr.getParent() != null) { |
| ptr = entityIdToManifest.get(ptr.getParent()); |
| } else { |
| ptr = null; |
| } |
| } |
| |
| //If no parent entity has the catalogItemId set try to match them by the type we are trying to load. |
| //The current convention is to set catalog item IDs to the java type (for both plain java or CAMP plan) they represent. |
| //This will be applicable only the first time the store is rebinded, while the catalog items don't have the default |
| //version appended to their IDs, but then we will have catalogItemId set on entities so not neede further anyways. |
| BrooklynTypeRegistry types = managementContext.getTypeRegistry(); |
| ptr = entityManifest; |
| while (ptr != null) { |
| RegisteredType t = types.get(ptr.getType(), BrooklynCatalog.DEFAULT_VERSION); |
| if (t != null) { |
| LOG.debug("Inferred catalog item ID " + t.getId() + " for " + entityManifest + " from ancestor " + ptr); |
| return new CatalogItemIdAndSearchPath(t.getId(), entityManifest.getCatalogItemIdSearchPath()); |
| } |
| if (ptr.getParent() != null) { |
| ptr = entityIdToManifest.get(ptr.getParent()); |
| } else { |
| ptr = null; |
| } |
| } |
| |
| //As a last resort go through all catalog items trying to load the type and use the first that succeeds. |
| //But first check if can be loaded from the default classpath |
| if (JavaBrooklynClassLoadingContext.create(managementContext).tryLoadClass(entityManifest.getType()).isPresent()) { |
| return new CatalogItemIdAndSearchPath(null, searchPath); |
| } |
| |
| // TODO get to the point when we can deprecate this behaviour!: |
| for (RegisteredType item : types.getAll()) { |
| BrooklynClassLoadingContext loader = CatalogUtils.newClassLoadingContext(managementContext, item); |
| boolean canLoadClass = loader.tryLoadClass(entityManifest.getType()).isPresent(); |
| if (canLoadClass) { |
| LOG.warn("Missing catalog item for " + entityManifest.getId() + " (" + entityManifest.getType() |
| + "), inferring as " + item.getId() + " because that is able to load the item"); |
| return new CatalogItemIdAndSearchPath(item.getId(), searchPath); |
| } |
| } |
| } |
| return new CatalogItemIdAndSearchPath(null, searchPath); |
| } |
| |
| protected static class LoadedClass<T extends BrooklynObject> { |
| protected final Class<? extends T> clazz; |
| protected final String catalogItemId; |
| protected final List<String> searchPath; |
| |
| protected LoadedClass(Class<? extends T> clazz, String catalogItemId, List<String> searchPath) { |
| this.clazz = clazz; |
| this.catalogItemId = catalogItemId; |
| this.searchPath = searchPath; |
| } |
| } |
| |
| protected class BrooklynObjectInstantiator { |
| |
| protected final ClassLoader classLoader; |
| protected final RebindContextImpl rebindContext; |
| protected final Reflections reflections; |
| |
| protected BrooklynObjectInstantiator(ClassLoader classLoader, RebindContextImpl rebindContext, Reflections reflections) { |
| this.classLoader = classLoader; |
| this.rebindContext = rebindContext; |
| this.reflections = reflections; |
| } |
| |
| protected Entity newEntity(EntityMementoManifest entityManifest) { |
| String entityId = entityManifest.getId(); |
| CatalogItemIdAndSearchPath idPath = |
| findCatalogItemIds(classLoader, mementoManifest.getEntityIdToManifest(), entityManifest); |
| String entityType = entityManifest.getType(); |
| |
| LoadedClass<? extends Entity> loaded = |
| load(Entity.class, entityType, idPath.getCatalogItemId(), idPath.getSearchPath(), entityId); |
| Class<? extends Entity> entityClazz = loaded.clazz; |
| |
| Entity entity; |
| |
| if (InternalFactory.isNewStyle(entityClazz)) { |
| // Not using entityManager.createEntity(EntitySpec) because don't want init() to be called. |
| // Creates an uninitialized entity, but that has correct id + proxy. |
| InternalEntityFactory entityFactory = managementContext.getEntityFactory(); |
| entity = entityFactory.constructEntity(entityClazz, Reflections.getAllInterfaces(entityClazz), entityId); |
| |
| } else { |
| LOG.warn("Deprecated rebind of entity without no-arg constructor; " + |
| "this may not be supported in future versions: id=" + entityId + "; type=" + entityType); |
| |
| // There are several possibilities for the constructor; find one that works. |
| // Prefer passing in the flags because required for Application to set the management context |
| // TODO Feels very hacky! |
| |
| Map<Object, Object> flags = Maps.newLinkedHashMap(); |
| flags.put("id", entityId); |
| if (AbstractApplication.class.isAssignableFrom(entityClazz)) flags.put("mgmt", managementContext); |
| |
| // TODO document the multiple sources of flags, and the reason for setting the mgmt context *and* |
| // supplying it as the flag |
| // (NB: merge reported conflict as the two things were added separately) |
| entity = invokeConstructor(null, entityClazz, |
| new Object[]{flags}, new Object[]{flags, null}, new Object[]{null}, new Object[0]); |
| |
| // In case the constructor didn't take the Map arg, then also set it here. |
| // e.g. for top-level app instances such as WebClusterDatabaseExampleApp will (often?) not have |
| // interface + constructor. |
| // TODO On serializing the memento, we should capture which interfaces so can recreate |
| // the proxy+spec (including for apps where there's not an obvious interface). |
| FlagUtils.setFieldsFromFlags(ImmutableMap.of("id", entityId), entity); |
| if (entity instanceof AbstractApplication) { |
| FlagUtils.setFieldsFromFlags(ImmutableMap.of("mgmt", managementContext), entity); |
| } |
| ((AbstractEntity) entity).setManagementContext(managementContext); |
| managementContext.prePreManage(entity); |
| } |
| |
| setCatalogItemIds(entity, loaded.catalogItemId, loaded.searchPath); |
| |
| return entity; |
| } |
| |
| protected void setCatalogItemIds(BrooklynObject object, String catalogItemId, List<String> searchPath) { |
| final BrooklynObjectInternal internal = (BrooklynObjectInternal) object; |
| internal.setCatalogItemIdAndSearchPath(catalogItemId, searchPath); |
| } |
| |
| |
| protected <T extends BrooklynObject> LoadedClass<? extends T> load(Class<T> bType, Memento memento) { |
| return load(bType, memento.getType(), memento.getCatalogItemId(), memento.getCatalogItemIdSearchPath(), |
| memento.getId()); |
| } |
| |
| @SuppressWarnings("unchecked") |
| // TODO should prefer a registered type as the type to load (in lieu of jType), |
| // but note some callers (enrichers etc) use catalogItemId to be the first entry in search path rather than their actual type, |
| // so until callers are all updated all we can do here is load the java type with no guarantee the catalogItemId should be the same. |
| // (yoml should help a lot with this.) |
| protected <T extends BrooklynObject> LoadedClass<? extends T> load(Class<T> bType, String jType, |
| String catalogItemId, List<String> searchPath, String contextSuchAsId) { |
| checkNotNull(jType, "Type of %s (%s) must not be null", contextSuchAsId, bType.getSimpleName()); |
| |
| CatalogUpgrades.markerForCodeThatLoadsJavaTypesButShouldLoadRegisteredType(); |
| |
| List<String> warnings = MutableList.of(); |
| List<String> reboundSearchPath = MutableList.of(); |
| if (searchPath != null && !searchPath.isEmpty()) { |
| for (String searchItemId : searchPath) { |
| String fixedSearchItemId = null; |
| VersionedName searchItemVersionedName = VersionedName.fromString(searchItemId); |
| |
| OsgiManager osgi = managementContext.getOsgiManager().orNull(); |
| |
| String bundleUpgraded = CatalogUpgrades.getBundleUpgradedIfNecessary(managementContext, searchItemId); |
| if (bundleUpgraded!=null && !bundleUpgraded.equals(searchItemId)) { |
| logRebindingDebug("Upgrading search path entry of " + bType.getSimpleName().toLowerCase() + " " + contextSuchAsId + " from " + searchItemId + " to bundle " + bundleUpgraded); |
| searchItemVersionedName = VersionedName.fromString(bundleUpgraded); |
| } |
| |
| if (osgi != null) { |
| ManagedBundle bundle = osgi.getManagedBundle(searchItemVersionedName); |
| if (bundle != null) { |
| // found as bundle |
| fixedSearchItemId = searchItemVersionedName.toOsgiString(); |
| reboundSearchPath.add(fixedSearchItemId); |
| continue; |
| } |
| } |
| |
| // look for as a type now |
| RegisteredType t1 = managementContext.getTypeRegistry().get(searchItemId); |
| if (t1 == null) { |
| String newSearchItemId = CatalogUpgrades.getTypeUpgradedIfNecessary(managementContext, searchItemId); |
| if (!newSearchItemId.equals(searchItemId)) { |
| logRebindingDebug("Upgrading search path entry of " + bType.getSimpleName().toLowerCase() + " " + contextSuchAsId + " from " + searchItemId + " to type " + newSearchItemId); |
| searchItemId = newSearchItemId; |
| t1 = managementContext.getTypeRegistry().get(newSearchItemId); |
| } |
| } |
| if (t1 != null) fixedSearchItemId = t1.getId(); |
| if (fixedSearchItemId == null) { |
| CatalogItem<?, ?> ci = findCatalogItemInReboundCatalog(bType, searchItemId, contextSuchAsId); |
| if (ci != null) { |
| fixedSearchItemId = ci.getCatalogItemId(); |
| logRebindingWarn("Needed rebind catalog to resolve search path entry " + searchItemId + " (now " + fixedSearchItemId + ") for " + bType.getSimpleName().toLowerCase() + " " + contextSuchAsId + |
| ", persistence should remove this in future but future versions will not support this and definitions should be fixed"); |
| } else { |
| // could do some magic if there is a peer with the same version use it, might be handy for snapshots especially |
| // (snapshots upgrading bundle version "*" drop the qualifier so don't replace peers; |
| // but as soon as you have a larger datestamp, it will replace the previous ones) |
| logRebindingWarn("Could not find search path entry " + searchItemId + " for " + bType.getSimpleName().toLowerCase() + " " + contextSuchAsId + ", ignoring"); |
| } |
| } |
| if (fixedSearchItemId != null) { |
| reboundSearchPath.add(fixedSearchItemId); |
| } else { |
| warnings.add("unable to resolve search path entry " + searchItemId); |
| } |
| } |
| } |
| |
| if (catalogItemId != null) { |
| String transformedCatalogItemId = null; |
| |
| Maybe<RegisteredType> contextRegisteredType = managementContext.getTypeRegistry().getMaybe(catalogItemId, |
| // this is context RT, not item we are loading, so bType does not apply here |
| // if we were instantiating from an RT instead of a JT (ideal) then we would use bType to filter |
| null); |
| if (contextRegisteredType.isAbsent()) { |
| transformedCatalogItemId = CatalogUpgrades.getTypeUpgradedIfNecessary(managementContext, catalogItemId); |
| if (!transformedCatalogItemId.equals(catalogItemId)) { |
| // catalog item id is sometimes the type of the item, but sometimes just the first part of the search path |
| logRebindingInfo("Upgrading " + bType.getSimpleName().toLowerCase() + " " + contextSuchAsId + |
| " stored catalog item context on rebind" + |
| " from " + catalogItemId + " to " + transformedCatalogItemId); |
| |
| // again ignore bType |
| contextRegisteredType = managementContext.getTypeRegistry().getMaybe(transformedCatalogItemId, null); |
| |
| } else { |
| transformedCatalogItemId = null; |
| } |
| } |
| |
| if (contextRegisteredType.isPresent()) { |
| transformedCatalogItemId = contextRegisteredType.get().getId(); |
| } else { |
| CatalogItem<?, ?> catalogItem = findCatalogItemInReboundCatalog(bType, catalogItemId, contextSuchAsId); |
| if (catalogItem != null) { |
| transformedCatalogItemId = catalogItem.getCatalogItemId(); |
| } |
| } |
| if (transformedCatalogItemId != null) { |
| try { |
| BrooklynClassLoadingContextSequential loader = |
| new BrooklynClassLoadingContextSequential(managementContext); |
| loader.add(newClassLoadingContextForCatalogItems(managementContext, transformedCatalogItemId, |
| reboundSearchPath)); |
| return new LoadedClass<T>(loader.loadClass(jType, bType), transformedCatalogItemId, reboundSearchPath); |
| } catch (Exception e) { |
| Exceptions.propagateIfFatal(e); |
| warnings.add("unable to load class " + jType + " for resovled context type " + transformedCatalogItemId); |
| } |
| } else { |
| // TODO fail, rather than fallback to java? |
| warnings.add("unable to resolve context type " + catalogItemId); |
| } |
| } else { |
| // can happen for enrichers etc added by java, and for BasicApplication when things are deployed; |
| // no need to warn |
| } |
| |
| try { |
| Class<T> jTypeC = (Class<T>) loadClass(jType); |
| if (!warnings.isEmpty()) { |
| LOG.warn("Loaded java type " + jType + " for " + bType.getSimpleName().toLowerCase() + " " + contextSuchAsId + " but had errors: " + Strings.join(warnings, ";")); |
| } |
| return new LoadedClass<T>(jTypeC, catalogItemId, reboundSearchPath); |
| } catch (Exception e) { |
| Exceptions.propagateIfFatal(e); |
| } |
| |
| if (catalogItemId != null) { |
| String msg = "Class " + jType + " not found for " + bType.getSimpleName().toLowerCase() + " " + contextSuchAsId + " (" + catalogItemId + "): " + Strings.join(warnings, ";"); |
| LOG.warn(msg + " (rethrowing)"); |
| throw new IllegalStateException(msg); |
| |
| } else if (BrooklynFeatureEnablement.isEnabled(FEATURE_BACKWARDS_COMPATIBILITY_INFER_CATALOG_ITEM_ON_REBIND)) { |
| //Try loading from whichever catalog bundle succeeds (legacy CI items only; also disabling this, as no longer needed 2017-09) |
| BrooklynCatalog catalog = managementContext.getCatalog(); |
| for (CatalogItem<?, ?> item : catalog.getCatalogItemsLegacy()) { |
| BrooklynClassLoadingContext catalogLoader = CatalogUtils.newClassLoadingContext(managementContext, item); |
| Maybe<Class<?>> catalogClass = catalogLoader.tryLoadClass(jType); |
| if (catalogClass.isPresent()) { |
| LOG.warn("Falling back to java type " + jType + " for " + bType.getSimpleName().toLowerCase() + " " + contextSuchAsId + " using catalog search paths, found on " + item + |
| (warnings.isEmpty() ? "" : ", after errors: " + Strings.join(warnings, ";"))); |
| return new LoadedClass<T>((Class<? extends T>) catalogClass.get(), catalogItemId, reboundSearchPath); |
| } |
| } |
| String msg = "Class " + jType + " not found for " + bType.getSimpleName().toLowerCase() + " " + contextSuchAsId + ", even after legacy global classpath search" + |
| (warnings.isEmpty() ? "" : ": " + Strings.join(warnings, ";")); |
| LOG.warn(msg + " (rethrowing)"); |
| throw new IllegalStateException(msg); |
| |
| } else { |
| String msg = "Class " + jType + " not found for " + bType.getSimpleName().toLowerCase() + " " + contextSuchAsId + |
| (warnings.isEmpty() ? "" : ": " + Strings.join(warnings, ";")); |
| LOG.warn(msg + " (rethrowing)"); |
| throw new IllegalStateException(msg); |
| } |
| } |
| |
| private <T extends BrooklynObject> CatalogItem<?, ?> findCatalogItemInReboundCatalog(Class<T> bType, |
| String catalogItemId, String contextSuchAsId) { |
| CatalogItem<?, ?> catalogItem = rebindContext.lookup().lookupCatalogItem(catalogItemId); |
| if (catalogItem == null) { |
| if (BrooklynFeatureEnablement.isEnabled(FEATURE_AUTO_FIX_CATALOG_REF_ON_REBIND)) { |
| // See https://issues.apache.org/jira/browse/BROOKLYN-149 |
| // This is a dangling reference to the catalog item (which will have been logged by lookupCatalogItem). |
| // Try loading as any version. |
| if (RegisteredTypeNaming.isUsableTypeColonVersion(catalogItemId) || |
| // included through 0.12 so legacy type names are accepted (with warning) |
| CatalogUtils.looksLikeVersionedId(catalogItemId)) { |
| String symbolicName = CatalogUtils.getSymbolicNameFromVersionedId(catalogItemId); |
| catalogItem = rebindContext.lookup().lookupCatalogItem(symbolicName); |
| |
| if (catalogItem != null) { |
| LOG.warn("Unable to load catalog item " + catalogItemId + " for " + contextSuchAsId |
| + " (" + bType.getSimpleName() + "); will auto-upgrade to " |
| + catalogItem.getCatalogItemId() + ":" + catalogItem.getVersion()); |
| } |
| } |
| } |
| } |
| return catalogItem; |
| } |
| |
| protected Class<?> loadClass(String jType) throws ClassNotFoundException { |
| try { |
| return reflections.loadClass(jType); |
| } catch (Exception e) { |
| Exceptions.propagateIfFatal(e); |
| } |
| return new ClassLoaderUtils(reflections.getClassLoader(), managementContext).loadClass(jType); |
| } |
| |
| @SuppressWarnings("unchecked") |
| public <T> Class<? extends T> loadClass(String classname, Class<T> superType) { |
| try { |
| return (Class<? extends T>) loadClass(classname); |
| } catch (ClassNotFoundException e) { |
| throw Exceptions.propagate(e); |
| } |
| } |
| |
| /** |
| * Constructs a new location, passing to its constructor the location id and all of memento.getFlags(). |
| */ |
| protected Location newLocation(String locationId, String locationType) { |
| Class<? extends Location> locationClazz = loadClass(locationType, Location.class); |
| |
| if (InternalFactory.isNewStyle(locationClazz)) { |
| // Not using loationManager.createLocation(LocationSpec) because don't want init() to be called |
| // TODO Need to rationalise this to move code into methods of InternalLocationFactory. |
| // But note that we'll change all locations to be entities at some point! |
| // See same code approach used in #newEntity(EntityMemento, Reflections) |
| InternalLocationFactory locationFactory = managementContext.getLocationFactory(); |
| Location location = locationFactory.constructLocation(locationClazz); |
| FlagUtils.setFieldsFromFlags(ImmutableMap.of("id", locationId), location); |
| managementContext.prePreManage(location); |
| ((AbstractLocation) location).setManagementContext(managementContext); |
| |
| return location; |
| } else { |
| LOG.warn("Deprecated rebind of location without no-arg constructor; " + |
| "this may not be supported in future versions: id=" + locationId + "; type=" + locationType); |
| |
| // There are several possibilities for the constructor; find one that works. |
| // Prefer passing in the flags because required for Application to set the management context |
| // TODO Feels very hacky! |
| Map<String, ?> flags = MutableMap.of("id", locationId, "deferConstructionChecks", true); |
| |
| return invokeConstructor(reflections, locationClazz, new Object[]{flags}); |
| } |
| // note 'used' config keys get marked in BasicLocationRebindSupport |
| } |
| |
| /** |
| * Constructs a new policy, passing to its constructor the policy id and all of memento.getConfig(). |
| */ |
| protected Policy newPolicy(PolicyMemento memento) { |
| String id = memento.getId(); |
| LoadedClass<? extends Policy> loaded = load(Policy.class, memento); |
| Class<? extends Policy> policyClazz = loaded.clazz; |
| |
| Policy policy; |
| if (InternalFactory.isNewStyle(policyClazz)) { |
| InternalPolicyFactory policyFactory = managementContext.getPolicyFactory(); |
| policy = policyFactory.constructPolicy(policyClazz); |
| FlagUtils.setFieldsFromFlags(ImmutableMap.of("id", id), policy); |
| ((AbstractPolicy) policy).setManagementContext(managementContext); |
| ((AbstractPolicy) policy).setHighlights(memento.getHighlights()); |
| |
| } else { |
| LOG.warn("Deprecated rebind of policy without no-arg constructor; " + |
| "this may not be supported in future versions: id=" + id + "; type=" + policyClazz); |
| |
| // There are several possibilities for the constructor; find one that works. |
| // Prefer passing in the flags because required for Application to set the management context |
| // TODO Feels very hacky! |
| Map<String, Object> flags = MutableMap.<String, Object>of( |
| "id", id, |
| "deferConstructionChecks", true, |
| "noConstructionInit", true); |
| flags.putAll(memento.getConfig()); |
| |
| policy = invokeConstructor(null, policyClazz, new Object[]{flags}); |
| } |
| |
| setCatalogItemIds(policy, loaded.catalogItemId, loaded.searchPath); |
| return policy; |
| } |
| |
| /** |
| * Constructs a new enricher, passing to its constructor the enricher id and all of memento.getConfig(). |
| */ |
| protected Enricher newEnricher(EnricherMemento memento) { |
| String id = memento.getId(); |
| LoadedClass<? extends Enricher> loaded = load(Enricher.class, memento); |
| Class<? extends Enricher> enricherClazz = loaded.clazz; |
| |
| Enricher enricher; |
| if (InternalFactory.isNewStyle(enricherClazz)) { |
| InternalPolicyFactory policyFactory = managementContext.getPolicyFactory(); |
| enricher = policyFactory.constructEnricher(enricherClazz); |
| FlagUtils.setFieldsFromFlags(ImmutableMap.of("id", id), enricher); |
| ((AbstractEnricher) enricher).setManagementContext(managementContext); |
| |
| } else { |
| LOG.warn("Deprecated rebind of enricher without no-arg constructor; " + |
| "this may not be supported in future versions: id=" + id + "; type=" + enricherClazz); |
| |
| // There are several possibilities for the constructor; find one that works. |
| // Prefer passing in the flags because required for Application to set the management context |
| // TODO Feels very hacky! |
| Map<String, Object> flags = MutableMap.<String, Object>of( |
| "id", id, |
| "deferConstructionChecks", true, |
| "noConstructionInit", true); |
| flags.putAll(memento.getConfig()); |
| |
| enricher = invokeConstructor(reflections, enricherClazz, new Object[]{flags}); |
| } |
| |
| setCatalogItemIds(enricher, loaded.catalogItemId, loaded.searchPath); |
| return enricher; |
| } |
| |
| /** |
| * Constructs a new enricher, passing to its constructor the enricher id and all of memento.getConfig(). |
| */ |
| protected Feed newFeed(FeedMemento memento) { |
| String id = memento.getId(); |
| LoadedClass<? extends Feed> loaded = load(Feed.class, memento); |
| Class<? extends Feed> feedClazz = loaded.clazz; |
| |
| Feed feed; |
| if (InternalFactory.isNewStyle(feedClazz)) { |
| InternalPolicyFactory policyFactory = managementContext.getPolicyFactory(); |
| feed = policyFactory.constructFeed(feedClazz); |
| FlagUtils.setFieldsFromFlags(ImmutableMap.of("id", id), feed); |
| ((AbstractFeed) feed).setManagementContext(managementContext); |
| |
| } else { |
| throw new IllegalStateException("rebind of feed without no-arg constructor unsupported: id=" + id + |
| "; type=" + feedClazz); |
| } |
| |
| setCatalogItemIds(feed, loaded.catalogItemId, loaded.searchPath); |
| return feed; |
| } |
| |
| @SuppressWarnings({"rawtypes"}) |
| protected CatalogItem<?, ?> newCatalogItem(CatalogItemMemento memento) { |
| String id = memento.getId(); |
| // catalog item subtypes are internal to brooklyn, not in osgi |
| String itemType = checkNotNull(memento.getType(), "catalog item type of %s must not be null in memento", id); |
| Class<? extends CatalogItem> clazz = loadClass(itemType, CatalogItem.class); |
| return invokeConstructor(reflections, clazz, new Object[]{}); |
| } |
| |
| protected <T> T invokeConstructor(Reflections reflections, Class<T> clazz, Object[]... possibleArgs) { |
| for (Object[] args : possibleArgs) { |
| try { |
| Maybe<T> v = Reflections.invokeConstructorFromArgs(clazz, args, true); |
| if (v.isPresent()) { |
| return v.get(); |
| } |
| } catch (Exception e) { |
| throw Exceptions.propagate(e); |
| } |
| } |
| StringBuilder args = new StringBuilder(); |
| if (possibleArgs.length < 1) args.append("no possible argument sets supplied; error"); |
| else if (possibleArgs.length < 2) args.append("args are " + Arrays.asList(possibleArgs[0])); |
| else { |
| args.append("args are " + Arrays.asList(possibleArgs[0])); |
| for (int i = 1; i < possibleArgs.length; i++) { |
| args.append(" or "); |
| args.append(Arrays.asList(possibleArgs[i])); |
| } |
| } |
| throw new IllegalStateException("Cannot instantiate instance of type " + clazz + |
| "; expected constructor signature not found (" + args + " / " + |
| Arrays.asList(args).stream().map(a -> a.getClass()).collect(Collectors.toList()) + ")"); |
| } |
| |
| protected ManagedBundle newManagedBundle(ManagedBundleMemento bundleMemento) { |
| return RebindIteration.newManagedBundle(bundleMemento); |
| } |
| } |
| |
| protected BrooklynMementoPersister getPersister() { |
| return rebindManager.getPersister(); |
| } |
| |
| protected <T extends TreeNode> Map<String, T> sortParentFirst(Map<String, T> nodes) { |
| return RebindManagerImpl.sortParentFirst(nodes); |
| } |
| |
| /** |
| * logs at debug, except during subsequent read-only rebinds, in which it logs trace |
| */ |
| protected void logRebindingDebug(String message, Object... args) { |
| if (shouldLogRebinding()) { |
| LOG.debug(message, args); |
| } else { |
| LOG.trace(message, args); |
| } |
| } |
| |
| /** |
| * logs at info, except during subsequent read-only rebinds, in which it logs trace |
| */ |
| protected void logRebindingInfo(String message, Object... args) { |
| if (shouldLogRebinding()) { |
| LOG.info(message, args); |
| } else { |
| LOG.trace(message, args); |
| } |
| } |
| |
| /** |
| * logs at warn, except during subsequent read-only rebinds, in which it logs trace |
| */ |
| protected void logRebindingWarn(String message, Object... args) { |
| if (shouldLogRebinding()) { |
| LOG.warn(message, args); |
| } else { |
| LOG.trace(message, args); |
| } |
| } |
| |
| protected boolean shouldLogRebinding() { |
| return (readOnlyRebindCount.get() < 5) || (readOnlyRebindCount.get() % 1000 == 0); |
| } |
| |
| @Beta |
| public static ManagedBundle newManagedBundle(ManagedBundleMemento memento) { |
| ManagedBundle result = new BasicManagedBundle(memento.getSymbolicName(), memento.getVersion(), memento.getUrl(), |
| memento.getFormat(), null, memento.getChecksum(), memento.getDeleteable()); |
| FlagUtils.setFieldsFromFlags(ImmutableMap.of("id", memento.getId()), result); |
| return result; |
| } |
| |
| } |