/*
* 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 org.mule.runtime.api.i18n.I18nMessageFactory.createStaticMessage;
import static org.mule.runtime.api.util.Preconditions.checkArgument;
import static org.mule.runtime.module.extension.internal.util.IntrospectionUtils.getParameterClasses;
import static org.mule.runtime.module.extension.internal.util.MuleExtensionUtils.getClassLoader;
import org.mule.runtime.api.exception.MuleException;
import org.mule.runtime.api.exception.MuleRuntimeException;
import org.mule.runtime.api.meta.model.ExtensionModel;
import org.mule.runtime.api.meta.model.config.ConfigurationModel;
import org.mule.runtime.core.api.registry.MuleRegistry;
import org.mule.runtime.core.api.registry.RegistrationException;
import org.mule.runtime.core.transformer.simple.StringToEnum;
import org.mule.runtime.core.util.collection.ImmutableListCollector;
import org.mule.runtime.extension.api.runtime.ConfigurationInstance;
import org.mule.runtime.extension.api.runtime.ConfigurationProvider;
import org.mule.runtime.extension.api.runtime.ExpirableConfigurationProvider;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* Hold the state related to registered {@link ExtensionModel extensionModels} and their instances.
* <p>
* It also provides utility methods and caches to easily locate pieces of such state.
* <p>
* It acts as a facade of the {@link MuleRegistry}, which is where {@link ConfigurationProvider} are finally stored.
*
* @since 3.7.0
*/
final class ExtensionRegistry {
private final LoadingCache<ExtensionModel, Multimap<ConfigurationModel, ConfigurationProvider>> providersByExtension =
CacheBuilder.newBuilder().build(new CacheLoader<ExtensionModel, Multimap<ConfigurationModel, ConfigurationProvider>>() {
@Override
public Multimap<ConfigurationModel, ConfigurationProvider> load(ExtensionModel key) throws Exception {
List<ConfigurationProvider> providers = registry.lookupObjects(ConfigurationProvider.class).stream()
.filter(provider -> provider.getExtensionModel() == key).collect(new ImmutableListCollector<>());
Multimap multimap = HashMultimap.create();
providers.forEach(p -> multimap.put(p.getConfigurationModel(), p));
return multimap;
}
});
private final Map<ExtensionEntityKey, ExtensionModel> extensions = new ConcurrentHashMap<>();
private final Set<Class<? extends Enum>> enumClasses = new HashSet<>();
private final MuleRegistry registry;
/**
* Creates a new instance
*
* @param registry the {@link MuleRegistry} to use for holding instances
*/
ExtensionRegistry(MuleRegistry registry) {
this.registry = registry;
}
/**
* Registers the given {@code extension}
*
* @param name the registration name you want for the {@code extension}
* @param extensionModel a {@link ExtensionModel}
*/
void registerExtension(String name, ExtensionModel extensionModel) {
extensions.put(new ExtensionEntityKey(name), extensionModel);
getParameterClasses(extensionModel, getClassLoader(extensionModel)).stream()
.filter(type -> Enum.class.isAssignableFrom(type))
.forEach(type -> {
final Class<Enum> enumClass = (Class<Enum>) type;
if (enumClasses.add(enumClass)) {
try {
registry.registerTransformer(new StringToEnum(enumClass));
} catch (MuleException e) {
throw new MuleRuntimeException(createStaticMessage("Could not register transformer for enum "
+ enumClass.getName()), e);
}
}
});
}
/**
* @return an immutable view of the currently registered {@link ExtensionModel}
*/
Set<ExtensionModel> getExtensions() {
return ImmutableSet.copyOf(extensions.values());
}
/**
* @return an {@link Optional} with the {@link ExtensionModel} which name and vendor equals {@code extensionName} and
* {@code vendor}
*/
Optional<ExtensionModel> getExtension(String extensionName) {
return Optional.ofNullable(extensions.get(new ExtensionEntityKey(extensionName)));
}
/**
* @param name the registration name of the {@link ExtensionModel} you want to test
* @return {@code true} if an {@link ExtensionModel} is registered under {@code name}. {@code false} otherwise
*/
boolean containsExtension(String name) {
return extensions.containsKey(new ExtensionEntityKey(name));
}
/**
* Returns all the {@link ConfigurationProvider configuration providers} which serve {@link ConfigurationModel configuration
* models} owned by {@code extensionModel}
*
* @param extensionModel a registered {@link ExtensionModel}
* @return an immutable {@link List}. Might be empty but will never be {@code null}
*/
Collection<ConfigurationProvider> getConfigurationProviders(ExtensionModel extensionModel) {
return providersByExtension.getUnchecked(extensionModel).values();
}
/**
* Returns all the {@link ConfigurationProvider configuration providers} associated to a given {@link ConfigurationModel configuration
* model} owned by the {@code extensionModel}.
*
* @param extensionModel a registered {@link ExtensionModel}
* @return an immutable {@link List}. Might be empty but will never be {@code null}
*/
Collection<ConfigurationProvider> getConfigurationProviders(ExtensionModel extensionModel,
ConfigurationModel configurationModel) {
return providersByExtension.getUnchecked(extensionModel).get(configurationModel);
}
/**
* Returns the {@link ConfigurationProvider} registered under the given {@code key}
*
* @param key the key for the fetched {@link ConfigurationProvider}
* @return a {@link ConfigurationProvider}
*/
Optional<ConfigurationProvider> getConfigurationProvider(String key) {
return Optional.ofNullable(registry.get(key));
}
/**
* Registers the given {@code configurationProvider} in the underlying {@link #registry}.
* <p>
* The {@code configurationProvider} is registered under a key matching its {@link ConfigurationProvider#getName()}.
*
* @param configurationProvider a {@link ConfigurationProvider} to be registered
* @throws IllegalArgumentException if {@code configurationProvider} is {@code null}
* @throws MuleRuntimeException if the {@code configurationProvider} could not be registered
*/
void registerConfigurationProvider(ConfigurationProvider configurationProvider) {
checkArgument(configurationProvider != null, "Cannot register a null configurationProvider");
try {
registry.registerObject(configurationProvider.getName(), configurationProvider);
} catch (RegistrationException e) {
throw new MuleRuntimeException(createStaticMessage(format("Found exception while registering configuration provider '%s'",
configurationProvider.getName())),
e);
}
providersByExtension.invalidate(configurationProvider.getExtensionModel());
}
/**
* Returns a {@link Multimap} which keys are registrations keys and the values are the {@link ConfigurationInstance} instances
* which are expired
*
* @return an immutable {@link Multimap}
*/
Multimap<String, ConfigurationInstance> getExpiredConfigs() {
ListMultimap<String, ConfigurationInstance> expired = ArrayListMultimap.create();
for (ExtensionModel extensionModel : extensions.values()) {
getConfigurationProviders(extensionModel).stream().filter(provider -> provider instanceof ExpirableConfigurationProvider)
.forEach(provider -> expired.putAll(provider.getName(), ((ExpirableConfigurationProvider) provider).getExpired()));
}
return Multimaps.unmodifiableListMultimap(expired);
}
}