/**
 * 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 ONDITIONS 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.usecases;

import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageProducer;
import javax.jms.Session;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.Vector;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.ActiveMQMessageConsumer;
import org.apache.activemq.broker.BrokerFactory;
import org.apache.activemq.broker.BrokerService;
import org.apache.activemq.broker.region.policy.PolicyEntry;
import org.apache.activemq.broker.region.policy.PolicyMap;
import org.apache.activemq.command.ActiveMQTopic;
import org.apache.activemq.leveldb.LevelDBStore;
import org.apache.activemq.store.kahadb.KahaDBPersistenceAdapter;
import org.apache.activemq.util.ThreadTracker;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static org.junit.Assert.assertTrue;

public class DurableSubProcessConcurrentCommitActivateNoDuplicateTest {

   private static final Logger LOG = LoggerFactory.getLogger(DurableSubProcessConcurrentCommitActivateNoDuplicateTest.class);
   public static final long RUNTIME = 5 * 60 * 1000;

   public static final int SERVER_SLEEP = 500; // max
   public static final int CARGO_SIZE = 600; // max

   public static final int MAX_CLIENTS = 2;
   public static final Random CLIENT_LIFETIME = new Random(30 * 1000, 2 * 60 * 1000);
   public static final Random CLIENT_ONLINE = new Random(30 * 1000, 40 * 1000);
   public static final Random CLIENT_OFFLINE = new Random(1 * 1000, 10 * 1000);

   public static final int CLIENT_OFFLINE_DURING_COMMIT = 2; // random(x) == x

   public static final Persistence PERSISTENT_ADAPTER = Persistence.KAHADB;

   public static final long BROKER_RESTART = -2 * 60 * 1000;

   public static final boolean ALLOW_SUBSCRIPTION_ABANDONMENT = true;
   public static final boolean CHECK_REDELIVERY = true;

   private BrokerService broker;
   private ActiveMQTopic topic;

   private ClientManager clientManager;
   private Server server;
   private HouseKeeper houseKeeper;

   private final ReentrantReadWriteLock processLock = new ReentrantReadWriteLock(true);
   private int restartCount = 0;
   private final AtomicInteger onlineCount = new AtomicInteger(0);
   static final Vector<Throwable> exceptions = new Vector<>();

   // long form of test that found https://issues.apache.org/jira/browse/AMQ-3805
   @Ignore("short version in org.apache.activemq.usecases.DurableSubscriptionOfflineTest.testNoDuplicateOnConcurrentSendTranCommitAndActivate" + " and org.apache.activemq.usecases.DurableSubscriptionOfflineTest.testOrderOnActivateDeactivate")
   @Test
   public void testProcess() {
      try {
         server.start();
         clientManager.start();

         if (ALLOW_SUBSCRIPTION_ABANDONMENT)
            houseKeeper.start();

         if (BROKER_RESTART <= 0)
            Thread.sleep(RUNTIME);
         else {
            long end = System.currentTimeMillis() + RUNTIME;

            while (true) {
               long now = System.currentTimeMillis();
               if (now > end)
                  break;

               now = end - now;
               now = now < BROKER_RESTART ? now : BROKER_RESTART;
               Thread.sleep(now);

               restartBroker();
            }
         }
      } catch (Throwable e) {
         exit("ProcessTest.testProcess failed.", e);
      }

      //allow the clients to unsubscribe before finishing
      clientManager.setEnd(true);
      try {
         Thread.sleep(60 * 1000);
      } catch (InterruptedException e) {
         exit("ProcessTest.testProcess failed.", e);
      }

      server.done = true;

      try {
         server.join(60 * 1000);
      } catch (Exception ignored) {
      }
      processLock.writeLock().lock();
      assertTrue("no exceptions: " + exceptions, exceptions.isEmpty());
      LOG.info("DONE.");
   }

   private void restartBroker() throws Exception {
      LOG.info("Broker restart: waiting for components.");

      processLock.writeLock().lock();
      try {
         destroyBroker();
         startBroker(false);

         restartCount++;
         LOG.info("Broker restarted. count: " + restartCount);
      } finally {
         processLock.writeLock().unlock();
      }
   }

   /**
    * Creates batch of messages in a transaction periodically. The last message
    * in the transaction is always a special message what contains info about
    * the whole transaction.
    * <p>
    * Notifies the clients about the created messages also.
    */
   final class Server extends Thread {

      final String url = "vm://" + DurableSubProcessConcurrentCommitActivateNoDuplicateTest.getName() + "?" + "jms.redeliveryPolicy.maximumRedeliveries=2&jms.redeliveryPolicy.initialRedeliveryDelay=500&" + "jms.producerWindowSize=20971520&jms.prefetchPolicy.all=100&" + "jms.copyMessageOnSend=false&jms.disableTimeStampsByDefault=false&" + "jms.alwaysSyncSend=true&jms.dispatchAsync=false&" + "jms.watchTopicAdvisories=false&" + "waitForStart=200&create=false";
      final ConnectionFactory cf = new ActiveMQConnectionFactory(url);

      final Object sendMutex = new Object();
      final String[] cargos = new String[500];

      int transRover = 0;
      int messageRover = 0;
      public volatile int committingTransaction = -1;
      public boolean done = false;

      public Server() {
         super("Server");
         setPriority(Thread.MIN_PRIORITY);
         setDaemon(true);
      }

      @Override
      public void run() {
         try {
            while (!done) {

               Thread.sleep(1000);

               processLock.readLock().lock();
               try {
                  send();
               } finally {
                  processLock.readLock().unlock();
               }
            }
         } catch (Throwable e) {
            exit("Server.run failed", e);
         }
      }

      public void send() throws JMSException {
         // do not create new clients now
         // ToDo: Test this case later.
         synchronized (sendMutex) {
            int trans = ++transRover;
            boolean relevantTrans = true; //random(2) > 1;
            ClientType clientType = relevantTrans ? ClientType.randomClientType() : null; // sends this types
            //int count = random(500, 700);
            int count = 1000;

            LOG.info("Sending Trans[id=" + trans + ", count=" + count + ", clientType=" + clientType + ", firstID=" + (messageRover + 1) + "]");

            Connection con = cf.createConnection();
            Session sess = con.createSession(true, Session.SESSION_TRANSACTED);
            MessageProducer prod = sess.createProducer(null);

            for (int i = 0; i < count; i++) {
               Message message = sess.createMessage();
               message.setIntProperty("ID", ++messageRover);
               message.setIntProperty("TRANS", trans);
               String type = clientType != null ? clientType.randomMessageType() : ClientType.randomNonRelevantMessageType();
               message.setStringProperty("TYPE", type);

               if (CARGO_SIZE > 0)
                  message.setStringProperty("CARGO", getCargo(random(CARGO_SIZE)));

               prod.send(topic, message);
               clientManager.onServerMessage(message);
            }

            Message message = sess.createMessage();
            message.setIntProperty("ID", ++messageRover);
            message.setIntProperty("TRANS", trans);
            message.setBooleanProperty("COMMIT", true);
            message.setBooleanProperty("RELEVANT", relevantTrans);
            prod.send(topic, message);
            clientManager.onServerMessage(message);

            committingTransaction = trans;
            sess.commit();
            committingTransaction = -1;

            LOG.info("Committed Trans[id=" + trans + ", count=" + count + ", clientType=" + clientType + "], ID=" + messageRover);

            sess.close();
            con.close();
         }
      }

      private String getCargo(int length) {
         if (length == 0)
            return null;

         if (length < cargos.length) {
            String result = cargos[length];
            if (result == null) {
               result = getCargoImpl(length);
               cargos[length] = result;
            }
            return result;
         }
         return getCargoImpl(length);
      }

      private String getCargoImpl(int length) {
         StringBuilder sb = new StringBuilder(length);
         for (int i = length; --i >= 0; ) {
            sb.append('a');
         }
         return sb.toString();
      }
   }

   /**
    * Clients listen on different messages in the topic. The 'TYPE' property
    * helps the client to select the proper messages.
    */
   private enum ClientType {
      A("a", "b", "c"), B("c", "d", "e"), C("d", "e", "f"), D("g", "h");

      public final String[] messageTypes;
      public final HashSet<String> messageTypeSet;
      public final String selector;

      ClientType(String... messageTypes) {
         this.messageTypes = messageTypes;
         messageTypeSet = new HashSet<>(Arrays.asList(messageTypes));

         StringBuilder sb = new StringBuilder("TYPE in (");
         for (int i = 0; i < messageTypes.length; i++) {
            if (i > 0)
               sb.append(", ");
            sb.append('\'').append(messageTypes[i]).append('\'');
         }
         sb.append(')');
         selector = sb.toString();
      }

      public static ClientType randomClientType() {
         return values()[DurableSubProcessConcurrentCommitActivateNoDuplicateTest.random(values().length - 1)];
      }

      public final String randomMessageType() {
         return messageTypes[DurableSubProcessConcurrentCommitActivateNoDuplicateTest.random(messageTypes.length - 1)];
      }

      public static String randomNonRelevantMessageType() {
         return Integer.toString(DurableSubProcessConcurrentCommitActivateNoDuplicateTest.random(20));
      }

      public final boolean isRelevant(String messageType) {
         return messageTypeSet.contains(messageType);
      }

      @Override
      public final String toString() {
         return this.name() /* + '[' + selector + ']' */;
      }
   }

   /**
    * Creates new cliens.
    */
   private final class ClientManager extends Thread {

      private int clientRover = 0;

      private final CopyOnWriteArrayList<Client> clients = new CopyOnWriteArrayList<>();

      private boolean end;

      public ClientManager() {
         super("ClientManager");
         setDaemon(true);
      }

      public synchronized void setEnd(boolean end) {
         this.end = end;

      }

      @Override
      public void run() {
         try {
            while (true) {
               if (clients.size() < MAX_CLIENTS && !end) {
                  processLock.readLock().lock();
                  try {
                     createNewClient();
                  } finally {
                     processLock.readLock().unlock();
                  }
               }

               int size = clients.size();
               //sleepRandom(1000, 4000);
               Thread.sleep(100);
            }
         } catch (Throwable e) {
            exit("ClientManager.run failed.", e);
         }
      }

      private void createNewClient() throws JMSException {
         ClientType type = ClientType.randomClientType();

         Client client;
         synchronized (server.sendMutex) {
            client = new Client(++clientRover, type, CLIENT_LIFETIME, CLIENT_ONLINE, CLIENT_OFFLINE);
            clients.add(client);
         }
         client.start();

         LOG.info(client.toString() + " created. " + this);
      }

      public void removeClient(Client client) {
         clients.remove(client);
      }

      public void onServerMessage(Message message) throws JMSException {
         for (Client client : clients) {
            client.onServerMessage(message);
         }
      }

      @Override
      public String toString() {
         StringBuilder sb = new StringBuilder("ClientManager[count=");
         sb.append(clients.size());
         sb.append(", clients=");
         boolean sep = false;
         for (Client client : clients) {
            if (sep)
               sb.append(", ");
            else
               sep = true;
            sb.append(client.toString());
         }
         sb.append(']');
         return sb.toString();
      }
   }

   /**
    * Consumes massages from a durable subscription. Goes online/offline
    * periodically. Checks the incoming messages against the sent messages of
    * the server.
    */
   private final class Client extends Thread {

      String url = "failover:(tcp://localhost:61656?wireFormat.maxInactivityDuration=0)?" + "jms.watchTopicAdvisories=false&" + "jms.alwaysSyncSend=true&jms.dispatchAsync=true&" + "jms.producerWindowSize=20971520&" + "jms.copyMessageOnSend=false&" + "jms.sendAcksAsync=false&" + "initialReconnectDelay=100&maxReconnectDelay=30000&" + "useExponentialBackOff=true";
      final ConnectionFactory cf = new ActiveMQConnectionFactory(url);

      public static final String SUBSCRIPTION_NAME = "subscription";

      private final int id;
      private final String conClientId;

      private final Random lifetime;
      private final Random online;
      private final Random offline;

      private final ClientType clientType;
      private final String selector;

      private final ConcurrentLinkedQueue<Message> waitingList = new ConcurrentLinkedQueue<>();
      private final HashSet<Integer> processed = CHECK_REDELIVERY ? new HashSet<Integer>(10000) : null;

      private ActiveMQMessageConsumer consumer = null;

      public Client(int id, ClientType clientType, Random lifetime, Random online, Random offline) throws JMSException {
         super("Client" + id);
         setDaemon(true);

         this.id = id;
         conClientId = "cli" + id;
         this.clientType = clientType;
         selector = "(COMMIT = true and RELEVANT = true) or " + clientType.selector;

         this.lifetime = lifetime;
         this.online = online;
         this.offline = offline;

         subscribe();
      }

      @Override
      public void run() {
         long end = System.currentTimeMillis() + 60000;
         try {
            boolean sleep = false;
            while (true) {
               long max = end - System.currentTimeMillis();
               if (max <= 0)
                  break;

                    /*
                    if (sleep)
                        offline.sleepRandom();
                    else
                        sleep = true;
                    */

               Thread.sleep(100);

               processLock.readLock().lock();
               onlineCount.incrementAndGet();
               try {
                  process(online.next());
               } finally {
                  onlineCount.decrementAndGet();
                  processLock.readLock().unlock();
               }
            }

            if (!ALLOW_SUBSCRIPTION_ABANDONMENT || random(1) > 0)
               unsubscribe();
            else {
               LOG.info("Client abandon the subscription. " + this);

               // housekeeper should sweep these abandoned subscriptions
               houseKeeper.abandonedSubscriptions.add(conClientId);
            }
         } catch (Throwable e) {
            exit(toString() + " failed.", e);
         }

         clientManager.removeClient(this);
         LOG.info(toString() + " DONE.");
      }

      private void process(long millis) throws JMSException {
         //long end = System.currentTimeMillis() + millis;
         long end = System.currentTimeMillis() + 200;
         long hardEnd = end + 20000; // wait to finish the transaction.
         boolean inTransaction = false;
         int transCount = 0;

         Connection con = openConnection();
         Session sess = con.createSession(false, Session.CLIENT_ACKNOWLEDGE);
         consumer = (ActiveMQMessageConsumer) sess.createDurableSubscriber(topic, SUBSCRIPTION_NAME, selector, false);
         LOG.info(toString() + " ONLINE.");
         try {
            do {
               long max = end - System.currentTimeMillis();
               if (max <= 0) {
                  if (!inTransaction) {
                     LOG.info(toString() + " done after no work!");
                     break;
                  }

                  max = hardEnd - System.currentTimeMillis();
                  if (max <= 0)
                     exit("" + this + " failed: Transaction is not finished.");
               }

               Message message = consumer.receive(max);
               if (message == null)
                  continue;

               onClientMessage(message);

               if (message.propertyExists("COMMIT")) {
                  message.acknowledge(); // CLIENT_ACKNOWLEDGE

                  int trans = message.getIntProperty("TRANS");
                  LOG.info("Received Trans[id=" + trans + ", count=" + transCount + "] in " + this + ".");

                  inTransaction = false;
                  transCount = 0;

                  int committing = server.committingTransaction;
                  if (committing == trans) {
                     LOG.info("Going offline during transaction commit. messageID=" + message.getIntProperty("ID"));
                     break;
                  }
               } else {
                  inTransaction = true;
                  transCount++;
                  if (1 == transCount) {
                     LOG.info("In Trans[id=" + message.getIntProperty("TRANS") + "] first ID=" + message.getIntProperty("ID"));
                  }
               }
            } while (true);
         } finally {
            sess.close();
            con.close();

            LOG.info(toString() + " OFFLINE.");

            // Check if the messages are in the waiting
            // list for long time.
            Message topMessage = waitingList.peek();
            if (topMessage != null)
               checkDeliveryTime(topMessage);
         }
      }

      public void onServerMessage(Message message) throws JMSException {
         if (Boolean.TRUE.equals(message.getObjectProperty("COMMIT"))) {
            if (Boolean.TRUE.equals(message.getObjectProperty("RELEVANT")))
               waitingList.add(message);
         } else {
            String messageType = message.getStringProperty("TYPE");
            if (clientType.isRelevant(messageType))
               waitingList.add(message);
         }
      }

      public void onClientMessage(Message message) {
         Message serverMessage = waitingList.poll();
         try {
            Integer receivedId = (Integer) message.getObjectProperty("ID");
            if (processed != null && processed.contains(receivedId))
               LOG.info("! Message has been processed before. " + this + " redeliveredFlag=" + message.getJMSRedelivered() + ", message = " + message);

            if (serverMessage == null)
               exit("" + this + " failed: There is no next server message, but received: " + message);

            Integer serverId = (Integer) serverMessage.getObjectProperty("ID");
            if (receivedId == null || serverId == null)
               exit("" + this + " failed: message ID not found.\r\n" + " received: " + message + "\r\n" + "   server: " + serverMessage);

            if (!serverId.equals(receivedId)) {
               StringBuilder missingList = new StringBuilder();
               Object lastTrans = null;
               int transCount = 0;
               Message nextServerMessage = serverMessage;
               do {
                  Integer nextServerId = (Integer) nextServerMessage.getObjectProperty("ID");
                  if (nextServerId.equals(receivedId)) {
                     if (lastTrans != null)
                        missingList.append("Missing TRANS=").append(lastTrans).append(", size=").append(transCount).append("\r\n");
                     break;
                  }

                  Object trans = nextServerMessage.getObjectProperty("TRANS");
                  if (!trans.equals(lastTrans)) {
                     if (lastTrans != null)
                        missingList.append("Missing TRANS=").append(lastTrans).append(", size=").append(transCount).append("\r\n");
                     lastTrans = trans;
                     transCount = 1;
                  } else
                     transCount++;
               } while ((nextServerMessage = waitingList.poll()) != null);

               exit("Missing messages!\r\n" + missingList +
                       "Received message: " + message + "\r\n" +
                       "Expected message: " + serverMessage);
            }

            checkDeliveryTime(message);

            if (processed != null)
               processed.add(receivedId);
         } catch (Throwable e) {
            exit("" + this + ".onClientMessage failed.\r\n" + " received: " + message + "\r\n" + "   server: " + serverMessage, e);
         }
      }

      /**
       * Checks if the message was not delivered fast enough.
       */
      public void checkDeliveryTime(Message message) throws JMSException {
         long creation = message.getJMSTimestamp();
         long min = System.currentTimeMillis() - (offline.max + online.min) * (BROKER_RESTART > 0 ? 4 : 1);

         if (false && min > creation) {
            SimpleDateFormat df = new SimpleDateFormat("HH:mm:ss.SSS");
            exit("" + this + ".checkDeliveryTime failed. Message time: " + df.format(new Date(creation)) + ", min: " + df.format(new Date(min)) + "\r\n" + message);
         }
      }

      private Connection openConnection() throws JMSException {
         Connection con = cf.createConnection();
         con.setClientID(conClientId);
         ((ActiveMQConnection) con).setCloseTimeout(60000);
         con.start();
         return con;
      }

      private void subscribe() throws JMSException {
         processLock.readLock().lock();
         try {
            Connection con = openConnection();
            Session session = con.createSession(false, Session.AUTO_ACKNOWLEDGE);
            session.createDurableSubscriber(topic, SUBSCRIPTION_NAME, selector, true);
            session.close();
            con.close();
         } finally {
            processLock.readLock().unlock();
         }
      }

      private void unsubscribe() throws JMSException {
         processLock.readLock().lock();
         LOG.info("Unsubscribe: " + this);
         try {
            Connection con = openConnection();
            Session session = con.createSession(false, Session.AUTO_ACKNOWLEDGE);
            session.unsubscribe(SUBSCRIPTION_NAME);
            session.close();
            con.close();
         } finally {
            processLock.readLock().unlock();
         }
      }

      @Override
      public String toString() {
         return "Client[id=" + id + ", type=" + clientType + "] consumerId=" + (consumer != null ? consumer.getConsumerId() : "null");
      }
   }

   /**
    * Sweeps out not-used durable subscriptions.
    */
   private final class HouseKeeper extends Thread {

      private HouseKeeper() {
         super("HouseKeeper");
         setDaemon(true);
      }

      public final CopyOnWriteArrayList<String> abandonedSubscriptions = new CopyOnWriteArrayList<>();

      @Override
      public void run() {
         while (true) {
            try {
               Thread.sleep(3 * 60 * 1000);

               processLock.readLock().lock();
               try {
                  sweep();
               } finally {
                  processLock.readLock().unlock();
               }
            } catch (InterruptedException ex) {
               break;
            } catch (Throwable e) {
               Exception log = new Exception("HouseKeeper failed.", e);
               log.printStackTrace();
            }
         }
      }

      private void sweep() throws Exception {
         LOG.info("Housekeeper sweeping.");

         int closed = 0;
         ArrayList<String> sweeped = new ArrayList<>();
         try {
            for (String clientId : abandonedSubscriptions) {
               LOG.info("Sweeping out subscription of " + clientId + ".");
               broker.getAdminView().destroyDurableSubscriber(clientId, Client.SUBSCRIPTION_NAME);
               sweeped.add(clientId);
               closed++;
            }
         } catch (Exception ignored) {
            LOG.info("Ex on destroy sub " + ignored);
         } finally {
            abandonedSubscriptions.removeAll(sweeped);
         }

         LOG.info("Housekeeper sweeped out " + closed + " subscriptions.");
      }
   }

   public static int random(int max) {
      return (int) (Math.random() * (max + 1));
   }

   public static int random(int min, int max) {
      return random(max - min) + min;
   }

   public static void sleepRandom(int maxMillis) throws InterruptedException {
      Thread.sleep(random(maxMillis));
   }

   public static void sleepRandom(int minMillis, int maxMillis) throws InterruptedException {
      Thread.sleep(random(minMillis, maxMillis));
   }

   public static final class Random {

      final int min;
      final int max;

      Random(int min, int max) {
         this.min = min;
         this.max = max;
      }

      public int next() {
         return random(min, max);
      }

      public void sleepRandom() throws InterruptedException {
         DurableSubProcessConcurrentCommitActivateNoDuplicateTest.sleepRandom(min, max);
      }
   }

   public static void exit(String message) {
      exit(message, null);
   }

   public static void exit(String message, Throwable e) {
      Throwable cause = new RuntimeException(message, e);
      LOG.error(message, cause);
      exceptions.add(cause);
      ThreadTracker.result();
      //fail(cause.toString());
      System.exit(-9);
   }

   @Before
   public void setUp() throws Exception {
      topic = new ActiveMQTopic("TopicT");
      startBroker();

      clientManager = new ClientManager();
      server = new Server();
      houseKeeper = new HouseKeeper();

   }

   @After
   public void tearDown() throws Exception {
      destroyBroker();
   }

   private enum Persistence {
      MEMORY, LEVELDB, KAHADB
   }

   private void startBroker() throws Exception {
      startBroker(true);
   }

   private void startBroker(boolean deleteAllMessages) throws Exception {
      if (broker != null)
         return;

      broker = BrokerFactory.createBroker("broker:(vm://" + getName() + ")");
      broker.setBrokerName(getName());
      broker.setAdvisorySupport(false);
      broker.setDeleteAllMessagesOnStartup(deleteAllMessages);

      switch (PERSISTENT_ADAPTER) {
         case MEMORY:
            broker.setPersistent(false);
            break;

         case LEVELDB:
            File amqData = new File("activemq-data/" + getName() + "-leveldb");
            if (deleteAllMessages)
               delete(amqData);

            broker.setPersistent(true);
            LevelDBStore amq = new LevelDBStore();
            amq.setDirectory(amqData);
            broker.setPersistenceAdapter(amq);
            break;

         case KAHADB:
            File kahadbData = new File("activemq-data/" + getName() + "-kahadb");
            if (deleteAllMessages)
               delete(kahadbData);

            broker.setPersistent(true);
            KahaDBPersistenceAdapter kahadb = new KahaDBPersistenceAdapter();
            kahadb.setDirectory(kahadbData);
            kahadb.setJournalMaxFileLength(5 * 1024 * 1024);
            broker.setPersistenceAdapter(kahadb);
            break;
      }

      broker.addConnector("tcp://localhost:61656");

      broker.getSystemUsage().getMemoryUsage().setLimit(256 * 1024 * 1024);
      broker.getSystemUsage().getTempUsage().setLimit(256 * 1024 * 1024);
      broker.getSystemUsage().getStoreUsage().setLimit(1024 * 1024 * 1024);

      PolicyMap policyMap = new PolicyMap();
      PolicyEntry defaultEntry = new PolicyEntry();
      defaultEntry.setMaxAuditDepth(20000);
      policyMap.setDefaultEntry(defaultEntry);
      broker.setDestinationPolicy(policyMap);
      broker.start();
   }

   protected static String getName() {
      return "DurableSubProcessWithRestartTest";
   }

   private static boolean delete(File path) {
      if (path == null)
         return true;

      if (path.isDirectory()) {
         for (File file : path.listFiles()) {
            delete(file);
         }
      }
      return path.delete();
   }

   private void destroyBroker() throws Exception {
      if (broker == null)
         return;

      broker.stop();
      broker = null;
   }
}
