/**
* Mule Development Kit
* Copyright 2010-2011 (c) MuleSoft, Inc. All rights reserved. http://www.mulesoft.com
*
* Licensed 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.mule.devkit.dynamic.api.invocation;
import org.mule.api.Capabilities;
import org.mule.api.Capability;
import org.mule.api.MuleContext;
import org.mule.api.MuleException;
import org.mule.api.lifecycle.Disposable;
import org.mule.api.lifecycle.InitialisationException;
import org.mule.api.processor.MessageProcessor;
import org.mule.api.registry.RegistrationException;
import org.mule.api.source.MessageSource;
import org.mule.api.transformer.DataType;
import org.mule.api.transformer.Transformer;
import org.mule.api.transformer.TransformerException;
import org.mule.devkit.dynamic.api.helper.LifeCycles;
import org.mule.devkit.dynamic.api.helper.MuleContexts;
import org.mule.devkit.dynamic.api.helper.Parameters;
import org.mule.devkit.dynamic.api.helper.Reflections;
import org.mule.devkit.dynamic.api.model.Module;
import org.mule.transformer.types.DataTypeFactory;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
public class DynamicModule implements Disposable {
/**
* Encapsulate logic dealing with event received from a {@link org.mule.api.annotations.Source}.
* @param <T>
*/
public interface Listener<T> {
/**
* Called every time associated {@link org.mule.api.annotations.Source} fires an event.
* @param event
*/
void onEvent(T event);
}
private static final Logger LOGGER = Logger.getLogger(DynamicModule.class.getPackage().getName());
private final MuleContext context;
private final Module module;
private static final String MODULE_OBJECT_REGISTRY_KEY = "moduleObject";
private final int retryMax;
protected static final int DEFAULT_RETRY_MAX = 5;
private final Map<String, Object> parameters;
private final Map<Class<?>, Invoker> invokerCache = new HashMap<Class<?>, Invoker>();
private final Map<Class<?>, Registrar> registrarCache = new HashMap<Class<?>, Registrar>();
public DynamicModule(final Module module) {
this(module, Collections.<String, Object>emptyMap());
}
public DynamicModule(final Module module, final Map<String, Object> overriddenParameters) {
this(module, overriddenParameters, DynamicModule.DEFAULT_RETRY_MAX);
}
public DynamicModule(final Module module, final Map<String, Object> overriddenParameters, final int retryMax) {
if (module == null) {
throw new IllegalArgumentException("null module");
}
if (overriddenParameters == null) {
throw new IllegalArgumentException("null overriddenParameters");
}
if (retryMax <= 0) {
throw new IllegalArgumentException("retryMax must be > 0");
}
validateParameterTypeCorrectness(module.getParameters(), overriddenParameters);
ensureNoMissingParameters(module.getParameters(), overriddenParameters);
try {
this.context = MuleContexts.defaultMuleContext();
} catch (Exception e) {
throw new RuntimeException(e);
}
this.module = module;
this.retryMax = retryMax;
this.parameters = allParameters(module.getParameters(), overriddenParameters);
try {
initialise();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
protected final MuleContext getMuleContext() {
return this.context;
}
private void initialise() throws InitialisationException, RegistrationException, MuleException {
final Capabilities capabilities = this.module.getModule();
if (capabilities.isCapableOf(Capability.LIFECYCLE_CAPABLE)) {
LifeCycles.initialise(capabilities);
LifeCycles.start(capabilities);
}
if (this.module.getConnectionManager() != null) {
LifeCycles.initialise(this.module.getConnectionManager());
}
//Apply parameters to the ModuleObject.
final Object moduleObject = this.module.getModuleObject();
for (final Map.Entry<String, Object> entry : this.parameters.entrySet()) {
Reflections.set(moduleObject, entry.getKey(), entry.getValue());
}
this.context.getRegistry().registerObject(DynamicModule.MODULE_OBJECT_REGISTRY_KEY, moduleObject);
}
protected final void validateParameterTypeCorrectness(final List<Module.Parameter> defaultParameters, final Map<String, Object> overriddenParameters) {
final List<String> incorrectParameterTypes = new LinkedList<String>();
//Ensure all overridden parameter types are correct.
for (final Map.Entry<String, Object> entry : overriddenParameters.entrySet()) {
final String parameterName = entry.getKey();
final Module.Parameter parameter = Parameters.getParameter(defaultParameters, parameterName);
if (parameter == null) {
continue;
}
final Class<?> expectedType = Reflections.asType(parameter.getType());
final Class<?> type = entry.getValue().getClass();
if (!expectedType.isAssignableFrom(type)) {
final StringBuilder details = new StringBuilder(parameterName);
details.append("(type ").append(type.getCanonicalName()).append(" is not assignable to ").append(expectedType.getCanonicalName()).append(")");
incorrectParameterTypes.add(details.toString());
}
}
if (!incorrectParameterTypes.isEmpty()) {
final String terminaison = incorrectParameterTypes.size()>1?"s":"";
throw new IllegalArgumentException("Incorrect type"+terminaison+" for parameter"+terminaison+" <"+incorrectParameterTypes+">");
}
}
protected final void ensureNoMissingParameters(final List<Module.Parameter> defaultParameters, final Map<String, Object> overriddenParameters) {
final List<String> missingMandatoryParameters = new LinkedList<String>();
//Ensure all mandatory parameter values are provided.
for (final Module.Parameter parameter : defaultParameters) {
if (!parameter.isOptional() && parameter.getDefaultValue() == null
&& !overriddenParameters.containsKey(parameter.getName())) {
missingMandatoryParameters.add(parameter.getName());
}
}
if (!missingMandatoryParameters.isEmpty()) {
final String terminaison = missingMandatoryParameters.size()>1?"s":"";
throw new IllegalArgumentException("Value"+terminaison+" for parameter"+terminaison+" <"+missingMandatoryParameters+"> must be provided");
}
}
/**
* Aggregate all parameters: default and overridden ones.
* Overridden parameters take precedence over default ones.
* @return
*/
protected final Map<String, Object> allParameters(final List<Module.Parameter> defaultParameters, final Map<String, Object> overriddenParameters) {
final Map<String, Object> allParameters = new HashMap<String, Object>();
final Set<String> defaultParameterNames = new HashSet<String>();
for (final Module.Parameter parameter : defaultParameters) {
//Only add default values
if (parameter.getDefaultValue() != null) {
try {
final Transformer transformer = this.context.getRegistry().lookupTransformer(DataType.STRING_DATA_TYPE, DataTypeFactory.create(parameter.getType()));
allParameters.put(parameter.getName(), transformer.transform(parameter.getDefaultValue()));
} catch (TransformerException e) {
throw new RuntimeException("Failed to transform <"+parameter.getDefaultValue()+">", e);
}
}
defaultParameterNames.add(parameter.getName());
}
for (final Map.Entry<String, Object> entry : overriddenParameters.entrySet()) {
//Only add existing parameters
final String parameterName = entry.getKey();
if (!defaultParameterNames.contains(parameterName)) {
if (DynamicModule.LOGGER.isLoggable(Level.WARNING)) {
DynamicModule.LOGGER.log(Level.WARNING, "Value has been provided for unknown parameter <{0}>; it will be ignored", parameterName);
}
continue;
}
allParameters.put(parameterName, entry.getValue());
}
return allParameters;
}
/**
* @param processorName
* @return {@link Module.Processor} extracted from {@link Module$Processor}with specified name, null otherwise
*/
protected final Module.Processor findProcessor(final String processorName) {
for (final Module.Processor processor : this.module.getProcessors()) {
if (processorName.equals(processor.getName())) {
return processor;
}
}
return null;
}
/**
* @param messageProcessor
* @return an {@link Invoker} for {@link MessageProcessor}. Creates it if needed.
* @throws InitialisationException
* @throws MuleException
* @see #createInvoker(org.mule.api.processor.MessageProcessor)
*/
protected synchronized final Invoker getInvoker(final MessageProcessor messageProcessor) throws InitialisationException, MuleException {
final Class<?> key = messageProcessor.getClass();
if (this.invokerCache.containsKey(key)) {
return this.invokerCache.get(key);
}
final Invoker invoker = new Invoker(this.context, messageProcessor, this.retryMax);
this.invokerCache.put(key, invoker);
return invoker;
}
/**
* Invoke `processorName` with provided `overriddenParameters`. Non overridden parameters will rely on default value.
* @param <T>
* @param processorName
* @param overriddenParameters
* @return
* @throws InitialisationException
* @throws MuleException
*/
public final <T> T invoke(final String processorName, final Map<String, Object> overriddenParameters) throws InitialisationException, MuleException {
if (processorName == null) {
throw new IllegalArgumentException("The processor name cannot be null");
}
if (overriddenParameters == null) {
throw new IllegalArgumentException("The overridenParameters cannot be null");
}
final Module.Processor processor = findProcessor(processorName);
if (processor == null) {
throw new IllegalArgumentException("Cannot find a Processor named <"+processorName+">");
}
validateParameterTypeCorrectness(processor.getParameters(), overriddenParameters);
ensureNoMissingParameters(processor.getParameters(), overriddenParameters);
return this.<T>invoke(processor.getMessageProcessor(), allParameters(processor.getParameters(), overriddenParameters));
}
protected <T> T invoke(final MessageProcessor messageProcessor, final Map<String, Object> parameters) throws InitialisationException, MuleException {
return getInvoker(messageProcessor).<T>invoke(parameters);
}
/**
* @param sourceName
* @return {@link Module.Source} extracted from {@link Module$Source}with specified name, null otherwise
*/
protected final Module.Source findSource(final String sourceName) {
for (final Module.Source source : this.module.getSources()) {
if (sourceName.equals(source.getName())) {
return source;
}
}
return null;
}
/**
* @param messageSource
* @return a cached {@link Invoker} for {@link MessageProcessor}.
* @throws InitialisationException
* @throws MuleException
* @see #createInvoker(org.mule.api.processor.MessageProcessor)
*/
protected synchronized final Registrar getRegistrar(final MessageSource messageSource) throws InitialisationException, MuleException {
final Class<?> key = messageSource.getClass();
return this.registrarCache.get(key);
}
/**
* @param messageSource
* @return a new cached {@link Regsitrar}
*/
protected synchronized final Registrar createAndCacheRegistrar(final MessageSource messageSource) {
final Class<?> key = messageSource.getClass();
final Registrar registrar = new Registrar(this.context, messageSource);
this.registrarCache.put(key, registrar);
return registrar;
}
/**
* Subscribe {@link Listener} to `sourceName` {@link org.mule.api.annotations.Source} with `overriddenParameters`.
* @param sourceName
* @param overriddenParameters
* @param listener
* @throws InitialisationException
* @throws MuleException
*/
public synchronized final void subscribe(final String sourceName, final Map<String, Object> overriddenParameters, final Listener listener) throws InitialisationException, MuleException {
if (sourceName == null) {
throw new IllegalArgumentException("null sourceName");
}
if (overriddenParameters == null) {
throw new IllegalArgumentException("null overriddenParameters");
}
final Module.Source source = findSource(sourceName);
if (source == null) {
throw new IllegalArgumentException("Cannot find a Source named <"+sourceName+">");
}
validateParameterTypeCorrectness(source.getParameters(), overriddenParameters);
ensureNoMissingParameters(source.getParameters(), overriddenParameters);
final Registrar registrar = getRegistrar(source.getMessageSource());
if (registrar != null) {
throw new IllegalStateException("Source <"+sourceName+"> is already subscribed");
}
createAndCacheRegistrar(source.getMessageSource()).start(allParameters(source.getParameters(), overriddenParameters), listener);
}
/**
* Unsubscribe {@link Listener} previously registered to `sourceName` {@link Source}.
* @param sourceName
* @throws InitialisationException
* @throws MuleException
*/
public final void unsubscribe(final String sourceName) throws MuleException {
if (sourceName == null) {
throw new IllegalArgumentException("null sourceName");
}
final Module.Source source = findSource(sourceName);
if (source == null) {
throw new IllegalArgumentException("Cannot find a Source named <"+sourceName+">");
}
final Registrar registrar = getRegistrar(source.getMessageSource());
if (registrar == null) {
throw new IllegalStateException("Source <"+sourceName+"> is not subscribed");
}
registrar.stop();
}
/**
* Cleanup all internal resources:
* * call {@link Invoker#dispose()} for all cached {@link Invoker}
* * call {@link Registrar#stop()} for all cached {@link Registrar}
* * call {@link MuleContext#dispose()}
*/
@Override
public final void dispose() {
for (final Invoker invoker : this.invokerCache.values()) {
invoker.dispose();
}
for (final Registrar registrar : this.registrarCache.values()) {
try {
registrar.stop();
} catch (MuleException e) {
if (DynamicModule.LOGGER.isLoggable(Level.WARNING)) {
DynamicModule.LOGGER.log(Level.WARNING, "Got exception while closing <"+registrar+">", e);
}
}
}
this.context.dispose();
}
}