/*
 * 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.jmeter.protocol.jms.sampler;

import java.util.Date;
import java.util.function.Predicate;
import java.util.regex.Pattern;

import javax.jms.DeliveryMode;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;

import org.apache.commons.lang3.StringUtils;
import org.apache.jmeter.samplers.AbstractSampler;
import org.apache.jmeter.samplers.Entry;
import org.apache.jmeter.samplers.SampleResult;
import org.apache.jmeter.util.JMeterUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 *
 * BaseJMSSampler is an abstract class which provides implementation for common
 * properties. Rather than duplicate the code, it's contained in the base class.
 */
public abstract class BaseJMSSampler extends AbstractSampler {

    private static final long serialVersionUID = 241L;

    private static final Logger LOGGER = LoggerFactory.getLogger(BaseJMSSampler.class);

    //++ These are JMX file attribute names and must not be changed
    private static final String JNDI_INITIAL_CONTEXT_FAC = "jms.initial_context_factory"; // $NON-NLS-1$

    private static final String PROVIDER_URL = "jms.provider_url"; // $NON-NLS-1$

    private static final String CONN_FACTORY = "jms.connection_factory"; // $NON-NLS-1$

    // N.B. Cannot change value, as that is used in JMX files
    private static final String DEST = "jms.topic"; // $NON-NLS-1$

    private static final String PRINCIPAL = "jms.security_principle"; // $NON-NLS-1$

    private static final String CREDENTIALS = "jms.security_credentials"; // $NON-NLS-1$

    /*
     * The number of samples to aggregate
     */
    private static final String ITERATIONS = "jms.iterations"; // $NON-NLS-1$

    private static final String USE_AUTH = "jms.authenticate"; // $NON-NLS-1$

    private static final String USE_PROPERTIES_FILE = "jms.jndi_properties"; // $NON-NLS-1$

    /*
     * If true, store the response in the sampleResponse
     * (N.B. do not change the value, as it is used in JMX files)
     */
    private static final String STORE_RESPONSE = "jms.read_response"; // $NON-NLS-1$

    // Is Destination setup static? else dynamic
    private static final String DESTINATION_STATIC = "jms.destination_static"; // $NON-NLS-1$
    private static final boolean DESTINATION_STATIC_DEFAULT = true; // default to maintain compatibility

    /** Property name for regex of error codes which force reconnection **/
    private static final String ERROR_RECONNECT_ON_CODES = "jms_error_reconnect_on_codes"; // $NON-NLS-1$
    private transient Predicate<String> isReconnectErrorCode = e -> false;

    //-- End of JMX file attribute names

    // See BUG 45460. We need to keep the resource in order to interpret existing files
    private static final String REQUIRED = JMeterUtils.getResString("jms_auth_required"); // $NON-NLS-1$

    public BaseJMSSampler() {
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public SampleResult sample(Entry e) {
        return this.sample();
    }

    public abstract SampleResult sample();

    // ------------- get/set properties ----------------------//
    /**
     * set the initial context factory
     *
     * @param icf the initial context factory
     */
    public void setJNDIIntialContextFactory(String icf) {
        setProperty(JNDI_INITIAL_CONTEXT_FAC, icf);
    }

    /**
     * method returns the initial context factory for jndi initial context
     * lookup.
     *
     * @return the initial context factory
     */
    public String getJNDIInitialContextFactory() {
        return getPropertyAsString(JNDI_INITIAL_CONTEXT_FAC);
    }

    /**
     * set the provider user for jndi
     *
     * @param url the provider URL
     */
    public void setProviderUrl(String url) {
        setProperty(PROVIDER_URL, url);
    }

    /**
     * method returns the provider url for jndi to connect to
     *
     * @return the provider URL
     */
    public String getProviderUrl() {
        return getPropertyAsString(PROVIDER_URL);
    }

    /**
     * set the connection factory for
     *
     * @param factory the connection factory
     */
    public void setConnectionFactory(String factory) {
        setProperty(CONN_FACTORY, factory);
    }

    /**
     * return the connection factory parameter used to lookup the connection
     * factory from the JMS server
     *
     * @return the connection factory
     */
    public String getConnectionFactory() {
        return getPropertyAsString(CONN_FACTORY);
    }

    /**
     * set the destination (topic or queue name)
     *
     * @param dest the destination
     */
    public void setDestination(String dest) {
        setProperty(DEST, dest);
    }

    /**
     * return the destination (topic or queue name)
     *
     * @return the destination
     */
    public String getDestination() {
        return getPropertyAsString(DEST);
    }

    /**
     * set the username to login into the jms server if needed
     *
     * @param user the name of the user
     */
    public void setUsername(String user) {
        setProperty(PRINCIPAL, user);
    }

    /**
     * return the username used to login to the jms server
     *
     * @return the username used to login to the jms server
     */
    public String getUsername() {
        return getPropertyAsString(PRINCIPAL);
    }

    /**
     * Set the password to login to the jms server
     *
     * @param pwd the password to use for login on the jms server
     */
    public void setPassword(String pwd) {
        setProperty(CREDENTIALS, pwd);
    }

    /**
     * return the password used to login to the jms server
     *
     * @return the password used to login to the jms server
     */
    public String getPassword() {
        return getPropertyAsString(CREDENTIALS);
    }

    /**
     * set the number of iterations the sampler should aggregate
     *
     * @param count the number of iterations
     */
    public void setIterations(String count) {
        setProperty(ITERATIONS, count);
    }

    /**
     * get the number of samples to aggregate
     *
     * @return String containing the number of samples to aggregate
     */
    public String getIterations() {
        return getPropertyAsString(ITERATIONS);
    }

    /**
     * get the number of samples to aggregate
     *
     * @return int containing the number of samples to aggregate
     */
    public int getIterationCount() {
        return getPropertyAsInt(ITERATIONS);
    }

    /**
     * Set whether authentication is required for JNDI
     *
     * @param useAuth flag whether to use authentication
     */
    public void setUseAuth(boolean useAuth) {
        setProperty(USE_AUTH, useAuth);
    }

    /**
     * return whether jndi requires authentication
     *
     * @return whether jndi requires authentication
     */
    public boolean isUseAuth() {
        final String useAuth = getPropertyAsString(USE_AUTH);
        return useAuth.equalsIgnoreCase("true") || useAuth.equals(REQUIRED); // $NON-NLS-1$
    }

    /**
     * set whether the sampler should store the response or not
     *
     * @param read whether the sampler should store the response or not
     */
    public void setReadResponse(String read) {
        setProperty(STORE_RESPONSE, read);
    }

    /**
     * return whether the sampler should store the response
     *
     * @return whether the sampler should store the response
     */
    public String getReadResponse() {
        return getPropertyAsString(STORE_RESPONSE);
    }

    /**
     * return whether the sampler should store the response
     *
     * @return boolean: whether the sampler should read the response
     */
    public boolean getReadResponseAsBoolean() {
        return getPropertyAsBoolean(STORE_RESPONSE);
    }

    /**
     * if the sampler should use jndi.properties file, call the method with the string "true"
     *
     * @param properties flag whether to use <em>jndi.properties</em> file
     */
    public void setUseJNDIProperties(String properties) {
        setProperty(USE_PROPERTIES_FILE, properties);
    }

    /**
     * return whether the sampler should use properties file instead of UI
     * parameters.
     *
     * @return the string "true" when the sampler should use properties file
     *         instead of UI parameters, the string "false" otherwise.
     */
    public String getUseJNDIProperties() {
        return getPropertyAsString(USE_PROPERTIES_FILE);
    }

    /**
     * return the properties as boolean true/false.
     *
     * @return whether the sampler should use properties file instead of UI parameters.
     */
    public boolean getUseJNDIPropertiesAsBoolean() {
        return getPropertyAsBoolean(USE_PROPERTIES_FILE);
    }

    /**
     * if the sampler should use a static destination, call the method with true
     *
     * @param isStatic flag whether the destination is a static destination
     */
    public void setDestinationStatic(boolean isStatic) {
        setProperty(DESTINATION_STATIC, isStatic, DESTINATION_STATIC_DEFAULT);
    }

    /**
     * return whether the sampler should use a static destination.
     *
     * @return  whether the sampler should use a static destination.
     */
    public boolean isDestinationStatic(){
        return getPropertyAsBoolean(DESTINATION_STATIC, DESTINATION_STATIC_DEFAULT);
    }

    /**
     * Returns a String with the JMS Message Header values.
     *
     * @param message JMS Message
     * @return String with message header values.
     */
    public static String getMessageHeaders(Message message) {
        final StringBuilder response = new StringBuilder(256);
        try {
            response.append("JMS Message Header Attributes:");
            response.append("\n   Correlation ID: ");
            response.append(message.getJMSCorrelationID());

            response.append("\n   Delivery Mode: ");
            if (message.getJMSDeliveryMode() == DeliveryMode.PERSISTENT) {
                response.append("PERSISTANT");
            } else {
                response.append("NON-PERSISTANT");
            }

            final Destination destination = message.getJMSDestination();

            response.append("\n   Destination: ");
            response.append(destination == null ? null : destination
                .toString());

            response.append("\n   Expiration: ");
            response.append(new Date(message.getJMSExpiration()));

            response.append("\n   Message ID: ");
            response.append(message.getJMSMessageID());

            response.append("\n   Priority: ");
            response.append(message.getJMSPriority());

            response.append("\n   Redelivered: ");
            response.append(message.getJMSRedelivered());

            final Destination replyTo = message.getJMSReplyTo();
            response.append("\n   Reply to: ");
            response.append(replyTo == null ? null : replyTo.toString());

            response.append("\n   Timestamp: ");
            response.append(new Date(message.getJMSTimestamp()));

            response.append("\n   Type: ");
            response.append(message.getJMSType());

            response.append("\n\n");

        } catch (JMSException e) {
            LOGGER.warn(
                    "Can't extract message headers", e);
        }

        return response.toString();
    }

    public String getReconnectionErrorCodes() {
        return getPropertyAsString(ERROR_RECONNECT_ON_CODES);
    }

    public void setReconnectionErrorCodes(String reconnectionErrorCodes) {
        setProperty(ERROR_RECONNECT_ON_CODES, reconnectionErrorCodes);
    }

    public Predicate<String> getIsReconnectErrorCode() {
        return isReconnectErrorCode;
    }

    /**
     *
     */
    protected void configureIsReconnectErrorCode() {
        String regex = StringUtils.trimToEmpty(getReconnectionErrorCodes());
        if (regex.isEmpty()) {
            isReconnectErrorCode = e -> false;
        } else {
            isReconnectErrorCode = Pattern.compile(regex).asPredicate();
        }
    }
}
