/*
* 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.core.component;
import static org.mule.runtime.api.i18n.I18nMessageFactory.createStaticMessage;
import org.mule.runtime.core.DefaultMuleEventContext;
import org.mule.runtime.core.api.DefaultMuleException;
import org.mule.runtime.core.api.MuleContext;
import org.mule.runtime.core.api.Event;
import org.mule.runtime.core.api.MuleEventContext;
import org.mule.runtime.api.exception.MuleException;
import org.mule.runtime.core.api.component.JavaComponent;
import org.mule.runtime.core.api.component.LifecycleAdapter;
import org.mule.runtime.core.api.construct.FlowConstruct;
import org.mule.runtime.api.lifecycle.Disposable;
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.core.api.model.EntryPointResolverSet;
import org.mule.runtime.core.config.i18n.CoreMessages;
import org.mule.runtime.core.api.model.resolvers.LegacyEntryPointResolverSet;
import org.mule.runtime.core.internal.registry.JSR250ValidatorProcessor;
import org.mule.runtime.core.util.annotation.AnnotationMetaData;
import org.mule.runtime.core.util.annotation.AnnotationUtils;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <code>DefaultComponentLifecycleAdapter</code> is a default implementation of {@link LifecycleAdapter} for use with
* {@link JavaComponent} that expects component instances to implement Mule lifecycle interfaces in order to receive lifecycle.
* Lifecycle interfaces supported are -
* <ul>
* <li>{@link Initialisable}</li>
* <li>{@link Startable}</li>
* <li>{@link Stoppable}</li>
* <li>{@link Disposable}</li>
* </ul>
* This implementation also supports JSR-250 lifecycle annotations {@link javax.annotation.PostConstruct} (for initialisation)
* and/or {@link javax.annotation.PreDestroy} (for disposal of the object). Only one of each annotation can be used per component
* object.
*
* @see JSR250ValidatorProcessor for details about the rules for using JSR-250 lifecycle
* annotations
*/
public class DefaultComponentLifecycleAdapter implements LifecycleAdapter {
/**
* logger used by this class
*/
protected static final Logger logger = LoggerFactory.getLogger(DefaultComponentLifecycleAdapter.class);
protected Object componentObject;
protected JavaComponent component;
protected EntryPointResolverSet entryPointResolver;
protected FlowConstruct flowConstruct;
protected boolean isInitialisable = false;
protected boolean isStartable = false;
protected boolean isStoppable = false;
protected boolean isDisposable = false;
protected Method initMethod;
protected Method disposeMethod;
private boolean started = false;
private boolean disposed = false;
protected MuleContext muleContext;
public DefaultComponentLifecycleAdapter(Object componentObject, JavaComponent component, FlowConstruct flowConstruct,
MuleContext muleContext)
throws MuleException {
if (muleContext == null) {
throw new IllegalStateException("No muleContext provided");
}
if (componentObject == null) {
throw new IllegalArgumentException("POJO Service cannot be null");
}
if (entryPointResolver == null) {
entryPointResolver = new LegacyEntryPointResolverSet();
}
this.componentObject = componentObject;
this.component = component;
this.flowConstruct = flowConstruct;
// save a ref for later disposal call
this.muleContext = muleContext;
setLifecycleFlags();
}
public DefaultComponentLifecycleAdapter(Object componentObject, JavaComponent component, FlowConstruct flowConstruct,
EntryPointResolverSet entryPointResolver, MuleContext muleContext)
throws MuleException {
this(componentObject, component, flowConstruct, muleContext);
this.entryPointResolver = entryPointResolver;
}
protected void setLifecycleFlags() {
Object object = componentObject;
initMethod = findInitMethod(object);
disposeMethod = findDisposeMethod(object);
isInitialisable = initMethod != null;
isDisposable = disposeMethod != null;
isStartable = Startable.class.isInstance(object);
isStoppable = Stoppable.class.isInstance(object);
}
protected Method findInitMethod(Object object) {
if (object instanceof Initialisable) {
try {
return object.getClass().getMethod(Initialisable.PHASE_NAME);
} catch (NoSuchMethodException e) {
// ignore
}
}
List<AnnotationMetaData> metaData = AnnotationUtils.getMethodAnnotations(object.getClass(), PostConstruct.class);
if (metaData.size() == 0) {
return null;
} else if (metaData.size() > 1) {
throw new IllegalArgumentException(CoreMessages.objectHasMoreThanOnePostConstructAnnotation(object.getClass())
.getMessage());
} else {
Method m = (Method) metaData.get(0).getMember();
new JSR250ValidatorProcessor().validateLifecycleMethod(m);
return m;
}
}
protected Method findDisposeMethod(Object object) {
if (object instanceof Disposable) {
try {
return object.getClass().getMethod(Disposable.PHASE_NAME);
} catch (NoSuchMethodException e) {
// ignore
}
}
List<AnnotationMetaData> metaData = AnnotationUtils.getMethodAnnotations(object.getClass(), PreDestroy.class);
if (metaData.size() == 0) {
return null;
} else if (metaData.size() > 1) {
throw new IllegalArgumentException(CoreMessages.objectHasMoreThanOnePreDestroyAnnotation(object.getClass()).getMessage());
} else {
Method m = (Method) metaData.get(0).getMember();
new JSR250ValidatorProcessor().validateLifecycleMethod(m);
return m;
}
}
/**
* Propagates initialise() life-cycle to component object implementations if they implement the mule {@link Initialisable}
* interface.
* <p/>
* <b>NOTE:</b> It is up to component implementations to ensure their implementation of <code>initialise()</code> is
* thread-safe.
*/
@Override
public void initialise() throws InitialisationException {
if (isInitialisable) {
try {
initMethod.invoke(componentObject);
} catch (IllegalAccessException e) {
throw new InitialisationException(e, this);
} catch (InvocationTargetException e) {
throw new InitialisationException(e.getTargetException(), this);
}
}
}
/**
* Propagates start() life-cycle to component object implementations if they implement the mule {@link Startable} interface.
* NOT: It is up to component implementations to ensure their implementation of start() is thread-safe.
*/
@Override
public void start() throws MuleException {
if (isStartable) {
try {
((Startable) componentObject).start();
started = true;
} catch (Exception e) {
throw new DefaultMuleException(CoreMessages.failedToStart("Service: " + flowConstruct.getName()), e);
}
} else {
started = true;
}
}
/**
* Propagates stop() life-cycle to component object implementations if they implement the mule {@link Stoppable} interface. NOT:
* It is up to component implementations to ensure their implementation of stop() is thread-safe.
*/
@Override
public void stop() throws MuleException {
if (isStoppable) {
try {
((Stoppable) componentObject).stop();
started = false;
} catch (Exception e) {
throw new DefaultMuleException(CoreMessages.failedToStop("Service: " + flowConstruct.getName()), e);
}
} else {
started = false;
}
}
/**
* Propagates dispose() life-cycle to component object implementations if they implement the mule {@link Disposable} interface.
* NOT: It is up to component implementations to ensure their implementation of dispose() is thread-safe.
*/
@Override
public void dispose() {
try {
if (isDisposable) {
// make sure we haven't lost the reference to the object
Object o = componentObject;
if (o != null) {
try {
disposeMethod.invoke(o);
} catch (InvocationTargetException e) {
// unwrap
throw e.getTargetException();
}
}
}
componentObject = null;
} catch (Throwable e) {
logger.error("failed to dispose: " + flowConstruct.getName(), e);
}
disposed = true;
}
/**
* @return true if the service has been started
*/
@Override
public boolean isStarted() {
return started;
}
/**
* @return whether the service managed by this lifecycle has been disposed
*/
@Override
public boolean isDisposed() {
return disposed;
}
@Override
public Object invoke(Event event, Event.Builder eventBuilder) throws MuleException {
// Invoke method
MuleEventContext eventContext = new DefaultMuleEventContext(flowConstruct, event);
Object result;
try {
if (componentObject == null) {
throw new ComponentException(createStaticMessage("componentObject is null"), component);
}
// Use the overriding entrypoint resolver if one is set
if (component.getEntryPointResolverSet() != null) {
result = component.getEntryPointResolverSet().invoke(componentObject, eventContext, eventBuilder);
} else {
result = entryPointResolver.invoke(componentObject, eventContext, eventBuilder);
}
} catch (Exception e) {
throw new ComponentException(createStaticMessage("%s: %s", e.getClass().getName(), e.getMessage()), component, e);
}
return result;
}
}