/* * 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.util.concurrent.TimeUnit.SECONDS; import static org.mule.runtime.api.util.Preconditions.checkArgument; import org.mule.runtime.api.exception.MuleException; import org.mule.runtime.api.scheduler.Scheduler; import org.mule.runtime.core.api.MuleContext; import org.mule.runtime.extension.api.runtime.ConfigurationInstance; import com.google.common.collect.Multimap; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.function.BiConsumer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Default implementation of {@link ConfigurationExpirationMonitor} which schedules tasks that run on a given {@link #frequency} * to check for dynamic configuration instances which should be expired. The selected instances are expired by being unregistered * and having the shutdown lifecycle applied to them. * <p/> * Instances of this class are immutable and should be built through a {@link Builder} which can be obtained through the * {@link Builder#newBuilder(ExtensionRegistry, MuleContext)} method * * @since 4.0 */ public final class DefaultConfigurationExpirationMonitor implements ConfigurationExpirationMonitor { private static final Logger LOGGER = LoggerFactory.getLogger(DefaultConfigurationExpirationMonitor.class); /** * A builder object for instances of {@link DefaultConfigurationExpirationMonitor} */ public static class Builder { /** * Creates a new builder instance * * @param extensionRegistry the {@link ExtensionRegistry} instance used to obtain the configuration instances * @param muleContext the {@link MuleContext} which owns the configuration instances * @return a new {@link Builder} */ public static Builder newBuilder(ExtensionRegistry extensionRegistry, MuleContext muleContext) { Builder builder = new Builder(); builder.manager.extensionRegistry = extensionRegistry; builder.manager.muleContext = muleContext; return builder; } private DefaultConfigurationExpirationMonitor manager = new DefaultConfigurationExpirationMonitor(); private Builder() {} /** * Specifies how often should this instance check for expired config instances * * @param frequency a scalar time value * @param timeUnit a {@link TimeUnit} which qualifies the {@code frequency} * @return {@code this} instance */ public Builder runEvery(long frequency, TimeUnit timeUnit) { manager.frequency = frequency; manager.timeUnit = timeUnit; return this; } /** * Provides a {@link BiConsumer} which receives each expired config instance and provides the behavior of how to expire it. * The {@link BiConsumer} will receive the instance registration key as the first argument and the instance itself as the * second * * @param expirationHandler a {@link BiConsumer} which acts as a expiration handler * @return {@code this} instance */ public Builder onExpired(BiConsumer<String, ConfigurationInstance> expirationHandler) { manager.expirationHandler = expirationHandler; return this; } /** * Validates the provided configuration and returns an actual {@link ConfigurationExpirationMonitor}. * * @return a {@link ConfigurationExpirationMonitor} */ public ConfigurationExpirationMonitor build() { checkArgument(manager.extensionRegistry != null, "extensionRegistry cannot be null"); checkArgument(manager.muleContext != null, "muleContext cannot be null"); checkArgument(manager.frequency > 0, "frequency must be greater than zero"); checkArgument(manager.timeUnit != null, "timeUnit cannot be null"); return manager; } } private ExtensionRegistry extensionRegistry; private MuleContext muleContext; private long frequency; private TimeUnit timeUnit; private BiConsumer<String, ConfigurationInstance> expirationHandler; private Scheduler executor; private ScheduledFuture<?> scheduledMonitoring; private DefaultConfigurationExpirationMonitor() {} /** * Starts a scheduler which fires expiration tasks on the given {@link #frequency} and executes the {@link #expirationHandler} * on each matching configuration instance * */ @Override public void beginMonitoring() { // TODO: Change the executor type when MULE-8870 is implemented executor = muleContext.getSchedulerService().ioScheduler(muleContext.getSchedulerBaseConfig() .withName("extension.expiration.manager").withShutdownTimeout(30, SECONDS)); scheduledMonitoring = executor.scheduleWithFixedDelay(() -> expire(), frequency, frequency, timeUnit); } private void expire() { if (stopChecking()) { return; } LOGGER.debug("Running configuration expiration cycle"); try { Multimap<String, ConfigurationInstance> expired = extensionRegistry.getExpiredConfigs(); if (LOGGER.isDebugEnabled()) { LOGGER.debug(expired.isEmpty() ? "No expired configuration instances were found" : "Found {} expired configurations", expired.size()); } expired.entries().stream().forEach(entry -> handleExpiration(entry.getKey(), entry.getValue())); } catch (Exception e) { LOGGER.error("Found exception trying to expire idle configurations. Will try again on next cycle", e); } } private void handleExpiration(String key, ConfigurationInstance config) { if (stopChecking()) { return; } try { expirationHandler.accept(key, config); LOGGER.debug("Configuration of key {} was expired", key); } catch (Exception e) { LOGGER .error(String.format("Could not process expiration for dynamic config '%s' of type '%s'. Will try again on next cycle", key, config.getClass().getName()), e); } } /** * Shutdowns the scheduler that executes the expiration tasks. It waits up to 30 seconds for it to shutdown and it throws a * {@link MuleException} if it could not be stopped */ @Override public void stopMonitoring() { scheduledMonitoring.cancel(false); executor.stop(); } private boolean stopChecking() { return muleContext.isStopping() || muleContext.isStopped(); } }