/*
 * 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.artemis.tests.unit.ra;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

import org.apache.activemq.artemis.ra.ActiveMQResourceAdapter;
import org.apache.activemq.artemis.tests.util.ActiveMQTestBase;
import org.junit.Test;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;

/**
 * This test is used to generate the commented out configs in the src/config/ra.xml. If you add a setter to the ActiveMQResourceAdapter
 * this test should fail, if it does paste the new commented out configs into the ra.xml file and in here. don't forget to
 * add a description for each new property added and try and put it in the config some where appropriate.
 */
public class ActiveMQResourceAdapterConfigTest extends ActiveMQTestBase {

   private static String config = "" +
      "<config-property>\n" +
      "         <description>\n" +
      "            The transport type. Multiple connectors can be configured by using a comma separated list,\n" +
      "            i.e. org.apache.activemq.artemis.core.remoting.impl.invm.InVMConnectorFactory,org.apache.activemq.artemis.core.remoting.impl.invm.InVMConnectorFactory.\n" +
      "         </description>\n" +
      "         <config-property-name>ConnectorClassName</config-property-name>\n" +
      "         <config-property-type>java.lang.String</config-property-type>\n" +
      "         <config-property-value>org.apache.activemq.artemis.core.remoting.impl.invm.InVMConnectorFactory</config-property-value>\n" +
      "      </config-property>\n" +
      "      <config-property>\n" +
      "         <description>The transport configuration. These values must be in the form of key=val;key=val;,\n" +
      "            if multiple connectors are used then each set must be separated by a comma i.e. host=host1;port=61616,host=host2;port=61617.\n" +
      "            Each set of params maps to the connector classname specified.\n" +
      "         </description>\n" +
      "         <config-property-name>ConnectionParameters</config-property-name>\n" +
      "         <config-property-type>java.lang.String</config-property-type>\n" +
      "         <config-property-value>server-id=0</config-property-value>\n" +
      "      </config-property>";

   private static String commentedOutConfigs = "" +
      "      <config-property>\n" +
      "        <description>Does we support HA</description>\n" +
      "        <config-property-name>HA</config-property-name>\n" +
      "        <config-property-type>boolean</config-property-type>\n" +
      "        <config-property-value>false</config-property-value>\n" +
      "      </config-property>\n" +
      "      <config-property>\n" +
      "        <description>Use A local Transaction instead of XA?</description>\n" +
      "        <config-property-name>UseLocalTx</config-property-name>\n" +
      "        <config-property-type>boolean</config-property-type>\n" +
      "        <config-property-value>false</config-property-value>\n" +
      "      </config-property>\n" +
      "      <config-property>\n" +
      "        <description>The user name used to login to the JMS server</description>\n" +
      "        <config-property-name>UserName</config-property-name>\n" +
      "        <config-property-type>java.lang.String</config-property-type>\n" +
      "        <config-property-value></config-property-value>\n" +
      "      </config-property>\n" +
      "      <config-property>\n" +
      "        <description>The password used to login to the JMS server</description>\n" +
      "        <config-property-name>Password</config-property-name>\n" +
      "        <config-property-type>java.lang.String</config-property-type>\n" +
      "        <config-property-value></config-property-value>\n" +
      "      </config-property>\n" +
      "      <config-property>\n" +
      "        <description>The jndi params to use to look up the jms resources if local jndi is not to be used</description>\n" +
      "        <config-property-name>JndiParams</config-property-name>\n" +
      "        <config-property-type>java.lang.String</config-property-type>\n" +
      "        <config-property-value>java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory;java.naming.provider.url=jnp://localhost:1199;java.naming.factory.url.pkgs=org.jboss.naming:org.jnp.interfaces</config-property-value>\n" +
      "      </config-property>\n" +
      "      <config-property>\n" +
      "        <description>The jndi entries to use to look up the connection factory</description>\n" +
      "        <config-property-name>Entries</config-property-name>\n" +
      "        <config-property-type>java.lang.String</config-property-type>\n" +
      "        <config-property-value>[\"java://jmsXA\"]</config-property-value>\n" +
      "      </config-property>\n" +
      "      <config-property>\n" +
      "        <description>The discovery group address</description>\n" +
      "        <config-property-name>DiscoveryLocalBindAddress</config-property-name>\n" +
      "        <config-property-type>java.lang.String</config-property-type>\n" +
      "        <config-property-value></config-property-value>\n" +
      "      </config-property>\n" +
      "      <config-property>\n" +
      "        <description>The discovery group local bind address</description>\n" +
      "        <config-property-name>DiscoveryAddress</config-property-name>\n" +
      "        <config-property-type>java.lang.String</config-property-type>\n" +
      "        <config-property-value></config-property-value>\n" +
      "      </config-property>\n" +
      "      <config-property>\n" +
      "        <description>The discovery group port</description>\n" +
      "        <config-property-name>DiscoveryPort</config-property-name>\n" +
      "        <config-property-type>int</config-property-type>\n" +
      "        <config-property-value></config-property-value>\n" +
      "      </config-property>\n" +
      "      <config-property>\n" +
      "        <description>The discovery refresh timeout</description>\n" +
      "        <config-property-name>DiscoveryRefreshTimeout</config-property-name>\n" +
      "        <config-property-type>long</config-property-type>\n" +
      "        <config-property-value></config-property-value>\n" +
      "      </config-property>\n" +
      "      <config-property>\n" +
      "        <description>The discovery initial wait timeout</description>\n" +
      "        <config-property-name>DiscoveryInitialWaitTimeout</config-property-name>\n" +
      "        <config-property-type>long</config-property-type>\n" +
      "        <config-property-value></config-property-value>\n" +
      "      </config-property> \n" +
      "      <config-property>\n" +
      "         <description>The class to use for load balancing connections</description>\n" +
      "         <config-property-name>ConnectionLoadBalancingPolicyClassName</config-property-name>\n" +
      "         <config-property-type>java.lang.String</config-property-type>\n" +
      "         <config-property-value></config-property-value>\n" +
      "      </config-property>\n" +
      "      <config-property>\n" +
      "         <description>number of reconnect attempts for connections after failover occurs</description>\n" +
      "         <config-property-name>ReconnectAttempts</config-property-name>\n" +
      "         <config-property-type>int</config-property-type>\n" +
      "         <config-property-value></config-property-value>\n" +
      "      </config-property>\n" +
      "      <config-property>\n" +
      "        <description>The client failure check period</description>\n" +
      "        <config-property-name>ClientFailureCheckPeriod</config-property-name>\n" +
      "        <config-property-type>long</config-property-type>\n" +
      "        <config-property-value></config-property-value>\n" +
      "      </config-property>\n" +
      "      <config-property>\n" +
      "        <description>The connection TTL</description>\n" +
      "        <config-property-name>ConnectionTTL</config-property-name>\n" +
      "        <config-property-type>long</config-property-type>\n" +
      "        <config-property-value></config-property-value>\n" +
      "      </config-property>\n" +
      "      <config-property>\n" +
      "        <description>The call timeout</description>\n" +
      "        <config-property-name>CallTimeout</config-property-name>\n" +
      "        <config-property-type>long</config-property-type>\n" +
      "        <config-property-value></config-property-value>\n" +
      "      </config-property>\n" +
      "      <config-property>\n" +
      "        <description>The dups ok batch size</description>\n" +
      "        <config-property-name>DupsOKBatchSize</config-property-name>\n" +
      "        <config-property-type>int</config-property-type>\n" +
      "        <config-property-value></config-property-value>\n" +
      "      </config-property>\n" +
      "      <config-property>\n" +
      "        <description>The transaction batch size</description>\n" +
      "        <config-property-name>TransactionBatchSize</config-property-name>\n" +
      "        <config-property-type>int</config-property-type>\n" +
      "        <config-property-value></config-property-value>\n" +
      "      </config-property>\n" +
      "      <config-property>\n" +
      "        <description>The consumer window size</description>\n" +
      "        <config-property-name>ConsumerWindowSize</config-property-name>\n" +
      "        <config-property-type>int</config-property-type>\n" +
      "        <config-property-value></config-property-value>\n" +
      "      </config-property>\n" +
      "      <config-property>\n" +
      "        <description>The consumer max rate</description>\n" +
      "        <config-property-name>ConsumerMaxRate</config-property-name>\n" +
      "        <config-property-type>int</config-property-type>\n" +
      "        <config-property-value></config-property-value>\n" +
      "      </config-property>\n" +
      "      <config-property>\n" +
      "        <description>The confirmation window size</description>\n" +
      "        <config-property-name>ConfirmationWindowSize</config-property-name>\n" +
      "        <config-property-type>int</config-property-type>\n" +
      "        <config-property-value></config-property-value>\n" +
      "      </config-property>\n" +
      "      <config-property>\n" +
      "        <description>The producer max rate</description>\n" +
      "        <config-property-name>ProducerMaxRate</config-property-name>\n" +
      "        <config-property-type>int</config-property-type>\n" +
      "        <config-property-value></config-property-value>\n" +
      "      </config-property>\n" +
      "      <config-property>\n" +
      "        <description>The min large message size</description>\n" +
      "        <config-property-name>MinLargeMessageSize</config-property-name>\n" +
      "        <config-property-type>int</config-property-type>\n" +
      "        <config-property-value></config-property-value>\n" +
      "      </config-property>\n" +
      "      <config-property>\n" +
      "        <description>The block on acknowledge</description>\n" +
      "        <config-property-name>BlockOnAcknowledge</config-property-name>\n" +
      "        <config-property-type>boolean</config-property-type>\n" +
      "        <config-property-value></config-property-value>\n" +
      "      </config-property>\n" +
      "      <config-property>\n" +
      "        <description>The block on non durable send</description>\n" +
      "        <config-property-name>BlockOnNonDurableSend</config-property-name>\n" +
      "        <config-property-type>boolean</config-property-type>\n" +
      "        <config-property-value></config-property-value>\n" +
      "      </config-property>\n" +
      "      <config-property>\n" +
      "        <description>The block on durable send</description>\n" +
      "        <config-property-name>BlockOnDurableSend</config-property-name>\n" +
      "        <config-property-type>boolean</config-property-type>\n" +
      "        <config-property-value></config-property-value>\n" +
      "      </config-property>\n" +
      "      <config-property>\n" +
      "        <description>The auto group</description>\n" +
      "        <config-property-name>AutoGroup</config-property-name>\n" +
      "        <config-property-type>boolean</config-property-type>\n" +
      "        <config-property-value></config-property-value>\n" +
      "      </config-property>\n" +
      "      <config-property>\n" +
      "        <description>The pre acknowledge</description>\n" +
      "        <config-property-name>PreAcknowledge</config-property-name>\n" +
      "        <config-property-type>boolean</config-property-type>\n" +
      "        <config-property-value></config-property-value>\n" +
      "      </config-property>\n" +
      "      <config-property>\n" +
      "        <description>The retry interval</description>\n" +
      "        <config-property-name>RetryInterval</config-property-name>\n" +
      "        <config-property-type>long</config-property-type>\n" +
      "        <config-property-value></config-property-value>\n" +
      "      </config-property>\n" +
      "      <config-property>\n" +
      "        <description>The retry interval multiplier</description>\n" +
      "        <config-property-name>RetryIntervalMultiplier</config-property-name>\n" +
      "        <config-property-type>java.lang.Double</config-property-type>\n" +
      "        <config-property-value></config-property-value>\n" +
      "      </config-property>\n" +
      "      <config-property>\n" +
      "        <description>The client id</description>\n" +
      "        <config-property-name>ClientID</config-property-name>\n" +
      "        <config-property-type>java.lang.String</config-property-type>\n" +
      "        <config-property-value></config-property-value>\n" +
      "      </config-property>\n" +
      "      <config-property>\n" +
      "         <description>use global pools for client</description>\n" +
      "         <config-property-name>UseGlobalPools</config-property-name>\n" +
      "         <config-property-type>boolean</config-property-type>\n" +
      "         <config-property-value></config-property-value>\n" +
      "      </config-property>\n" +
      "      <config-property>\n" +
      "         <description>max number of threads for scheduled threrad pool</description>\n" +
      "         <config-property-name>ScheduledThreadPoolMaxSize</config-property-name>\n" +
      "         <config-property-type>int</config-property-type>\n" +
      "         <config-property-value></config-property-value>\n" +
      "      </config-property>\n" +
      "      <config-property>\n" +
      "         <description>max number of threads in pool</description>\n" +
      "         <config-property-name>ThreadPoolMaxSize</config-property-name>\n" +
      "         <config-property-type>int</config-property-type>\n" +
      "         <config-property-value></config-property-value>\n" +
      "      </config-property>\n" +
      "      <config-property>\n" +
      "         <description>whether to use jndi for looking up destinations etc</description>\n" +
      "         <config-property-name>UseJNDI</config-property-name>\n" +
      "         <config-property-type>boolean</config-property-type>\n" +
      "         <config-property-value></config-property-value>\n" +
      "      </config-property>\n" +
      "      <config-property>\n" +
      "         <description>how long in milliseconds to wait before retry on failed MDB setup</description>\n" +
      "         <config-property-name>SetupInterval</config-property-name>\n" +
      "         <config-property-type>long</config-property-type>\n" +
      "         <config-property-value></config-property-value>\n" +
      "      </config-property>\n" +
      "      <config-property>\n" +
      "         <description>How many attempts should be made when connecting the MDB</description>\n" +
      "         <config-property-name>SetupAttempts</config-property-name>\n" +
      "         <config-property-type>int</config-property-type>\n" +
      "         <config-property-value></config-property-value>\n" +
      "      </config-property>" +
      "      <config-property>\n" +
      "         <description>Add a new managed connection factory</description>\n" +
      "         <config-property-name>ManagedConnectionFactory</config-property-name>\n" +
      "         <config-property-type>org.apache.activemq.artemis.ra.ActiveMQRAManagedConnectionFactory</config-property-type>\n" +
      "         <config-property-value></config-property-value>\n" +
      "      </config-property>" +
      "      <config-property>\n" +
      "         <description>Whether to use password mask, default false</description>\n" +
      "         <config-property-name>UseMaskedPassword</config-property-name>\n" +
      "         <config-property-type>boolean</config-property-type>\n" +
      "         <config-property-value>false</config-property-value>\n" +
      "      </config-property>" +
      "      <config-property>\n" +
      "         <description>The class definition (full qualified name and its properties) used to encrypt the password</description>\n" +
      "         <config-property-name>PasswordCodec</config-property-name>\n" +
      "         <config-property-type>java.lang.String</config-property-type>\n" +
      "         <config-property-value>org.apache.activemq.artemis.utils.DefaultSensitiveStringCodec;key=clusterpassword;algorithm=ssss</config-property-value>\n" +
      "      </config-property>" +
      "      <config-property>" +
      "         <description>Whether the resource adapter must use auto recovery</description>\n" +
      "         <config-property-name>UseAutoRecovery</config-property-name>\n" +
      "         <config-property-type>boolean</config-property-type>\n" +
      "         <config-property-value>true</config-property-value>\n" +
      "      </config-property>\n" +
      "      <config-property>\n" +
      "         <description>The initial size for message packet</description>\n" +
      "         <config-property-name>InitialMessagePacketSize</config-property-name>\n" +
      "         <config-property-type>int</config-property-type>\n" +
      "         <config-property-value>1500</config-property-value>\n" +
      "      </config-property>\n" +
      "      <config-property>\n" +
      "         <description>the Group ID</description>\n" +
      "         <config-property-name>GroupID</config-property-name>\n" +
      "         <config-property-type>java.lang.String</config-property-type>\n" +
      "         <config-property-value></config-property-value>\n" +
      "      </config-property>\n" +
      "      <config-property>\n" +
      "         <description>Whether the resource adapter must failover on the initial connection</description>\n" +
      "         <config-property-name>FailoverOnInitialConnection</config-property-name>\n" +
      "         <config-property-type>boolean</config-property-type>\n" +
      "         <config-property-value>false</config-property-value>\n" +
      "      </config-property>\n" +
      "      <config-property>\n" +
      "         <description>Whether the resource adapter compress large messages and send them as regular when possible</description>" +
      "         <config-property-name>CompressLargeMessage</config-property-name>" +
      "         <config-property-type>boolean</config-property-type>" +
      "         <config-property-value>false</config-property-value>" +
      "      </config-property>\n" +
      "      <config-property>\n" +
      "         <description>The timeout in milliseconds for failover call (or -1 for infinite)</description>\n" +
      "         <config-property-name>CallFailoverTimeout</config-property-name>\n" +
      "         <config-property-type>long</config-property-type>\n" +
      "         <config-property-value>-1</config-property-value>\n" +
      "      </config-property>\n" +
      "      <config-property>\n" +
      "         <description>The maximum interval (in milliseconds) between reconnection attempts</description>\n" +
      "         <config-property-name>MaxRetryInterval</config-property-name>\n" +
      "         <config-property-type>long</config-property-type>\n" +
      "         <config-property-value>2000</config-property-value>\n" +
      "      </config-property>\n" +
      "      <config-property>\n" +
      "         <description>Whether the resource adapter must cache large messages</description>\n" +
      "         <config-property-name>CacheLargeMessagesClient</config-property-name>\n" +
      "         <config-property-type>boolean</config-property-type>\n" +
      "         <config-property-value>false</config-property-value>\n" +
      "      </config-property>\n" +
      "      <config-property>\n" +
      "         <description>The producer window size (in bytes)</description>\n" +
      "         <config-property-name>ProducerWindowSize</config-property-name>\n" +
      "         <config-property-type>int</config-property-type>\n" +
      "         <config-property-value>65536</config-property-value>\n" +
      "      </config-property>\n" +
      "      <config-property>\n" +
      "         <description>The number of attempts for the initial connection</description>\n" +
      "         <config-property-name>InitialConnectAttempts</config-property-name>\n" +
      "         <config-property-type>int</config-property-type>\n" +
      "         <config-property-value>1</config-property-value>\n" +
      "      </config-property>\n" +
      "      <config-property>" +
      "         <description>my-channel</description>" +
      "         <config-property-name>JgroupsChannelName</config-property-name>" +
      "         <config-property-type>java.lang.String</config-property-type>" +
      "         <config-property-value></config-property-value>" +
      "      </config-property>" +
      "      <config-property>" +
      "         <description>my-file</description>" +
      "         <config-property-name>JgroupsFile</config-property-name>" +
      "         <config-property-type>java.lang.String</config-property-type>" +
      "         <config-property-value></config-property-value>" +
      "      </config-property>" +
      "      <config-property>" +
      "         <description>Jgroups Channel Locator Class</description>" +
      "         <config-property-name>JgroupsChannelLocatorClass</config-property-name>" +
      "         <config-property-type>java.lang.String</config-property-type>" +
      "         <config-property-value></config-property-value>" +
      "      </config-property>" +
      "      <config-property>" +
      "         <description>Name to locate a JChannel by JGroups Channel locator class</description>" +
      "         <config-property-name>JgroupsChannelRefName</config-property-name>" +
      "         <config-property-type>java.lang.String</config-property-type>" +
      "         <config-property-value></config-property-value>" +
      "      </config-property>" +
      "      <config-property>" +
      "         <description>ProtocolManagerConfig</description>" +
      "         <config-property-name>ProtocolManagerFactoryStr</config-property-name>" +
      "         <config-property-type>java.lang.String</config-property-type>" +
      "         <config-property-value></config-property-value>" +
      "      </config-property>" +
      "      <config-property>" +
      "         <description>List of package/class names against which matching objects are permitted to be deserilized</description>" +
      "         <config-property-name>DeserializationWhiteList</config-property-name>" +
      "         <config-property-type>java.lang.String</config-property-type>" +
      "         <config-property-value></config-property-value>" +
      "      </config-property>" +
      "      <config-property>" +
      "         <description>List of package/classe names against which matching objects are forbidden to be deserialized</description>" +
      "         <config-property-name>DeserializationBlackList</config-property-name>" +
      "         <config-property-type>java.lang.String</config-property-type>" +
      "         <config-property-value></config-property-value>" +
      "      </config-property>";

   private static String rootConfig = "<root>" + config + commentedOutConfigs + "</root>";

   @Test
   public void testConfiguration() throws Exception {
      Method[] methods = ActiveMQResourceAdapter.class.getMethods();
      Map<String, Method> methodList = new HashMap<>();
      for (Method method : methods) {
         if (method.getName().startsWith("set")) {
            methodList.put(method.getName(), method);
         }
      }
      DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
      DocumentBuilder db = dbf.newDocumentBuilder();
      InputStream io = new ByteArrayInputStream(rootConfig.getBytes());
      Document dom = db.parse(new InputSource(io));

      Element docEle = dom.getDocumentElement();

      NodeList nl = docEle.getElementsByTagName("config-property");

      for (int i = 0; i < nl.getLength(); i++) {
         Element el = (Element) nl.item(i);
         NodeList elementsByTagName = el.getElementsByTagName("config-property-name");
         assertEquals(el.toString(), elementsByTagName.getLength(), 1);
         Node configPropertyNameNode = elementsByTagName.item(0);
         String configPropertyName = configPropertyNameNode.getTextContent();
         System.out.println("configPropertyName = " + configPropertyName);
         Method setter = methodList.remove("set" + configPropertyName);
         assertNotNull("setter " + configPropertyName + " does not exist", setter);
         Class c = lookupType(setter);
         elementsByTagName = el.getElementsByTagName("config-property-type");
         assertEquals("setter " + configPropertyName + " has no type set", elementsByTagName.getLength(), 1);
         Node configPropertyTypeNode = elementsByTagName.item(0);
         String configPropertyTypeName = configPropertyTypeNode.getTextContent();
         assertEquals(configPropertyTypeName, c.getName());
      }
      if (!methodList.isEmpty()) {
         StringBuffer newConfig = new StringBuffer(commentedOutConfigs);
         newConfig.append("\n");
         for (Method method : methodList.values()) {
            newConfig.append("         \"      <config-property>\" + \n");
            newConfig.append("         \"         <description>***add***</description>\" + \n");
            newConfig.append("         \"         <config-property-name>").append(method.getName().substring(3)).append("</config-property-name>\" + \n");
            newConfig.append("         \"         <config-property-type>").append(lookupType(method).getName()).append("</config-property-type>\" + \n");
            newConfig.append("         \"         <config-property-value></config-property-value>\" + \n");
            newConfig.append("         \"      </config-property>\" + \n");
         }
         System.out.println(newConfig);
         fail("methods not shown please see previous and add");
      } else {
         System.out.println(commentedOutConfigs);
      }
   }

   /**
    * @param setter
    * @return
    */
   private Class<?> lookupType(Method setter) {
      Class<?> clzz = setter.getParameterTypes()[0];

      if (clzz == Boolean.class) {
         return Boolean.TYPE;
      } else if (clzz == Long.class) {
         return Long.TYPE;
      } else if (clzz == Integer.class) {
         return Integer.TYPE;
      } else {
         return clzz;
      }
   }
}
