/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you 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.apache.synapse.mediators.bean.enterprise;

import org.apache.synapse.ManagedLifecycle;
import org.apache.synapse.MessageContext;
import org.apache.synapse.SynapseException;
import org.apache.synapse.commons.beanstalk.enterprise.EnterpriseBeanstalk;
import org.apache.synapse.commons.beanstalk.enterprise.EnterpriseBeanstalkConstants;
import org.apache.synapse.commons.beanstalk.enterprise.EnterpriseBeanstalkManager;
import org.apache.synapse.core.SynapseEnvironment;
import org.apache.synapse.mediators.AbstractMediator;
import org.apache.synapse.mediators.Value;
import org.apache.synapse.mediators.bean.BeanUtils;
import org.apache.synapse.mediators.bean.Target;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

/**
 * EJB mediator calls an external Enterprise JavaBean(EJB) and stores the result in the
 * message content or in a message context property.
 * Currently, this mediator supports Stateless Session Beans and Stateful Session Beans.
 */
public class EJBMediator extends AbstractMediator implements ManagedLifecycle {

    /**
     * Name of the beanstalk to be used for this invocation.
     */
    private String beanstalkName;

    /**
     * Fully qualified name of the remote interface of the EJB.
     */
    private String className;

    /**
     * Session id of the stateful session bean, null for stateless session beans.
     */
    private Value beanId;

    /**
     * Target to store the result of the EJB method call.
     */
    private Target target;

    /**
     * JNDI name of the bean. Could be null if this bean is already cached in the beanstalk.
     */
    private String jndiName;

    /**
     * Whether or not this bean is removed from the beanstalk after the method invocation.
     */
    private boolean remove;

    /**
     * Argument list for the remote method invocation.
     */
    private List<Value> argumentList = new ArrayList<Value>();

    /**
     * Beanstalk retrieved from the Synapse environment.
     */
    private volatile EnterpriseBeanstalk beanstalk;

    /**
     * Resolved method. This is resolved by looking at the method name and the argument count.
     */
    private volatile Method method;

    /**
     *
     * @param se SynapseEnvironment to be used for initialization
     */
    public void init(SynapseEnvironment se) {

        EnterpriseBeanstalkManager beanstalkManager =
                (EnterpriseBeanstalkManager) se.getServerContextInformation().getProperty(
                        EnterpriseBeanstalkConstants.BEANSTALK_MANAGER_PROP_NAME);

        if (beanstalkManager == null) {
            throw new SynapseException("Initialization failed. BeanstalkManager not found.");
        }

        beanstalk = beanstalkManager.getBeanstalk(beanstalkName);

        if (beanstalk == null) {
            throw new SynapseException("Initialization failed. '" + beanstalkName +
                    "' beanstalk not found.");
        }
    }

    /**
     * Calls an external EJB according to the supplied semantics and attaches the result into the
     * message/message context.
     * @param synCtx The current message for mediation
     * @return true If mediation should continue
     */
    public boolean mediate(MessageContext synCtx) {

        Object ejb = beanstalk.getEnterpriseBean(
                                    className,
                                    beanId == null ? null : beanId.evaluateValue(synCtx),
                                    jndiName);

        if (ejb == null) {
            handleException("EJB was not found. class: " + className + ", bean id: " + beanId +
                    ", jndi name: " + jndiName + ".", synCtx);
        }

        Object result = null;
        try {
            result = BeanUtils.invokeInstanceMethod(ejb, method, buildArguments(synCtx));
        } catch (SynapseException e) {
            handleException("Failed to invoke method: " + method + " on EJB object of " +
                    "type: " + className, e, synCtx);
        }

        if (target != null) {
            target.insert(synCtx, result);
        }

        if (remove) {
            beanstalk.removeEnterpriseBean(className,
                                beanId == null ? null : beanId.evaluateValue(synCtx));
        }

        return true;
    }

    public void destroy() {
    }

    private Object[] buildArguments(MessageContext synCtx) {
        Object[] args = new Object[argumentList.size()];
        for (int i = 0; i < args.length; ++i) {
            args[i] = argumentList.get(i).evaluateObjectValue(synCtx);
        }
        return args;
    }

    public String getBeanstalkName() {
        return beanstalkName;
    }

    public void setBeanstalkName(String beanstalkName) {
        this.beanstalkName = beanstalkName;
    }

    public String getClassName() {
        return className;
    }

    public void setClassName(String className) {
        this.className = className;
    }

    public Value getBeanId() {
        return beanId;
    }

    public void setBeanId(Value beanId) {
        this.beanId = beanId;
    }

    public Method getMethod() {
        return method;
    }

    public void setMethod(Method method) {
        this.method = method;
    }

    public Target getTarget() {
        return target;
    }

    public void setTarget(Target target) {
        this.target = target;
    }

    public String getJndiName() {
        return jndiName;
    }

    public void setJndiName(String jndiName) {
        this.jndiName = jndiName;
    }

    public boolean isRemove() {
        return remove;
    }

    public void setRemove(boolean remove) {
        this.remove = remove;
    }

    public List<Value> getArgumentList() {
        return argumentList;
    }

    public void addArgument(Value argument) {
        argumentList.add(argument);
    }

    @Override
    public boolean isContentAltering() {
        return true;
    }

}
