/**
 * 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.activemq.ra;

import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import jakarta.jms.Queue;
import jakarta.jms.Session;
import jakarta.jms.Topic;
import jakarta.resource.ResourceException;
import jakarta.resource.spi.InvalidPropertyException;
import jakarta.resource.spi.ResourceAdapter;

import org.apache.activemq.RedeliveryPolicy;
import org.apache.activemq.command.ActiveMQDestination;
import org.apache.activemq.command.ActiveMQQueue;
import org.apache.activemq.command.ActiveMQTopic;
import org.apache.activemq.selector.SelectorParser;

/**
 * Configures the inbound JMS consumer specification using ActiveMQ
 * 
 * @org.apache.xbean.XBean element="activationSpec"
 *  $Date: 2007-08-11 17:29:21 -0400 (Sat, 11 Aug
 *          2007) $
 */
public class ActiveMQActivationSpec implements MessageActivationSpec, Serializable {

    /** Auto-acknowledge constant for <code>acknowledgeMode</code> property * */
    public static final String AUTO_ACKNOWLEDGE_MODE = "Auto-acknowledge";
    /** Dups-ok-acknowledge constant for <code>acknowledgeMode</code> property * */
    public static final String DUPS_OK_ACKNOWLEDGE_MODE = "Dups-ok-acknowledge";
    /** Durable constant for <code>subscriptionDurability</code> property * */
    public static final String DURABLE_SUBSCRIPTION = "Durable";
    /** NonDurable constant for <code>subscriptionDurability</code> property * */
    public static final String NON_DURABLE_SUBSCRIPTION = "NonDurable";
    public static final int INVALID_ACKNOWLEDGE_MODE = -1;

    private static final long serialVersionUID = -7153087544100459975L;

    private transient MessageResourceAdapter resourceAdapter;
    private String destinationType;
    private String messageSelector;
    private String destination;
    private String acknowledgeMode = AUTO_ACKNOWLEDGE_MODE;
    private String userName;
    private String password;
    private String clientId;
    private String subscriptionName;
    private String subscriptionDurability = NON_DURABLE_SUBSCRIPTION;
    private String noLocal = "false";
    private String useRAManagedTransaction = "false";
    private String maxSessions = "10";
    private String maxMessagesPerSessions = "10";
    private String enableBatch = "false";
    private String maxMessagesPerBatch = "10";
    private String trustStore;
    private String trustStorePassword;
    private String keyStore;
    private String keyStorePassword;
    private String keyStoreKeyPassword;
    private RedeliveryPolicy redeliveryPolicy;
    private boolean useJndi;

    /**
     * @see javax.resource.spi.ActivationSpec#validate()
     */
    public void validate() throws InvalidPropertyException {
        List<String> errorMessages = new ArrayList<String>();
        List<PropertyDescriptor> propsNotSet = new ArrayList<PropertyDescriptor>();
        try {
            if (!isValidDestination(errorMessages)) {
                propsNotSet.add(new PropertyDescriptor("destination", ActiveMQActivationSpec.class));
            }
            if (!isValidDestinationType(errorMessages)) {
                propsNotSet.add(new PropertyDescriptor("destinationType", ActiveMQActivationSpec.class));
            }
            if (!isValidAcknowledgeMode(errorMessages)) {
                propsNotSet.add(new PropertyDescriptor("acknowledgeMode", ActiveMQActivationSpec.class));
            }
            if (!isValidSubscriptionDurability(errorMessages)) {
                propsNotSet.add(new PropertyDescriptor("subscriptionDurability", ActiveMQActivationSpec.class));
            }
            if (!isValidClientId(errorMessages)) {
                propsNotSet.add(new PropertyDescriptor("clientId", ActiveMQActivationSpec.class));
            }
            if (!isValidSubscriptionName(errorMessages)) {
                propsNotSet.add(new PropertyDescriptor("subscriptionName", ActiveMQActivationSpec.class));
            }
            if (!isValidMaxMessagesPerSessions(errorMessages)) {
                propsNotSet.add(new PropertyDescriptor("maxMessagesPerSessions", ActiveMQActivationSpec.class));
            }
            if (!isValidMaxSessions(errorMessages)) {
                propsNotSet.add(new PropertyDescriptor("maxSessions", ActiveMQActivationSpec.class));
            }
            if (!isValidMessageSelector(errorMessages)) {
                propsNotSet.add(new PropertyDescriptor("messageSelector", ActiveMQActivationSpec.class));
            }
            if (!isValidNoLocal(errorMessages)) {
                propsNotSet.add(new PropertyDescriptor("noLocal", ActiveMQActivationSpec.class));
            }
            if (!isValidUseRAManagedTransaction(errorMessages)) {
                propsNotSet.add(new PropertyDescriptor("useRAManagedTransaction", ActiveMQActivationSpec.class));
            }
            if (!isValidEnableBatch(errorMessages)) {
                propsNotSet.add(new PropertyDescriptor("enableBatch", ActiveMQActivationSpec.class));
            }
            if (!isValidMaxMessagesPerBatch(errorMessages)) {
                propsNotSet.add(new PropertyDescriptor("maxMessagesPerBatch", ActiveMQActivationSpec.class));
            }

        } catch (IntrospectionException e) {
            e.printStackTrace();
        }

        if (propsNotSet.size() > 0) {
            StringBuffer b = new StringBuffer();
            b.append("Invalid settings:");
            for (Iterator<String> iter = errorMessages.iterator(); iter.hasNext();) {
                b.append(" ");
                b.append(iter.next());
            }
            InvalidPropertyException e = new InvalidPropertyException(b.toString());
            final PropertyDescriptor[] descriptors = propsNotSet.toArray(new PropertyDescriptor[propsNotSet.size()]);
            e.setInvalidPropertyDescriptors(descriptors);
            throw e;
        }
    }

    public boolean isValidUseRAManagedTransaction(List<String> errorMessages) {
        boolean valid = isValidBooleanString(useRAManagedTransaction); 
        if(!valid) {
            errorMessages.add("useRAManagedTransaction must be set to: true or false.");
        }
        return valid; 
    }

    public boolean isValidNoLocal(List<String> errorMessages) {
        boolean valid = isValidBooleanString(noLocal); 
        if(!valid) {
            errorMessages.add("noLocal must be set to: true or false.");
        }
        return valid;
    }

    public boolean isValidMessageSelector(List<String> errorMessages) {
        try {
            if (!isEmpty(messageSelector)) {
                SelectorParser.parse(messageSelector);
            }
            return true;
        } catch (Throwable e) {
            errorMessages.add("messageSelector not set to valid message selector: " + e);
            return false;
        }
    }

    public boolean isValidMaxSessions(List<String> errorMessages) {
        try {
            if (Integer.parseInt(maxSessions) > 0) {
                return true;
            }
        } catch (NumberFormatException e) {
            //
        }
        errorMessages.add("maxSessions must be set to number > 0");
        return false;
    }

    public boolean isValidMaxMessagesPerSessions(List<String> errorMessages) {
        try {
            if (Integer.parseInt(maxMessagesPerSessions) > 0) {
                return true;
            }
        } catch (NumberFormatException e) {
            //
        }
        errorMessages.add("maxMessagesPerSessions must be set to number > 0");
        return false;
    }

    public boolean isValidMaxMessagesPerBatch(List<String> errorMessages) {
        try {
            if (Integer.parseInt(maxMessagesPerBatch) > 0) {
                return true;
            }
        } catch (NumberFormatException e) {
            //
        }
        errorMessages.add("maxMessagesPerBatch must be set to number > 0");
        return false;
    }

    public boolean isValidEnableBatch(List<String> errorMessages) {
        boolean valid = isValidBooleanString(enableBatch);
        if(!valid) {
            errorMessages.add("enableBatch must be set to: true or false");
        }
        return valid;
    }

    public ResourceAdapter getResourceAdapter() {
        return resourceAdapter;
    }

    /**
     * @see javax.resource.spi.ResourceAdapterAssociation#setResourceAdapter(javax.resource.spi.ResourceAdapter)
     */
    public void setResourceAdapter(ResourceAdapter resourceAdapter) throws ResourceException {
        // spec section 5.3.3
        if (this.resourceAdapter != null) {
            throw new ResourceException("ResourceAdapter already set");
        }
        if (!(resourceAdapter instanceof MessageResourceAdapter)) {
            throw new ResourceException("ResourceAdapter is not of type: " + MessageResourceAdapter.class.getName());
        }
        this.resourceAdapter = (MessageResourceAdapter)resourceAdapter;
    }

    // ///////////////////////////////////////////////////////////////////////
    //
    // Java Bean getters and setters for this ActivationSpec class.
    //
    // ///////////////////////////////////////////////////////////////////////
    public String getDestinationType() {
        if (!isEmpty(destinationType)) {
            return destinationType;
        }
        return null;
    }

    /**
     * @param destinationType The destinationType to set.
     */
    public void setDestinationType(String destinationType) {
        this.destinationType = destinationType;
    }

    public String getPassword() {
        if (!isEmpty(password)) {
            return password;
        }
        return null;
    }

    /**
     * 
     */
    public void setPassword(String password) {
        this.password = password;
    }

    public String getUserName() {
        if (!isEmpty(userName)) {
            return userName;
        }
        return null;
    }

    /**
     * 
     */
    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getMessageSelector() {
        if (!isEmpty(messageSelector)) {
            return messageSelector;
        }
        return null;
    }

    /**
     * @param messageSelector The messageSelector to set.
     */
    public void setMessageSelector(String messageSelector) {
        this.messageSelector = messageSelector;
    }

    public String getNoLocal() {
        return noLocal;
    }

    /**
     * @param noLocal The noLocal to set.
     */
    public void setNoLocal(String noLocal) {
        if (noLocal != null) {
            this.noLocal = noLocal;
        }
    }

    public String getAcknowledgeMode() {
        if (!isEmpty(acknowledgeMode)) {
            return acknowledgeMode;
        }
        return null;
    }

    /**
     * 
     */
    public void setAcknowledgeMode(String acknowledgeMode) {
        this.acknowledgeMode = acknowledgeMode;
    }

    public String getClientId() {
        if (!isEmpty(clientId)) {
            return clientId;
        }
        return null;
    }

    /**
     * 
     */
    public void setClientId(String clientId) {
        this.clientId = clientId;
    }

    public String getDestination() {
        if (!isEmpty(destination)) {
            return destination;
        }
        return null;
    }

    /**
     * 
     */
    public void setDestination(String destination) {
        this.destination = destination;
    }

    public String getSubscriptionDurability() {
        if (!isEmpty(subscriptionDurability)) {
            return subscriptionDurability;
        }
        return null;
    }

    /**
     * 
     */
    public void setSubscriptionDurability(String subscriptionDurability) {
        this.subscriptionDurability = subscriptionDurability;
    }

    public String getSubscriptionName() {
        if (!isEmpty(subscriptionName)) {
            return subscriptionName;
        }
        return null;
    }

    /**
     * 
     */
    public void setSubscriptionName(String subscriptionName) {
        this.subscriptionName = subscriptionName;
    }

    public boolean isValidSubscriptionName(List<String> errorMessages) {
        if (!isDurableSubscription() ? true : subscriptionName != null && subscriptionName.trim().length() > 0) {
            return true;
        }
        errorMessages.add("subscriptionName must be set since durable subscription was requested.");
        return false;
    }

    public boolean isValidClientId(List<String> errorMessages) {
        if (!isDurableSubscription() ? true : clientId != null && clientId.trim().length() > 0) {
            return true;
        }
        errorMessages.add("clientId must be set since durable subscription was requested.");
        return false;
    }

    public boolean isDurableSubscription() {
        return DURABLE_SUBSCRIPTION.equals(subscriptionDurability);
    }

    public boolean isValidSubscriptionDurability(List<String> errorMessages) {
        // subscriptionDurability only applies to Topics
        if (DURABLE_SUBSCRIPTION.equals(subscriptionDurability) && getDestinationType() != null && !Topic.class.getName().equals(getDestinationType())) {
            errorMessages.add("subscriptionDurability cannot be set to: " + DURABLE_SUBSCRIPTION + " when destinationType is set to " + Queue.class.getName()
                              + " as it is only valid when destinationType is set to " + Topic.class.getName() + ".");
            return false;
        }
        if (NON_DURABLE_SUBSCRIPTION.equals(subscriptionDurability) || DURABLE_SUBSCRIPTION.equals(subscriptionDurability)) {
            return true;
        }
        errorMessages.add("subscriptionDurability must be set to: " + NON_DURABLE_SUBSCRIPTION + " or " + DURABLE_SUBSCRIPTION + ".");
        return false;
    }

    public boolean isValidAcknowledgeMode(List<String> errorMessages) {
        if (AUTO_ACKNOWLEDGE_MODE.equals(acknowledgeMode) || DUPS_OK_ACKNOWLEDGE_MODE.equals(acknowledgeMode)) {
            return true;
        }
        errorMessages.add("acknowledgeMode must be set to: " + AUTO_ACKNOWLEDGE_MODE + " or " + DUPS_OK_ACKNOWLEDGE_MODE + ".");
        return false;
    }

    public boolean isValidDestinationType(List<String> errorMessages) {
        if (Queue.class.getName().equals(destinationType) || Topic.class.getName().equals(destinationType)) {
            return true;
        }
        errorMessages.add("destinationType must be set to: " + Queue.class.getName() + " or " + Topic.class.getName() + ".");
        return false;
    }

    public boolean isValidDestination(List<String> errorMessages) {
        if (!(destination == null || destination.equals(""))) {
            return true;
        }
        errorMessages.add("destination is a required field and must be set to the destination name.");
        return false;
    }

    public boolean isEmpty(String value) {
        return value == null || "".equals(value.trim());
    }

    /**
     * 
     */
    @Override
    public String toString() {
        return "ActiveMQActivationSpec{" + "acknowledgeMode='" + acknowledgeMode + "'" + ", destinationType='" + destinationType + "'" + ", messageSelector='" + messageSelector + "'"
               + ", destination='" + destination + "'" + ", clientId='" + clientId + "'" + ", subscriptionName='" + subscriptionName + "'" + ", subscriptionDurability='" + subscriptionDurability
               + "'" + ", useJndi='"+ useJndi + "'" +"}";
    }

    public int getAcknowledgeModeForSession() {
        if (AUTO_ACKNOWLEDGE_MODE.equals(acknowledgeMode)) {
            return Session.AUTO_ACKNOWLEDGE;
        } else if (DUPS_OK_ACKNOWLEDGE_MODE.equals(acknowledgeMode)) {
            return Session.DUPS_OK_ACKNOWLEDGE;
        } else {
            return INVALID_ACKNOWLEDGE_MODE;
        }
    }

    /**
     * A helper method mostly for use in Dependency Injection containers which
     * allows you to customize the destination and destinationType properties
     * from a single ActiveMQDestination POJO
     */
    public void setActiveMQDestination(ActiveMQDestination destination) {
        setDestination(destination.getPhysicalName());
        if (destination instanceof Queue) {
            setDestinationType(Queue.class.getName());
        } else {
            setDestinationType(Topic.class.getName());
        }
    }

    /**
     * 
     */
    public ActiveMQDestination createDestination() {
        if (isEmpty(destinationType) || isEmpty(destination)) {
            return null;
        }

        ActiveMQDestination dest = null;
        if (Queue.class.getName().equals(destinationType)) {
            dest = new ActiveMQQueue(destination);
        } else if (Topic.class.getName().equals(destinationType)) {
            dest = new ActiveMQTopic(destination);
        } else {
            assert false : "Execution should never reach here";
        }
        return dest;
    }

    public String getMaxMessagesPerSessions() {
        return maxMessagesPerSessions;
    }

    /**
     * 
     */
    public void setMaxMessagesPerSessions(String maxMessagesPerSessions) {
        if (maxMessagesPerSessions != null) {
            this.maxMessagesPerSessions = maxMessagesPerSessions;
        }
    }

    public String getMaxSessions() {
        return maxSessions;
    }

    /**
     * 
     */
    public void setMaxSessions(String maxSessions) {
        if (maxSessions != null) {
            this.maxSessions = maxSessions;
        }
    }

    public String getUseRAManagedTransaction() {
        return useRAManagedTransaction;
    }

    /**
     * 
     */
    public void setUseRAManagedTransaction(String useRAManagedTransaction) {
        if (useRAManagedTransaction != null) {
            this.useRAManagedTransaction = useRAManagedTransaction;
        }
    }

    public int getMaxMessagesPerSessionsIntValue() {
        return Integer.parseInt(maxMessagesPerSessions);
    }

    public int getMaxSessionsIntValue() {
        return Integer.parseInt(maxSessions);
    }

    public boolean isUseRAManagedTransactionEnabled() {
        return Boolean.valueOf(useRAManagedTransaction);
    }

    public boolean getNoLocalBooleanValue() {
        return Boolean.valueOf(noLocal);
    }

    public String getEnableBatch() {
        return enableBatch;
    }

    /**
     * 
     */
    public void setEnableBatch(String enableBatch) {
        if (enableBatch != null) {
            this.enableBatch = enableBatch;
        }
    }

    public boolean getEnableBatchBooleanValue() {
        return Boolean.valueOf(enableBatch);
    }

    public int getMaxMessagesPerBatchIntValue() {
        return Integer.parseInt(maxMessagesPerBatch);
    }

    public String getMaxMessagesPerBatch() {
        return maxMessagesPerBatch;
    }

    /**
     * 
     */
    public void setMaxMessagesPerBatch(String maxMessagesPerBatch) {
        if (maxMessagesPerBatch != null) {
            this.maxMessagesPerBatch = maxMessagesPerBatch;
        }
    }

    public double getBackOffMultiplier() {
        if (redeliveryPolicy == null) {
            return 0;
        }
        return redeliveryPolicy.getBackOffMultiplier();
    }

    public long getInitialRedeliveryDelay() {
        if (redeliveryPolicy == null) {
            return 0;
        }
        return redeliveryPolicy.getInitialRedeliveryDelay();
    }

    public int getMaximumRedeliveries() {
        if (redeliveryPolicy == null) {
            return 0;
        }
        return redeliveryPolicy.getMaximumRedeliveries();
    }

    public boolean isUseExponentialBackOff() {
        if (redeliveryPolicy == null) {
            return false;
        }
        return redeliveryPolicy.isUseExponentialBackOff();
    }

    /**
     * 
     */
    public void setBackOffMultiplier(double backOffMultiplier) {
        lazyCreateRedeliveryPolicy().setBackOffMultiplier(backOffMultiplier);
    }
    
    public long getMaximumRedeliveryDelay() {
        if (redeliveryPolicy == null) {
            return 0;
        }
        return redeliveryPolicy.getMaximumRedeliveryDelay();
    }
    
    public void setMaximumRedeliveryDelay(long maximumRedeliveryDelay) {
        lazyCreateRedeliveryPolicy().setMaximumRedeliveryDelay(maximumRedeliveryDelay);
    }

    /**
     * 
     */
    public void setInitialRedeliveryDelay(long initialRedeliveryDelay) {
        lazyCreateRedeliveryPolicy().setInitialRedeliveryDelay(initialRedeliveryDelay);
        redeliveryPolicy.setRedeliveryDelay(initialRedeliveryDelay);
    }

    /**
     * 
     */
    public void setMaximumRedeliveries(int maximumRedeliveries) {
        lazyCreateRedeliveryPolicy().setMaximumRedeliveries(maximumRedeliveries);
    }

    /**
     * 
     */
    public void setUseExponentialBackOff(boolean useExponentialBackOff) {
        lazyCreateRedeliveryPolicy().setUseExponentialBackOff(useExponentialBackOff);
    }

    // don't use getter to avoid causing introspection errors in containers
    public RedeliveryPolicy redeliveryPolicy() {
        return redeliveryPolicy;
    }

    public RedeliveryPolicy lazyCreateRedeliveryPolicy() {
        if (redeliveryPolicy == null) {
            redeliveryPolicy = new RedeliveryPolicy();
        }
        return redeliveryPolicy;
    }
    
    public void setUseJndi(boolean useJndi) {
        this.useJndi = useJndi;
    }
    
    public boolean isUseJndi() {
        return useJndi;
    }

    public String getTrustStore() {
        if (!isEmpty(trustStore)) {
            return trustStore;
        }
        return null;
    }

    public void setTrustStore(String trustStore) {
        this.trustStore = trustStore;
    }

    public String getTrustStorePassword() {
        if (!isEmpty(trustStorePassword)) {
            return trustStorePassword;
        }
        return null;
    }

    public void setTrustStorePassword(String trustStorePassword) {
        this.trustStorePassword = trustStorePassword;
    }

    public String getKeyStore() {
        if (!isEmpty(keyStore)) {
            return keyStore;
        }
        return null;
    }

    public void setKeyStore(String keyStore) {
        this.keyStore = keyStore;
    }

    public String getKeyStorePassword() {
        if (!isEmpty(keyStorePassword)) {
            return keyStorePassword;
        }
        return null;
    }

    public void setKeyStorePassword(String keyStorePassword) {
        this.keyStorePassword = keyStorePassword;
    }

    public String getKeyStoreKeyPassword() {
        if (!isEmpty(keyStoreKeyPassword)) {
            return keyStoreKeyPassword;
        }
        return null;
    }

    public void setKeyStoreKeyPassword(String keyStoreKeyPassword) {
        this.keyStoreKeyPassword = keyStoreKeyPassword;
    }

    private static boolean isValidBooleanString(String booleanString) {
        return ("true".equalsIgnoreCase(booleanString) || ("false".equalsIgnoreCase(booleanString)));
    }
}
