/* * Copyright (c) MuleSoft, Inc. All rights reserved. http://www.mulesoft.com * The software in this package is published under the terms of the CPAL v1.0 * license, a copy of which has been included with this distribution in the * LICENSE.txt file. */ package org.mule.runtime.module.extension.internal.manager; import static java.lang.String.format; import static java.util.Optional.empty; import static java.util.Optional.of; import static java.util.Optional.ofNullable; import static org.mule.runtime.api.i18n.I18nMessageFactory.createStaticMessage; import static org.mule.runtime.api.util.Preconditions.checkArgument; import static org.mule.runtime.core.api.lifecycle.LifecycleUtils.disposeIfNeeded; import static org.mule.runtime.core.api.lifecycle.LifecycleUtils.stopIfNeeded; import static org.mule.runtime.core.util.ClassUtils.withContextClassLoader; import static org.mule.runtime.extension.api.util.ExtensionModelUtils.getConfigurationForComponent; import static org.mule.runtime.extension.api.util.ExtensionModelUtils.requiresConfig; import static org.mule.runtime.module.extension.internal.manager.DefaultConfigurationExpirationMonitor.Builder.newBuilder; import static org.mule.runtime.module.extension.internal.util.MuleExtensionUtils.getClassLoader; import static org.mule.runtime.module.extension.internal.util.MuleExtensionUtils.getImplicitConfigurationProviderName; import org.mule.runtime.api.exception.MuleException; import org.mule.runtime.api.exception.MuleRuntimeException; import org.mule.runtime.api.lifecycle.Initialisable; import org.mule.runtime.api.lifecycle.InitialisationException; import org.mule.runtime.api.lifecycle.Startable; import org.mule.runtime.api.lifecycle.Stoppable; import org.mule.runtime.api.meta.model.ComponentModel; import org.mule.runtime.api.meta.model.ExtensionModel; import org.mule.runtime.api.meta.model.config.ConfigurationModel; import org.mule.runtime.core.api.Event; import org.mule.runtime.core.api.MuleContext; import org.mule.runtime.core.api.context.MuleContextAware; import org.mule.runtime.core.api.extension.ExtensionManager; import org.mule.runtime.core.api.registry.MuleRegistry; import org.mule.runtime.core.api.time.Time; import org.mule.runtime.core.util.StringUtils; import org.mule.runtime.extension.api.manifest.ExtensionManifest; import org.mule.runtime.extension.api.persistence.manifest.ExtensionManifestXmlSerializer; import org.mule.runtime.extension.api.runtime.ConfigurationInstance; import org.mule.runtime.extension.api.runtime.ConfigurationProvider; import org.mule.runtime.extension.api.util.ExtensionModelUtils; import org.mule.runtime.module.extension.internal.config.ExtensionConfig; import org.mule.runtime.module.extension.internal.runtime.config.DefaultImplicitConfigurationProviderFactory; import org.mule.runtime.module.extension.internal.runtime.config.ImplicitConfigurationProviderFactory; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import org.apache.commons.io.IOUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Default implementation of {@link ExtensionManager}. This implementation uses standard Java SPI as a discovery mechanism. * <p/> * Although it allows registering {@link ConfigurationProvider} instances through the * {@link #registerConfigurationProvider(ConfigurationProvider)} method (and that's still the correct way of registering them), * this implementation automatically acknowledges any {@link ConfigurationProvider} already present on the {@link MuleRegistry} * * @since 3.7.0 */ public final class DefaultExtensionManager implements ExtensionManager, MuleContextAware, Initialisable, Startable, Stoppable { private static final Logger LOGGER = LoggerFactory.getLogger(DefaultExtensionManager.class); private final ImplicitConfigurationProviderFactory implicitConfigurationProviderFactory = new DefaultImplicitConfigurationProviderFactory(); private MuleContext muleContext; private ExtensionRegistry extensionRegistry; private ConfigurationExpirationMonitor configurationExpirationMonitor; private ExtensionErrorsRegistrant extensionErrorsRegistrant; @Override public void initialise() throws InitialisationException { extensionRegistry = new ExtensionRegistry(muleContext.getRegistry()); extensionErrorsRegistrant = new ExtensionErrorsRegistrant(muleContext.getErrorTypeRepository(), muleContext.getErrorTypeLocator()); } /** * Starts the {@link #configurationExpirationMonitor} * * @throws MuleException if it fails to start */ @Override public void start() throws MuleException { configurationExpirationMonitor = newConfigurationExpirationMonitor(); configurationExpirationMonitor.beginMonitoring(); } /** * Stops the {@link #configurationExpirationMonitor} * * @throws MuleException if it fails to stop */ @Override public void stop() throws MuleException { configurationExpirationMonitor.stopMonitoring(); } /** * {@inheritDoc} */ @Override public void registerExtension(ExtensionModel extensionModel) { final String extensionName = extensionModel.getName(); final String extensionVersion = extensionModel.getVersion(); final String extensionVendor = extensionModel.getVendor(); LOGGER.info("Registering extension {} (version: {} vendor: {} )", extensionName, extensionVersion, extensionVendor); if (extensionRegistry.containsExtension(extensionName)) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("An extension of name '{}' (version: {} vendor {}) is already registered. Skipping...", extensionName, extensionVersion, extensionVendor); } } else { withContextClassLoader(getClassLoader(extensionModel), () -> { extensionRegistry.registerExtension(extensionName, extensionModel); extensionErrorsRegistrant.registerErrors(extensionModel); }); } } /** * {@inheritDoc} */ @Override public void registerConfigurationProvider(ConfigurationProvider configurationProvider) { extensionRegistry.registerConfigurationProvider(configurationProvider); } /** * {@inheritDoc} */ @Override public ConfigurationInstance getConfiguration(String configurationProviderName, Event muleEvent) { return getConfigurationProvider(configurationProviderName).map(provider -> provider.get(muleEvent)) .orElseThrow(() -> new IllegalArgumentException(format("There is no registered configurationProvider under name '%s'", configurationProviderName))); } /** * {@inheritDoc} */ @Override public Optional<ConfigurationInstance> getConfiguration(ExtensionModel extensionModel, ComponentModel componentModel, Event muleEvent) { ConfigurationInstance instance = getConfigurationProvider(extensionModel, componentModel).map(p -> p.get(muleEvent)).orElse(null); if (instance != null) { return of(instance); } Optional<ConfigurationModel> configurationModel = getConfigurationModelForExtension(extensionModel, getConfigurationForComponent(extensionModel, componentModel)); if (configurationModel.isPresent()) { createImplicitConfiguration(extensionModel, configurationModel.get(), muleEvent); return of(getConfiguration(getImplicitConfigurationProviderName(extensionModel, configurationModel.get()), muleEvent)); } return empty(); } @Override public Optional<ConfigurationProvider> getConfigurationProvider(ExtensionModel extensionModel, ComponentModel componentModel) { Optional<ConfigurationModel> config = getConfigurationModelForExtension(extensionModel, getConfigurationForComponent(extensionModel, componentModel)); if (!config.isPresent() && requiresConfig(extensionModel, componentModel)) { throw new IllegalStateException( format("No config-ref was specified for component '%s' of extension '%s'. Please specify which to use", componentModel.getName(), extensionModel.getName())); } return config.map(c -> getConfigurationProvider(getImplicitConfigurationProviderName(extensionModel, c))).orElse(empty()); } /** * {@inheritDoc} */ @Override public Optional<ConfigurationProvider> getConfigurationProvider(String configurationProviderName) { checkArgument(!StringUtils.isBlank(configurationProviderName), "cannot get configuration from a blank provider name"); return extensionRegistry.getConfigurationProvider(configurationProviderName); } private void createImplicitConfiguration(ExtensionModel extensionModel, ConfigurationModel implicitConfigurationModel, Event muleEvent) { synchronized (extensionModel) { // check that another thread didn't beat us to create the instance if (!extensionRegistry .getConfigurationProvider(getImplicitConfigurationProviderName(extensionModel, implicitConfigurationModel)) .isPresent()) { registerConfigurationProvider(implicitConfigurationProviderFactory.createImplicitConfigurationProvider(extensionModel, implicitConfigurationModel, muleEvent, muleContext)); } } } private Optional<ConfigurationModel> getConfigurationModelForExtension(ExtensionModel extensionModel, Set<ConfigurationModel> assignableConfigurationModels) { List<ConfigurationModel> implicitConfigurationModels = assignableConfigurationModels.stream().filter(ExtensionModelUtils::canBeUsedImplicitly).collect(Collectors.toList()); if (implicitConfigurationModels.isEmpty()) { return empty(); } else if (implicitConfigurationModels.size() == 1) { return ofNullable(implicitConfigurationModels.get(0)); } throw new IllegalStateException(format("No configuration can be inferred for extension '%s'", extensionModel.getName())); } /** * {@inheritDoc} */ @Override public Set<ExtensionModel> getExtensions() { return extensionRegistry.getExtensions(); } /** * {@inheritDoc} */ @Override public Optional<ExtensionModel> getExtension(String extensionName) { return extensionRegistry.getExtension(extensionName); } private ConfigurationExpirationMonitor newConfigurationExpirationMonitor() { Time freq = getConfigurationExpirationFrequency(); return newBuilder(extensionRegistry, muleContext).runEvery(freq.getTime(), freq.getUnit()) .onExpired((key, object) -> disposeConfiguration(key, object)).build(); } /** * {@inheritDoc} */ @Override public ExtensionManifest parseExtensionManifestXml(URL manifestUrl) { try (InputStream manifestStream = manifestUrl.openStream()) { return new ExtensionManifestXmlSerializer().deserialize(IOUtils.toString(manifestStream)); } catch (IOException e) { throw new MuleRuntimeException(createStaticMessage("Could not read extension manifest on plugin " + manifestUrl.toString()), e); } } private void disposeConfiguration(String key, ConfigurationInstance configuration) { try { stopIfNeeded(configuration); disposeIfNeeded(configuration, LOGGER); } catch (Exception e) { LOGGER.error(format("Could not dispose expired dynamic config of key '%s' and type %s", key, configuration.getClass().getName()), e); } } private Time getConfigurationExpirationFrequency() { ExtensionConfig extensionConfig = muleContext.getConfiguration().getExtension(ExtensionConfig.class); final Time defaultFreq = new Time(5L, TimeUnit.MINUTES); if (extensionConfig != null) { return extensionConfig.getDynamicConfigExpirationFrequency().orElse(defaultFreq); } else { return defaultFreq; } } @Override public void setMuleContext(MuleContext muleContext) { this.muleContext = muleContext; } }