
package org.mule.modules.salesforce.processors;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import com.sforce.ws.transport.SoapConnection.SessionTimedOutException;
import org.mule.api.MessagingException;
import org.mule.api.MuleContext;
import org.mule.api.MuleEvent;
import org.mule.api.MuleException;
import org.mule.api.MuleMessage;
import org.mule.api.construct.FlowConstruct;
import org.mule.api.construct.FlowConstructAware;
import org.mule.api.context.MuleContextAware;
import org.mule.api.lifecycle.Disposable;
import org.mule.api.lifecycle.Initialisable;
import org.mule.api.lifecycle.InitialisationException;
import org.mule.api.lifecycle.Startable;
import org.mule.api.lifecycle.Stoppable;
import org.mule.api.processor.MessageProcessor;
import org.mule.api.registry.RegistrationException;
import org.mule.api.transformer.Transformer;
import org.mule.config.i18n.CoreMessages;
import org.mule.config.i18n.MessageFactory;
import org.mule.modules.salesforce.AbstractExpressionEvaluator;
import org.mule.modules.salesforce.SalesforceModule;
import org.mule.modules.salesforce.adapters.SalesforceModuleConnectionManager;
import org.mule.modules.salesforce.adapters.SalesforceModuleInjectionAdapter;
import org.mule.transformer.TransformerTemplate;
import org.mule.transport.NullPayload;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * GetDeletedRangeMessageProcessor invokes the {@link org.mule.modules.salesforce.SalesforceModule#getDeletedRange(java.lang.String, java.util.Calendar, java.util.Calendar)} method in {@link SalesforceModule }. For each argument there is a field in this processor to match it.  Before invoking the actual method the processor will evaluate and transform where possible to the expected argument type.
 * 
 */
public class GetDeletedRangeMessageProcessor
    extends AbstractExpressionEvaluator
    implements FlowConstructAware, MuleContextAware, Disposable, Initialisable, Startable, Stoppable, MessageProcessor
{

    private Object type;
    private String _typeType;
    private Object startTime;
    private Calendar _startTimeType;
    private Object endTime;
    private Calendar _endTimeType;
    private Object username;
    private String _usernameType;
    private Object password;
    private String _passwordType;
    private Object securityToken;
    private String _securityTokenType;
    private static Logger logger = LoggerFactory.getLogger(GetDeletedRangeMessageProcessor.class);
    /**
     * Module object
     * 
     */
    private Object moduleObject;
    /**
     * Mule Context
     * 
     */
    private MuleContext muleContext;
    /**
     * Flow construct
     * 
     */
    private FlowConstruct flowConstruct;
    /**
     * Variable used to track how many retries we have attempted on this message processor
     * 
     */
    private AtomicInteger retryCount;
    /**
     * Maximum number of retries that can be attempted.
     * 
     */
    private int retryMax;

    /**
     * Obtains the expression manager from the Mule context and initialises the connector. If a target object  has not been set already it will search the Mule registry for a default one.
     * 
     * @throws InitialisationException
     */
    public void initialise()
        throws InitialisationException
    {
        retryCount = new AtomicInteger();
        if (moduleObject == null) {
            try {
                moduleObject = muleContext.getRegistry().lookupObject(SalesforceModuleConnectionManager.class);
                if (moduleObject == null) {
                    throw new InitialisationException(MessageFactory.createStaticMessage("Cannot find object"), this);
                }
            } catch (RegistrationException e) {
                throw new InitialisationException(CoreMessages.initialisationFailure("org.mule.modules.salesforce.adapters.SalesforceModuleConnectionManager"), e, this);
            }
        }
        if (moduleObject instanceof String) {
            moduleObject = muleContext.getRegistry().lookupObject(((String) moduleObject));
            if (moduleObject == null) {
                throw new InitialisationException(MessageFactory.createStaticMessage("Cannot find object by config name"), this);
            }
        }
    }

    public void start()
        throws MuleException
    {
    }

    public void stop()
        throws MuleException
    {
    }

    public void dispose() {
    }

    /**
     * Set the Mule context
     * 
     * @param context Mule context to set
     */
    public void setMuleContext(MuleContext context) {
        this.muleContext = context;
    }

    /**
     * Sets flow construct
     * 
     * @param flowConstruct Flow construct to set
     */
    public void setFlowConstruct(FlowConstruct flowConstruct) {
        this.flowConstruct = flowConstruct;
    }

    /**
     * Sets the instance of the object under which the processor will execute
     * 
     * @param moduleObject Instace of the module
     */
    public void setModuleObject(Object moduleObject) {
        this.moduleObject = moduleObject;
    }

    /**
     * Sets retryMax
     * 
     * @param value Value to set
     */
    public void setRetryMax(int value) {
        this.retryMax = value;
    }

    /**
     * Sets startTime
     * 
     * @param value Value to set
     */
    public void setStartTime(Object value) {
        this.startTime = value;
    }

    /**
     * Sets endTime
     * 
     * @param value Value to set
     */
    public void setEndTime(Object value) {
        this.endTime = value;
    }

    /**
     * Sets type
     * 
     * @param value Value to set
     */
    public void setType(Object value) {
        this.type = value;
    }

    /**
     * Sets username
     * 
     * @param value Value to set
     */
    public void setUsername(Object value) {
        this.username = value;
    }

    /**
     * Sets securityToken
     * 
     * @param value Value to set
     */
    public void setSecurityToken(Object value) {
        this.securityToken = value;
    }

    /**
     * Sets password
     * 
     * @param value Value to set
     */
    public void setPassword(Object value) {
        this.password = value;
    }

    /**
     * Invokes the MessageProcessor.
     * 
     * @param event MuleEvent to be processed
     * @throws MuleException
     */
    public MuleEvent process(MuleEvent event)
        throws MuleException
    {
        MuleMessage _muleMessage = event.getMessage();
        SalesforceModuleConnectionManager _castedModuleObject = null;
        if (moduleObject instanceof String) {
            _castedModuleObject = ((SalesforceModuleConnectionManager) muleContext.getRegistry().lookupObject(((String) moduleObject)));
            if (_castedModuleObject == null) {
                throw new MessagingException(CoreMessages.failedToCreate("getDeletedRange"), event, new RuntimeException("Cannot find the configuration specified by the config-ref attribute."));
            }
        } else {
            _castedModuleObject = ((SalesforceModuleConnectionManager) moduleObject);
        }
        String _transformedUsername = null;
        String _transformedPassword = null;
        String _transformedSecurityToken = null;
        SalesforceModuleInjectionAdapter connection = null;
        try {
            if (username!= null) {
                _transformedUsername = ((String) evaluateAndTransform(muleContext, _muleMessage, GetDeletedRangeMessageProcessor.class.getDeclaredField("_usernameType").getGenericType(), null, username));
            } else {
                if (_castedModuleObject.getUsername() == null) {
                    throw new MessagingException(CoreMessages.failedToCreate("getDeletedRange"), event, new RuntimeException("You must provide a username at the config or the message processor level."));
                }
                _transformedUsername = ((String) evaluateAndTransform(muleContext, _muleMessage, GetDeletedRangeMessageProcessor.class.getDeclaredField("_usernameType").getGenericType(), null, _castedModuleObject.getUsername()));
            }
            if (password!= null) {
                _transformedPassword = ((String) evaluateAndTransform(muleContext, _muleMessage, GetDeletedRangeMessageProcessor.class.getDeclaredField("_passwordType").getGenericType(), null, password));
            } else {
                if (_castedModuleObject.getPassword() == null) {
                    throw new MessagingException(CoreMessages.failedToCreate("getDeletedRange"), event, new RuntimeException("You must provide a password at the config or the message processor level."));
                }
                _transformedPassword = ((String) evaluateAndTransform(muleContext, _muleMessage, GetDeletedRangeMessageProcessor.class.getDeclaredField("_passwordType").getGenericType(), null, _castedModuleObject.getPassword()));
            }
            if (securityToken!= null) {
                _transformedSecurityToken = ((String) evaluateAndTransform(muleContext, _muleMessage, GetDeletedRangeMessageProcessor.class.getDeclaredField("_securityTokenType").getGenericType(), null, securityToken));
            } else {
                if (_castedModuleObject.getSecurityToken() == null) {
                    throw new MessagingException(CoreMessages.failedToCreate("getDeletedRange"), event, new RuntimeException("You must provide a securityToken at the config or the message processor level."));
                }
                _transformedSecurityToken = ((String) evaluateAndTransform(muleContext, _muleMessage, GetDeletedRangeMessageProcessor.class.getDeclaredField("_securityTokenType").getGenericType(), null, _castedModuleObject.getSecurityToken()));
            }
            String _transformedType = ((String) evaluateAndTransform(muleContext, _muleMessage, GetDeletedRangeMessageProcessor.class.getDeclaredField("_typeType").getGenericType(), null, type));
            Calendar _transformedStartTime = ((Calendar) evaluateAndTransform(muleContext, _muleMessage, GetDeletedRangeMessageProcessor.class.getDeclaredField("_startTimeType").getGenericType(), null, startTime));
            Calendar _transformedEndTime = ((Calendar) evaluateAndTransform(muleContext, _muleMessage, GetDeletedRangeMessageProcessor.class.getDeclaredField("_endTimeType").getGenericType(), null, endTime));
            if (logger.isDebugEnabled()) {
                StringBuilder _messageStringBuilder = new StringBuilder();
                _messageStringBuilder.append("Attempting to acquire a connection using ");
                _messageStringBuilder.append("[username = ");
                _messageStringBuilder.append(_transformedUsername);
                _messageStringBuilder.append("] ");
                _messageStringBuilder.append("[securityToken = ");
                _messageStringBuilder.append(_transformedSecurityToken);
                _messageStringBuilder.append("] ");
                _messageStringBuilder.append("[password = ");
                _messageStringBuilder.append(_transformedPassword);
                _messageStringBuilder.append("] ");
                logger.debug(_messageStringBuilder.toString());
            }
            connection = _castedModuleObject.acquireConnection(new SalesforceModuleConnectionManager.ConnectionKey(_transformedUsername, _transformedPassword, _transformedSecurityToken));
            if (connection == null) {
                throw new MessagingException(CoreMessages.failedToCreate("getDeletedRange"), event, new RuntimeException("Cannot create connection"));
            } else {
                if (logger.isDebugEnabled()) {
                    StringBuilder _messageStringBuilder = new StringBuilder();
                    _messageStringBuilder.append("Connection has been acquired with ");
                    _messageStringBuilder.append("[id = ");
                    _messageStringBuilder.append(connection.getSessionId());
                    _messageStringBuilder.append("] ");
                    logger.debug(_messageStringBuilder.toString());
                }
            }
            retryCount.getAndIncrement();
            Object resultPayload;
            resultPayload = connection.getDeletedRange(_transformedType, _transformedStartTime, _transformedEndTime);
            TransformerTemplate.OverwitePayloadCallback overwritePayloadCallback = null;
            if (resultPayload == null) {
                overwritePayloadCallback = new TransformerTemplate.OverwitePayloadCallback(NullPayload.getInstance());
            } else {
                overwritePayloadCallback = new TransformerTemplate.OverwitePayloadCallback(resultPayload);
            }
            List<Transformer> transformerList;
            transformerList = new ArrayList<Transformer>();
            transformerList.add(new TransformerTemplate(overwritePayloadCallback));
            event.getMessage().applyTransformers(event, transformerList);
            retryCount.set(0);
            return event;
        } catch (SessionTimedOutException invalidConnection) {
            if (logger.isDebugEnabled()) {
                StringBuilder _messageStringBuilder = new StringBuilder();
                _messageStringBuilder.append("An exception (");
                _messageStringBuilder.append("com.sforce.ws.transport.SoapConnection.SessionTimedOutException");
                _messageStringBuilder.append(") has been thrown while executing ");
                _messageStringBuilder.append("getDeletedRange");
                _messageStringBuilder.append(". Destroying the connection with [id = ");
                _messageStringBuilder.append(connection.getSessionId());
                _messageStringBuilder.append("].");
                logger.debug(_messageStringBuilder.toString());
            }
            try {
                _castedModuleObject.destroyConnection(new SalesforceModuleConnectionManager.ConnectionKey(_transformedUsername, _transformedPassword, _transformedSecurityToken), connection);
                connection = null;
            } catch (Exception e) {
                logger.error(e.getMessage(), e);
            }
            if (retryCount.get()<= retryMax) {
                if (logger.isDebugEnabled()) {
                    StringBuilder _messageStringBuilder = new StringBuilder();
                    _messageStringBuilder.append("Forcing a retry [time=");
                    _messageStringBuilder.append(retryCount);
                    _messageStringBuilder.append(" out of  ");
                    _messageStringBuilder.append(retryMax);
                    _messageStringBuilder.append("].");
                    logger.debug(_messageStringBuilder.toString());
                }
                return process(event);
            }
            throw new MessagingException(CoreMessages.failedToInvoke("getDeletedRange"), event, invalidConnection);
        } catch (Exception e) {
            throw new MessagingException(CoreMessages.failedToInvoke("getDeletedRange"), event, e);
        } finally {
            try {
                if (connection!= null) {
                    if (logger.isDebugEnabled()) {
                        StringBuilder _messageStringBuilder = new StringBuilder();
                        _messageStringBuilder.append("Releasing the connection back into the pool [id=");
                        _messageStringBuilder.append(connection.getSessionId());
                        _messageStringBuilder.append("].");
                        logger.debug(_messageStringBuilder.toString());
                    }
                    _castedModuleObject.releaseConnection(new SalesforceModuleConnectionManager.ConnectionKey(_transformedUsername, _transformedPassword, _transformedSecurityToken), connection);
                }
            } catch (Exception e) {
                throw new MessagingException(CoreMessages.failedToInvoke("getDeletedRange"), event, e);
            }
        }
    }

}
