/*
 * 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.
 */

#include "CacheXmlParser.hpp"

#include <chrono>
#include <cinttypes>

#include <geode/PoolFactory.hpp>
#include <geode/PoolManager.hpp>
#include <geode/internal/chrono/duration.hpp>

#include "AutoDelete.hpp"
#include "CacheImpl.hpp"
#include "CacheRegionHelper.hpp"
#include "util/string.hpp"

#if defined(_WIN32)
#include <windows.h>
#else
#include <dlfcn.h>
#endif

namespace apache {
namespace geode {
namespace client {

namespace {
/** The name of the <code>cache</code> element */
auto CACHE = "cache";
auto CLIENT_CACHE = "client-cache";
auto PDX = "pdx";

/** The name of the <code>redundancy-level</code> element */
auto REDUNDANCY_LEVEL = "redundancy-level";

/** The name of the <code>region</code> element */
auto REGION = "region";

/** The name of the <code>root-region</code> element */
auto ROOT_REGION = "root-region";

/** The name of the <code>region-attributes</code> element */
auto REGION_ATTRIBUTES = "region-attributes";

auto LRU_ENTRIES_LIMIT = "lru-entries-limit";

auto DISK_POLICY = "disk-policy";

auto ENDPOINTS = "endpoints";

/** The name of the <code>region-time-to-live</code> element */
auto REGION_TIME_TO_LIVE = "region-time-to-live";

/** The name of the <code>region-idle-time</code> element */
auto REGION_IDLE_TIME = "region-idle-time";

/** The name of the <code>entry-time-to-live</code> element */
auto ENTRY_TIME_TO_LIVE = "entry-time-to-live";

/** The name of the <code>entry-idle-time</code> element */
auto ENTRY_IDLE_TIME = "entry-idle-time";

/** The name of the <code>expiration-attributes</code> element */
auto EXPIRATION_ATTRIBUTES = "expiration-attributes";

/** The name of the <code>cache-loader</code> element */
auto CACHE_LOADER = "cache-loader";

/** The name of the <code>cache-writer</code> element */
auto CACHE_WRITER = "cache-writer";

/** The name of the <code>cache-listener</code> element */
auto CACHE_LISTENER = "cache-listener";

/** The name of the <code>partition-resolver</code> element */
auto PARTITION_RESOLVER = "partition-resolver";

auto LIBRARY_NAME = "library-name";

auto LIBRARY_FUNCTION_NAME = "library-function-name";

auto CACHING_ENABLED = "caching-enabled";

auto INTEREST_LIST_ENABLED = "interest-list-enabled";

auto MAX_DISTRIBUTE_VALUE_LENGTH_WHEN_CREATE =
    "max-distribute-value-length-when-create";
/** The name of the <code>scope</code> attribute */
auto SCOPE = "scope";

/** The name of the <code>client-notification</code> attribute */
auto CLIENT_NOTIFICATION_ENABLED = "client-notification";

/** The name of the <code>initial-capacity</code> attribute */
auto INITIAL_CAPACITY = "initial-capacity";

/** The name of the <code>initial-capacity</code> attribute */
auto CONCURRENCY_LEVEL = "concurrency-level";

/** The name of the <code>load-factor</code> attribute */
auto LOAD_FACTOR = "load-factor";

/** The name of the <code>statistics-enabled</code> attribute */
auto STATISTICS_ENABLED = "statistics-enabled";

/** The name of the <code>timeout</code> attribute */
auto TIMEOUT = "timeout";

/** The name of the <code>action</code> attribute */
auto ACTION = "action";

/** The name of the <code>local</code> value */
auto LOCAL = "local";

/** The name of the <code>distributed-no-ack</code> value */
auto DISTRIBUTED_NO_ACK = "distributed-no-ack";

/** The name of the <code>distributed-ack</code> value */
auto DISTRIBUTED_ACK = "distributed-ack";

/** The name of the <code>global</code> value */
auto GLOBAL = "global";

/** The name of the <code>invalidate</code> value */
auto INVALIDATE = "invalidate";

/** The name of the <code>destroy</code> value */
auto DESTROY = "destroy";

/** The name of the <code>overflow</code> value */
auto OVERFLOWS = "overflows";

/** The name of the <code>overflow</code> value */
auto PERSIST = "persist";

/** The name of the <code>none</code> value */
auto NONE = "none";

/** The name of the <code>local-invalidate</code> value */
auto LOCAL_INVALIDATE = "local-invalidate";

/** The name of the <code>local-destroy</code> value */
auto LOCAL_DESTROY = "local-destroy";

/** The name of the <code>persistence-manager</code> value */
auto PERSISTENCE_MANAGER = "persistence-manager";

/** The name of the <code>property</code> value */
auto PROPERTY = "property";

auto CONCURRENCY_CHECKS_ENABLED = "concurrency-checks-enabled";

auto TOMBSTONE_TIMEOUT = "tombstone-timeout";

/** Pool elements and attributes */

auto POOL_NAME = "pool-name";
auto POOL = "pool";
auto NAME = "name";
auto VALUE = "value";
auto LOCATOR = "locator";
auto SERVER = "server";
auto HOST = "host";
auto PORT = "port";
auto IGNORE_UNREAD_FIELDS = "ignore-unread-fields";
auto READ_SERIALIZED = "read-serialized";
auto FREE_CONNECTION_TIMEOUT = "free-connection-timeout";
auto MULTIUSER_SECURE_MODE = "multiuser-authentication";
auto IDLE_TIMEOUT = "idle-timeout";
auto LOAD_CONDITIONING_INTERVAL = "load-conditioning-interval";
auto MAX_CONNECTIONS = "max-connections";
auto MIN_CONNECTIONS = "min-connections";
auto PING_INTERVAL = "ping-interval";
auto UPDATE_LOCATOR_LIST_INTERVAL = "update-locator-list-interval";
auto READ_TIMEOUT = "read-timeout";
auto RETRY_ATTEMPTS = "retry-attempts";
auto SERVER_GROUP = "server-group";
auto SOCKET_BUFFER_SIZE = "socket-buffer-size";
auto STATISTIC_INTERVAL = "statistic-interval";
auto SUBSCRIPTION_ACK_INTERVAL = "subscription-ack-interval";
auto SUBSCRIPTION_ENABLED = "subscription-enabled";
auto SUBSCRIPTION_MTT = "subscription-message-tracking-timeout";
auto SUBSCRIPTION_REDUNDANCY = "subscription-redundancy";
auto THREAD_LOCAL_CONNECTIONS = "thread-local-connections";
auto CLONING_ENABLED = "cloning-enabled";
auto ID = "id";
auto REFID = "refid";
auto PR_SINGLE_HOP_ENABLED = "pr-single-hop-enabled";

std::vector<std::pair<std::string, int>> parseEndPoints(
    const std::string &str) {
  std::vector<std::pair<std::string, int>> endPoints;
  std::string::size_type start = 0;
  std::string::size_type pos = str.find_first_of(',');
  while (std::string::npos != pos) {
    const std::string endPoint(str.substr(start, pos - start));
    const std::string::size_type split = endPoint.find_last_of(':');
    if (std::string::npos == split) {
      endPoints.push_back(std::pair<std::string, int>(endPoint, 0));
    } else {
      int port = 0;
      try {
        port = std::stoi(endPoint.substr(split + 1));
      } catch (...) {
        // NOP
      }
      endPoints.push_back(
          std::pair<std::string, int>(endPoint.substr(0, split), port));
    }
    start = pos + 1;
    pos = str.find_first_of(',', start);
  }
  return endPoints;
}
}  // namespace

/**
 * warningDebug:
 * @ctxt:  An XML parser context
 * @msg:  the message to display/transmit
 * @...:  extra parameters for the message display
 *
 * Display and format a warning messages, gives file, line, position and
 * extra parameters.
 */
extern "C" void warningDebug(void *, const char *msg, ...) {
  char logmsg[2048];
  va_list args;
  va_start(args, msg);
  // NOLINTNEXTLINE(clang-analyzer-valist.Uninitialized): clang-tidy bug
  std::vsnprintf(logmsg, sizeof(logmsg), msg, args);
  va_end(args);
  LOGWARN("SAX.warning during XML declarative client initialization: %s",
          logmsg);
}

/////////////End of XML Parser Cackllback functions///////////////

///////////////static variables of the class////////////////////////

FactoryLoaderFn<CacheLoader> CacheXmlParser::managedCacheLoaderFn_ = nullptr;
FactoryLoaderFn<CacheListener> CacheXmlParser::managedCacheListenerFn_ =
    nullptr;
FactoryLoaderFn<PartitionResolver> CacheXmlParser::managedPartitionResolverFn_ =
    nullptr;
FactoryLoaderFn<CacheWriter> CacheXmlParser::managedCacheWriterFn_ = nullptr;
FactoryLoaderFn<PersistenceManager>
    CacheXmlParser::managedPersistenceManagerFn_ = nullptr;

//////////////////////////////////////////////////////////////////

CacheXmlParser::CacheXmlParser(Cache *cache)
    : cacheCreation_(nullptr),
      nestedRegions_(0),
      config_(nullptr),
      parserMessage_(""),
      flagCacheXmlException_(false),
      flagIllegalStateException_(false),
      flagAnyOtherException_(false),
      flagExpirationAttribute_(false),
      namedRegions_(CacheImpl::getRegionShortcut()),
      poolFactory_(nullptr),
      cache_(cache) {
  start_element_map_.emplace(
      std::make_pair(std::string(CACHE), &CacheXmlParser::startCache));
  start_element_map_.emplace(
      std::make_pair(std::string(CLIENT_CACHE), &CacheXmlParser::startCache));
  start_element_map_.emplace(
      std::make_pair(std::string(PDX), &CacheXmlParser::startPdx));
  start_element_map_.emplace(
      std::make_pair(std::string(REGION), &CacheXmlParser::startRegion));
  start_element_map_.emplace(
      std::make_pair(std::string(ROOT_REGION), &CacheXmlParser::startRegion));
  start_element_map_.emplace(std::make_pair(
      std::string(REGION_ATTRIBUTES), &CacheXmlParser::startRegionAttributes));
  start_element_map_.emplace(
      std::make_pair(std::string(EXPIRATION_ATTRIBUTES),
                     &CacheXmlParser::startExpirationAttributes));
  start_element_map_.emplace(std::make_pair(std::string(CACHE_LOADER),
                                            &CacheXmlParser::startCacheLoader));
  start_element_map_.emplace(std::make_pair(std::string(CACHE_WRITER),
                                            &CacheXmlParser::startCacheWriter));
  start_element_map_.emplace(std::make_pair(
      std::string(CACHE_LISTENER), &CacheXmlParser::startCacheListener));
  start_element_map_.emplace(
      std::make_pair(std::string(PARTITION_RESOLVER),
                     &CacheXmlParser::startPartitionResolver));
  start_element_map_.emplace(
      std::make_pair(std::string(PERSISTENCE_MANAGER),
                     &CacheXmlParser::startPersistenceManager));
  start_element_map_.emplace(std::make_pair(
      std::string(PROPERTY), &CacheXmlParser::startPersistenceProperty));
  start_element_map_.emplace(
      std::make_pair(std::string(POOL), &CacheXmlParser::startPool));
  start_element_map_.emplace(
      std::make_pair(std::string(LOCATOR), &CacheXmlParser::startLocator));
  start_element_map_.emplace(
      std::make_pair(std::string(SERVER), &CacheXmlParser::startServer));

  end_element_map_.emplace(
      std::make_pair(std::string(CACHE), &CacheXmlParser::endCache));
  end_element_map_.emplace(
      std::make_pair(std::string(CLIENT_CACHE), &CacheXmlParser::endCache));
  end_element_map_.emplace(
      std::make_pair(std::string(REGION), &CacheXmlParser::endRegion));
  end_element_map_.emplace(
      std::make_pair(std::string(ROOT_REGION), &CacheXmlParser::endRegion));
  end_element_map_.emplace(std::make_pair(
      std::string(REGION_ATTRIBUTES), &CacheXmlParser::endRegionAttributes));
  end_element_map_.emplace(std::make_pair(
      std::string(REGION_TIME_TO_LIVE), &CacheXmlParser::endRegionTimeToLive));
  end_element_map_.emplace(std::make_pair(std::string(REGION_IDLE_TIME),
                                          &CacheXmlParser::endRegionIdleTime));
  end_element_map_.emplace(std::make_pair(std::string(ENTRY_TIME_TO_LIVE),
                                          &CacheXmlParser::endEntryTimeToLive));
  end_element_map_.emplace(std::make_pair(std::string(ENTRY_IDLE_TIME),
                                          &CacheXmlParser::endEntryIdleTime));
  end_element_map_.emplace(
      std::make_pair(std::string(PERSISTENCE_MANAGER),
                     &CacheXmlParser::endPersistenceManager));
  end_element_map_.emplace(
      std::make_pair(std::string(POOL), &CacheXmlParser::endPool));
}

void CacheXmlParser::startElement(const XMLCh *const,
                                  const XMLCh *const localname,
                                  const XMLCh *const,
                                  const xercesc::Attributes &attrs) {
  auto message = xercesc::XMLString::transcode(localname);
  auto name = std::string(message);
  auto iter = start_element_map_.find(name);
  if (iter != std::end(start_element_map_)) {
    iter->second(*this, attrs);
  }
  xercesc::XMLString::release(&message);
}

void CacheXmlParser::endElement(const XMLCh *const,
                                const XMLCh *const localname,
                                const XMLCh *const) {
  auto message = xercesc::XMLString::transcode(localname);
  auto name = std::string(message);
  auto iter = end_element_map_.find(name);
  if (iter != std::end(end_element_map_)) {
    iter->second(*this);
  }
  xercesc::XMLString::release(&message);
}

void CacheXmlParser::fatalError(const xercesc::SAXParseException &exception) {
  char *message = xercesc::XMLString::transcode(exception.getMessage());
  LOGDEBUG("Fatal Error: \"%s\" at line: %" PRIu64, message,
           exception.getLineNumber());
  auto ex = CacheXmlException(message);
  xercesc::XMLString::release(&message);
  throw ex;
}

void CacheXmlParser::parseFile(const char *filename) {
  try {
    xercesc::XMLPlatformUtils::Initialize();
  } catch (const xercesc::XMLException &toCatch) {
    char *message = xercesc::XMLString::transcode(toCatch.getMessage());
    auto exceptionMessage = "Error parsing XML file: " + std::string(message);
    xercesc::XMLString::release(&message);
    throw CacheXmlException(exceptionMessage);
  }

  auto parser = xercesc::XMLReaderFactory::createXMLReader();

  parser->setFeature(xercesc::XMLUni::fgXercesSchema, false);
  parser->setContentHandler(this);
  parser->setErrorHandler(this);

  try {
    parser->parse(filename);
  } catch (const xercesc::XMLException &toCatch) {
    char *message = xercesc::XMLString::transcode(toCatch.getMessage());
    auto exceptionMessage = "Error parsing XML file: " + std::string(message);
    xercesc::XMLString::release(&message);
    throw CacheXmlException(exceptionMessage);
  } catch (const xercesc::SAXParseException &toCatch) {
    char *message = xercesc::XMLString::transcode(toCatch.getMessage());
    auto exceptionMessage = "Error parsing XML file: " + std::string(message);
    xercesc::XMLString::release(&message);
    throw CacheXmlException(exceptionMessage);
  }

  delete parser;
  xercesc::XMLPlatformUtils::Terminate();
}

void CacheXmlParser::parseMemory(const char *buffer, int size) {
  try {
    xercesc::XMLPlatformUtils::Initialize();
  } catch (const xercesc::XMLException &toCatch) {
    char *message = xercesc::XMLString::transcode(toCatch.getMessage());
    auto exceptionMessage = "Error parsing XML file: " + std::string(message);
    xercesc::XMLString::release(&message);
    throw CacheXmlException(exceptionMessage);
  }

  auto parser = xercesc::XMLReaderFactory::createXMLReader();

  parser->setContentHandler(this);
  parser->setErrorHandler(this);

  try {
    xercesc::MemBufInputSource myxml_buf(
        reinterpret_cast<const XMLByte *>(buffer), size,
        "CacheXmlParser memory source");
    parser->parse(myxml_buf);
  } catch (const xercesc::XMLException &toCatch) {
    char *message = xercesc::XMLString::transcode(toCatch.getMessage());
    auto exceptionMessage = "Error parsing XML file: " + std::string(message);
    xercesc::XMLString::release(&message);
    throw CacheXmlException(exceptionMessage);
  } catch (const xercesc::SAXParseException &toCatch) {
    char *message = xercesc::XMLString::transcode(toCatch.getMessage());
    auto exceptionMessage = "Error parsing XML file: " + std::string(message);
    xercesc::XMLString::release(&message);
    throw CacheXmlException(exceptionMessage);
  }

  delete parser;
  xercesc::XMLPlatformUtils::Terminate();
}

//////////////////////  Static Methods  //////////////////////

/**
 * Parses XML data and from it creates an instance of
 * <code>CacheXmlParser</code> that can be used to create
 * the {@link Cache}, etc.
 *
 * @param  cacheXml
 *         The xml file
 *
 * @throws CacheXmlException
 *         Something went wrong while parsing the XML
 * @throws OutOfMemoryException
 * @throws CacheXmlException
 *         If xml file is not well-formed or
 *         Something went wrong while parsing the XML
 * @throws IllegalStateException
 *         If xml file is well-flrmed but not valid
 * @throws UnknownException otherwise
 */
CacheXmlParser *CacheXmlParser::parse(const char *cacheXml, Cache *cache) {
  CacheXmlParser *handler;
  _GEODE_NEW(handler, CacheXmlParser(cache));
  // use RAII to delete the handler object in case of exceptions
  DeleteObject<CacheXmlParser> delHandler(handler);

  {
    handler->parseFile(cacheXml);
    delHandler.noDelete();
    return handler;
  }
}

void CacheXmlParser::setAttributes(Cache *) {}

/**
 * Creates cache artifacts ({@link Cache}s, etc.) based upon the XML
 * parsed by this parser.
 *
 * @param  cache
 *         The cachewhcih is to be populated
 * @throws OutOfMemoryException if the memory allocation failed
 * @throws NotConnectedException if the cache is not connected
 * @throws InvalidArgumentException if the attributePtr is nullptr.
 * or if RegionAttributes is null or if regionName is null,
 * the empty   string, or contains a '/'
 * @throws RegionExistsException
 * @throws CacheClosedException if the cache is closed
 *         at the time of region creation
 * @throws UnknownException otherwise
 *
 */
void CacheXmlParser::create(Cache *cache) {
  // use DeleteObject class to delete cacheCreation_ in case of exceptions
  DeleteObject<CacheXmlCreation> delCacheCreation(cacheCreation_);

  if (cache == nullptr) {
    throw IllegalArgumentException(
        "XML:No cache specified for performing configuration");
  }
  if (!cacheCreation_) {
    throw CacheXmlException("XML: Element <cache> was not provided in the xml");
  }
  cacheCreation_->create(cache);
  delCacheCreation.noDelete();
  LOGINFO("Declarative configuration of cache completed successfully");
}

std::string CacheXmlParser::getOptionalAttribute(
    const xercesc::Attributes &attrs, const char *attributeName) {
  using unique_xml_char = std::unique_ptr<XMLCh, std::function<void(XMLCh *)>>;
  using unique_xerces_char = std::unique_ptr<char, std::function<void(char *)>>;
  auto xml_deleter = [](XMLCh *ch) { xercesc::XMLString::release(&ch); };
  auto xerces_deleter = [](char *ch) { xercesc::XMLString::release(&ch); };

  unique_xml_char translatedName(xercesc::XMLString::transcode(attributeName),
                                 xml_deleter);
  auto value = attrs.getValue(translatedName.get());
  if (!value) {
    return "";
  }

  unique_xerces_char translatedValue(xercesc::XMLString::transcode(value),
                                     xerces_deleter);
  if (!strlen(translatedValue.get())) {
    throw CacheXmlException("XML: Empty value provided for attribute: " +
                            std::string(CACHE) + " or " + CLIENT_CACHE);
  }

  return {translatedValue.get()};
}

std::string CacheXmlParser::getRequiredAttribute(
    const xercesc::Attributes &attrs, const char *attributeName) {
  using unique_xml_char = std::unique_ptr<XMLCh, std::function<void(XMLCh *)>>;
  auto xml_deleter = [](XMLCh *ch) { xercesc::XMLString::release(&ch); };

  unique_xml_char translatedName(xercesc::XMLString::transcode(attributeName),
                                 xml_deleter);
  auto value = attrs.getValue(translatedName.get());
  if (!value) {
    throw CacheXmlException("XML: No value provided for required attribute: " +
                            std::string(attributeName));
  }

  return getOptionalAttribute(attrs, attributeName);
}

void CacheXmlParser::startCache(const xercesc::Attributes &attrs) {
  auto value = getOptionalAttribute(attrs, ENDPOINTS);
  if (!value.empty()) {
    if (poolFactory_) {
      for (auto &&endPoint : parseEndPoints(value)) {
        poolFactory_->addServer(endPoint.first, endPoint.second);
      }
    }
  }

  value = getOptionalAttribute(attrs, REDUNDANCY_LEVEL);
  if (!value.empty()) {
    if (poolFactory_) {
      poolFactory_->setSubscriptionRedundancy(std::stoi(value));
    }
  }

  _GEODE_NEW(cacheCreation_, CacheXmlCreation());
}

void CacheXmlParser::startPdx(const xercesc::Attributes &attrs) {
  auto ignoreUnreadFields = getOptionalAttribute(attrs, IGNORE_UNREAD_FIELDS);
  if (!ignoreUnreadFields.empty()) {
    if (equal_ignore_case(ignoreUnreadFields, "true")) {
      cacheCreation_->setPdxIgnoreUnreadField(true);
    } else {
      cacheCreation_->setPdxIgnoreUnreadField(false);
    }
  }

  auto pdxReadSerialized = getOptionalAttribute(attrs, READ_SERIALIZED);
  if (!pdxReadSerialized.empty()) {
    if (equal_ignore_case(pdxReadSerialized, "true")) {
      cacheCreation_->setPdxReadSerialized(true);
    } else {
      cacheCreation_->setPdxReadSerialized(false);
    }
  }
}

void CacheXmlParser::startLocator(const xercesc::Attributes &attrs) {
  poolFactory_ = std::static_pointer_cast<PoolFactory>(_stack.top());

  auto host = getRequiredAttribute(attrs, HOST);
  auto port = getRequiredAttribute(attrs, PORT);

  poolFactory_->addLocator(host, std::stoi(port));
}

void CacheXmlParser::startServer(const xercesc::Attributes &attrs) {
  auto factory = std::static_pointer_cast<PoolFactory>(_stack.top());

  auto host = getRequiredAttribute(attrs, HOST);
  auto port = getRequiredAttribute(attrs, PORT);

  factory->addServer(host, std::stoi(port));
}

void CacheXmlParser::startPool(const xercesc::Attributes &attrs) {
  using apache::geode::client::equal_ignore_case;
  using apache::geode::internal::chrono::duration::from_string;

  auto factory =
      std::make_shared<PoolFactory>(cache_->getPoolManager().createFactory());

  auto poolName = getRequiredAttribute(attrs, NAME);

  auto poolxml = std::make_shared<PoolXmlCreation>(poolName, factory);

  auto freeConnectionTimeout =
      getOptionalAttribute(attrs, FREE_CONNECTION_TIMEOUT);
  if (!freeConnectionTimeout.empty()) {
    factory->setFreeConnectionTimeout(
        from_string<std::chrono::milliseconds>(freeConnectionTimeout));
  }

  auto multiUserSecureMode = getOptionalAttribute(attrs, MULTIUSER_SECURE_MODE);
  if (!multiUserSecureMode.empty()) {
    if (equal_ignore_case(multiUserSecureMode, "true")) {
      factory->setMultiuserAuthentication(true);
    } else {
      factory->setMultiuserAuthentication(false);
    }
  }

  auto idleTimeout = getOptionalAttribute(attrs, IDLE_TIMEOUT);
  if (!idleTimeout.empty()) {
    factory->setIdleTimeout(
        from_string<std::chrono::milliseconds>(idleTimeout));
  }

  auto loadConditioningInterval =
      getOptionalAttribute(attrs, LOAD_CONDITIONING_INTERVAL);
  if (!loadConditioningInterval.empty()) {
    factory->setLoadConditioningInterval(
        from_string<std::chrono::milliseconds>(loadConditioningInterval));
  }

  auto maxConnections = getOptionalAttribute(attrs, MAX_CONNECTIONS);
  if (!maxConnections.empty()) {
    factory->setMaxConnections(atoi(maxConnections.c_str()));
  }

  auto minConnections = getOptionalAttribute(attrs, MIN_CONNECTIONS);
  if (!minConnections.empty()) {
    factory->setMinConnections(atoi(minConnections.c_str()));
  }

  auto pingInterval = getOptionalAttribute(attrs, PING_INTERVAL);
  if (!pingInterval.empty()) {
    factory->setPingInterval(
        from_string<std::chrono::milliseconds>(std::string(pingInterval)));
  }

  auto updateLocatorListInterval =
      getOptionalAttribute(attrs, UPDATE_LOCATOR_LIST_INTERVAL);
  if (!updateLocatorListInterval.empty()) {
    factory->setUpdateLocatorListInterval(
        from_string<std::chrono::milliseconds>(updateLocatorListInterval));
  }

  auto readTimeout = getOptionalAttribute(attrs, READ_TIMEOUT);
  if (!readTimeout.empty()) {
    factory->setReadTimeout(
        from_string<std::chrono::milliseconds>(std::string(readTimeout)));
  }

  auto retryAttempts = getOptionalAttribute(attrs, RETRY_ATTEMPTS);
  if (!retryAttempts.empty()) {
    factory->setRetryAttempts(atoi(retryAttempts.c_str()));
  }

  auto serverGroup = getOptionalAttribute(attrs, SERVER_GROUP);
  if (!serverGroup.empty()) {
    factory->setServerGroup(serverGroup);
  }

  auto socketBufferSize = getOptionalAttribute(attrs, SOCKET_BUFFER_SIZE);
  if (!socketBufferSize.empty()) {
    factory->setSocketBufferSize(atoi(socketBufferSize.c_str()));
  }

  auto statisticInterval = getOptionalAttribute(attrs, STATISTIC_INTERVAL);
  if (!statisticInterval.empty()) {
    factory->setStatisticInterval(
        from_string<std::chrono::milliseconds>(statisticInterval));
  }

  auto subscriptionAckInterval =
      getOptionalAttribute(attrs, SUBSCRIPTION_ACK_INTERVAL);
  if (!subscriptionAckInterval.empty()) {
    factory->setSubscriptionAckInterval(
        from_string<std::chrono::milliseconds>(subscriptionAckInterval));
  }

  auto subscriptionEnabled = getOptionalAttribute(attrs, SUBSCRIPTION_ENABLED);
  if (!subscriptionEnabled.empty()) {
    if (equal_ignore_case(subscriptionEnabled, "true")) {
      factory->setSubscriptionEnabled(true);
    } else {
      factory->setSubscriptionEnabled(false);
    }
  }

  auto subscriptionMessageTrackingTimeout =
      getOptionalAttribute(attrs, SUBSCRIPTION_MTT);
  if (!subscriptionMessageTrackingTimeout.empty()) {
    factory->setSubscriptionMessageTrackingTimeout(
        from_string<std::chrono::milliseconds>(
            subscriptionMessageTrackingTimeout));
  }

  auto subscriptionRedundancy =
      getOptionalAttribute(attrs, SUBSCRIPTION_REDUNDANCY);
  if (!subscriptionRedundancy.empty()) {
    factory->setSubscriptionRedundancy(atoi(subscriptionRedundancy.c_str()));
  }

  auto threadLocalConnections =
      getOptionalAttribute(attrs, THREAD_LOCAL_CONNECTIONS);
  if (!threadLocalConnections.empty()) {
    if (equal_ignore_case(threadLocalConnections, "true")) {
      factory->setThreadLocalConnections(true);
    } else {
      factory->setThreadLocalConnections(false);
    }
  }

  auto prSingleHopEnabled = getOptionalAttribute(attrs, PR_SINGLE_HOP_ENABLED);
  if (!prSingleHopEnabled.empty()) {
    if (equal_ignore_case(prSingleHopEnabled, "true")) {
      factory->setPRSingleHopEnabled(true);
    } else {
      factory->setPRSingleHopEnabled(false);
    }
  }

  _stack.push(poolxml);
  _stack.push(factory);
}

void CacheXmlParser::endPool() {
  _stack.pop();  // remove factory
  auto poolxml = std::static_pointer_cast<PoolXmlCreation>(_stack.top());
  _stack.pop();  // remove pool
  cacheCreation_->addPool(poolxml);
}

/**
 * When a <code>region</code> element is first encountered, we
 * create a {@link RegionCreation} and push it on the _stack.
 * An {@link RegionAttributesFactory }is also created and puhed on _stack.
 */
void CacheXmlParser::startRegion(const xercesc::Attributes &attrs) {
  incNesting();
  auto isRoot = isRootLevel();
  auto regionName = getRequiredAttribute(attrs, NAME);

  auto region = std::make_shared<RegionXmlCreation>(regionName, isRoot);
  if (!region) {
    throw UnknownException("CacheXmlParser::startRegion:Out of memory");
  }

  _stack.push(region);

  auto refid = getOptionalAttribute(attrs, REFID);
  if (!refid.empty()) {
    if (namedRegions_.find(refid) != namedRegions_.end()) {
      auto regionAttributesFactory =
          RegionAttributesFactory(namedRegions_[refid]);
      region->setAttributes(regionAttributesFactory.create());
    } else {
      throw CacheXmlException("XML:referenced named attribute '" + refid +
                              "' does not exist.");
    }
  }
}

void CacheXmlParser::startRegionAttributes(const xercesc::Attributes &attrs) {
  bool isDistributed = false;
  bool isTCR = false;
  std::shared_ptr<RegionAttributesFactory> regionAttributesFactory = nullptr;

  if (attrs.getLength() > 24) {
    throw CacheXmlException(
        "XML:Too many attributes provided for <region-attributes>");
  }

  if (attrs.getLength() == 0) {
    auto region = std::static_pointer_cast<RegionXmlCreation>(_stack.top());
    regionAttributesFactory =
        std::make_shared<RegionAttributesFactory>(region->getAttributes());
  } else {
    auto id = getOptionalAttribute(attrs, ID);
    if (!id.empty()) {
      auto region = std::static_pointer_cast<RegionXmlCreation>(_stack.top());
      region->setAttrId(id);
    }

    auto refid = getOptionalAttribute(attrs, REFID);
    if (refid.empty()) {
      auto region = std::static_pointer_cast<RegionXmlCreation>(_stack.top());
      regionAttributesFactory =
          std::make_shared<RegionAttributesFactory>(region->getAttributes());
    } else {
      if (namedRegions_.find(refid) != namedRegions_.end()) {
        regionAttributesFactory =
            std::make_shared<RegionAttributesFactory>(namedRegions_[refid]);
      } else {
        throw CacheXmlException("XML:referenced named attribute '" + refid +
                                "' does not exist.");
      }
    }

    if (!regionAttributesFactory) {
      throw UnknownException(
          "CacheXmlParser::startRegionAttributes:Out of memory");
    }

    auto clientNotificationEnabled =
        getOptionalAttribute(attrs, CLIENT_NOTIFICATION_ENABLED);
    if (!clientNotificationEnabled.empty()) {
      bool flag = false;
      std::transform(clientNotificationEnabled.begin(),
                     clientNotificationEnabled.end(),
                     clientNotificationEnabled.begin(), ::tolower);
      if ("false" == clientNotificationEnabled) {
        flag = false;
      } else if ("true" == clientNotificationEnabled) {
        flag = true;
      } else {
        throw CacheXmlException(
            "XML: " + clientNotificationEnabled +
            " is not a valid name for the attribute <client-notification>");
      }
      if (poolFactory_) {
        poolFactory_->setSubscriptionEnabled(flag);
      }
    }

    auto initialCapacity = getOptionalAttribute(attrs, INITIAL_CAPACITY);
    if (!initialCapacity.empty()) {
      regionAttributesFactory->setInitialCapacity(std::stoi(initialCapacity));
    }

    auto concurrencyLevel = getOptionalAttribute(attrs, CONCURRENCY_LEVEL);
    if (!concurrencyLevel.empty()) {
      regionAttributesFactory->setConcurrencyLevel(std::stoi(concurrencyLevel));
    }

    auto loadFactor = getOptionalAttribute(attrs, LOAD_FACTOR);
    if (!loadFactor.empty()) {
      regionAttributesFactory->setLoadFactor(std::stof(loadFactor));
    }

    auto cachingEnabled = getOptionalAttribute(attrs, CACHING_ENABLED);
    if (!cachingEnabled.empty()) {
      bool flag = false;
      std::transform(cachingEnabled.begin(), cachingEnabled.end(),
                     cachingEnabled.begin(), ::tolower);
      if ("false" == cachingEnabled) {
        flag = false;
      } else if ("true" == cachingEnabled) {
        flag = true;
      } else {
        throw CacheXmlException(
            "XML: " + cachingEnabled +
            " is not a valid name for the attribute <caching-enabled>");
      }
      regionAttributesFactory->setCachingEnabled(flag);
    }

    auto lruEntriesLimit = getOptionalAttribute(attrs, LRU_ENTRIES_LIMIT);
    if (!lruEntriesLimit.empty()) {
      regionAttributesFactory->setLruEntriesLimit(std::stoi(lruEntriesLimit));
    }

    auto diskPolicyString = getOptionalAttribute(attrs, DISK_POLICY);
    if (!diskPolicyString.empty()) {
      auto diskPolicy = apache::geode::client::DiskPolicyType::NONE;
      if (OVERFLOWS == diskPolicyString) {
        diskPolicy = apache::geode::client::DiskPolicyType::OVERFLOWS;
      } else if (PERSIST == diskPolicyString) {
        throw IllegalStateException("Persistence feature is not supported");
      } else if (NONE == diskPolicyString) {
        diskPolicy = apache::geode::client::DiskPolicyType::NONE;
      } else {
        throw CacheXmlException(
            "XML: " + diskPolicyString +
            " is not a valid name for the attribute <disk-policy>");
      }
      regionAttributesFactory->setDiskPolicy(diskPolicy);
    }

    auto endpoints = getOptionalAttribute(attrs, ENDPOINTS);
    if (!endpoints.empty()) {
      if (poolFactory_) {
        for (auto &&endPoint : parseEndPoints(ENDPOINTS)) {
          poolFactory_->addServer(endPoint.first, endPoint.second);
        }
      }
      isTCR = true;
    }

    auto poolName = getOptionalAttribute(attrs, POOL_NAME);
    if (!poolName.empty()) {
      regionAttributesFactory->setPoolName(poolName);
      isTCR = true;
    }

    auto cloningEnabled = getOptionalAttribute(attrs, CLONING_ENABLED);
    if (!cloningEnabled.empty()) {
      bool flag = false;
      std::transform(cloningEnabled.begin(), cloningEnabled.end(),
                     cloningEnabled.begin(), ::tolower);

      if ("false" == cloningEnabled) {
        flag = false;
      } else if ("true" == cloningEnabled) {
        flag = true;
      } else {
        throw CacheXmlException("XML: " + cloningEnabled +
                                " is not a valid value for the attribute <" +
                                std::string(CLONING_ENABLED) + ">");
      }
      regionAttributesFactory->setCloningEnabled(flag);
      isTCR = true;
    }

    auto concurrencyChecksEnabled =
        getOptionalAttribute(attrs, CONCURRENCY_CHECKS_ENABLED);
    if (!concurrencyChecksEnabled.empty()) {
      bool flag = false;
      std::transform(concurrencyChecksEnabled.begin(),
                     concurrencyChecksEnabled.end(),
                     concurrencyChecksEnabled.begin(), ::tolower);
      if ("false" == concurrencyChecksEnabled) {
        flag = false;
      } else if ("true" == concurrencyChecksEnabled) {
        flag = true;
      } else {
        throw CacheXmlException("XML: " + concurrencyChecksEnabled +
                                " is not a valid value for the attribute "
                                "<" +
                                std::string(CONCURRENCY_CHECKS_ENABLED) + ">");
      }
      regionAttributesFactory->setConcurrencyChecksEnabled(flag);
    }
  }

  if (isDistributed && isTCR) {
    // we don't allow DR+TCR at current stage
    throw CacheXmlException(
        "XML:endpoints cannot be defined for distributed region.\n");
  }

  _stack.push(regionAttributesFactory);
}

void CacheXmlParser::endRegionAttributes() {
  auto regionAttributesFactory =
      std::static_pointer_cast<RegionAttributesFactory>(_stack.top());
  _stack.pop();
  if (!regionAttributesFactory) {
    throw UnknownException(
        "CacheXmlParser::endRegion:RegionAttributesFactory is null");
  }

  auto regionAttributes = regionAttributesFactory->create();

  auto regionPtr = std::static_pointer_cast<RegionXmlCreation>(_stack.top());
  if (!regionPtr) {
    throw UnknownException("CacheXmlParser::endRegion:Region is null");
  }

  std::string id = regionPtr->getAttrId();
  if (id != "") {
    namedRegions_[id] = regionAttributes;
  }

  regionPtr->setAttributes(regionAttributes);
}

/**
 * When a <code>expiration-attributes</code> element is first
 * encountered, we create an {@link ExpirationAttibutes} object from
 * the element's attributes and push it on the _stack.
 */
void CacheXmlParser::startExpirationAttributes(
    const xercesc::Attributes &attrs) {
  using apache::geode::internal::chrono::duration::from_string;

  flagExpirationAttribute_ = true;

  if (attrs.getLength() > 2) {
    throw CacheXmlException(
        "XML:Incorrect number of attributes provided for "
        "<expiration-attributes>");
  }

  ExpirationAction expire = ExpirationAction::INVALID_ACTION;
  auto action = getOptionalAttribute(attrs, ACTION);
  if (action.empty()) {
    throw CacheXmlException(
        "XML:The attribute <action> of <expiration-attributes> cannot be"
        "set to empty string. It should either have a action or the "
        "attribute should be removed. In the latter case the default action "
        "will be set");
  } else if (action == INVALIDATE) {
    expire = ExpirationAction::INVALIDATE;
  } else if (action == DESTROY) {
    expire = ExpirationAction::DESTROY;
  } else if (action == LOCAL_INVALIDATE) {
    expire = ExpirationAction::LOCAL_INVALIDATE;
  } else if (action == LOCAL_DESTROY) {
    expire = ExpirationAction::LOCAL_DESTROY;
  } else {
    throw CacheXmlException("XML: " + action +
                            " is not a valid value for the attribute <action>");
  }

  auto timeOut = getOptionalAttribute(attrs, TIMEOUT);
  if (timeOut.empty()) {
    throw CacheXmlException(
        "XML:Value for attribute <timeout> needs to be specified");
  }

  auto timeOutSeconds = from_string<std::chrono::seconds>(timeOut);

  auto expireAttr =
      std::make_shared<ExpirationAttributes>(timeOutSeconds, expire);
  if (!expireAttr) {
    throw UnknownException(
        "CacheXmlParser::startExpirationAttributes:Out of memory");
  }
  expireAttr->setAction(expire);

  _stack.push(expireAttr);
}

std::string CacheXmlParser::getLibraryName(const xercesc::Attributes &attrs) {
  return getOptionalAttribute(attrs, LIBRARY_NAME);
}

std::string CacheXmlParser::getLibraryFunctionName(
    const xercesc::Attributes &attrs) {
  return getRequiredAttribute(attrs, LIBRARY_FUNCTION_NAME);
}

void CacheXmlParser::startPersistenceManager(const xercesc::Attributes &attrs) {
  auto libraryName = getLibraryName(attrs);
  auto libraryFunctionName = getLibraryFunctionName(attrs);

  verifyFactoryFunction(managedPersistenceManagerFn_, libraryName,
                        libraryFunctionName);

  _stack.emplace(std::make_shared<std::string>(std::move(libraryName)));
  _stack.emplace(std::make_shared<std::string>(std::move(libraryFunctionName)));
}

void CacheXmlParser::startPersistenceProperty(
    const xercesc::Attributes &attrs) {
  auto propertyName = getRequiredAttribute(attrs, NAME);
  auto propertyValue = getRequiredAttribute(attrs, VALUE);

  if (config_ == nullptr) {
    config_ = Properties::create();
  }

  config_->insert(propertyName, propertyValue);
}

void CacheXmlParser::startCacheLoader(const xercesc::Attributes &attrs) {
  auto libraryName = getLibraryName(attrs);
  auto libraryFunctionName = getLibraryFunctionName(attrs);

  verifyFactoryFunction(managedCacheLoaderFn_, libraryName,
                        libraryFunctionName);

  auto regionAttributesFactory =
      std::static_pointer_cast<RegionAttributesFactory>(_stack.top());
  regionAttributesFactory->setCacheLoader(libraryName, libraryFunctionName);
}

void CacheXmlParser::startCacheListener(const xercesc::Attributes &attrs) {
  auto libraryName = getLibraryName(attrs);
  auto libraryFunctionName = getLibraryFunctionName(attrs);

  verifyFactoryFunction(managedCacheListenerFn_, libraryName,
                        libraryFunctionName);

  auto regionAttributesFactory =
      std::static_pointer_cast<RegionAttributesFactory>(_stack.top());
  regionAttributesFactory->setCacheListener(libraryName, libraryFunctionName);
}

void CacheXmlParser::startPartitionResolver(const xercesc::Attributes &attrs) {
  auto libraryName = getLibraryName(attrs);
  auto libraryFunctionName = getLibraryFunctionName(attrs);

  verifyFactoryFunction(managedPartitionResolverFn_, libraryName,
                        libraryFunctionName);

  auto regionAttributesFactory =
      std::static_pointer_cast<RegionAttributesFactory>(_stack.top());
  regionAttributesFactory->setPartitionResolver(libraryName,
                                                libraryFunctionName);
}

void CacheXmlParser::startCacheWriter(const xercesc::Attributes &attrs) {
  auto libraryName = getLibraryName(attrs);
  auto libraryFunctionName = getLibraryFunctionName(attrs);

  verifyFactoryFunction(managedCacheWriterFn_, libraryName,
                        libraryFunctionName);

  auto regionAttributesFactory =
      std::static_pointer_cast<RegionAttributesFactory>(_stack.top());
  regionAttributesFactory->setCacheWriter(libraryName, libraryFunctionName);
}

/**
 * After popping the current <code>RegionXmlCreation</code> off the
 * _stack, if the element on top of the _stack is a
 * <code>RegionXmlCreation</code>, then it is the parent region.
 */
void CacheXmlParser::endRegion() {
  auto isRoot = isRootLevel();
  auto regionPtr = std::static_pointer_cast<RegionXmlCreation>(_stack.top());
  _stack.pop();
  if (isRoot) {
    if (!_stack.empty()) {
      throw CacheXmlException("Xml file has incorrectly nested region tags");
    }
    if (!cacheCreation_) {
      throw CacheXmlException(
          "XML: Element <cache> was not provided in the xml");
    }

    cacheCreation_->addRootRegion(regionPtr);
  } else {
    if (_stack.empty()) {
      throw CacheXmlException("Xml file has incorrectly nested region tags");
    }
    auto parent = std::static_pointer_cast<RegionXmlCreation>(_stack.top());
    parent->addSubregion(regionPtr);
  }
  decNesting();
}

/**
 * When a <code>cache</code> element is finished
 */
void CacheXmlParser::endCache() {}

/**
 * When a <code>region-time-to-live</code> element is finished, the
 * {@link ExpirationAttributes} are on top of the _stack followed by
 * the {@link RegionAttributesFactory} to which the expiration
 * attributes are assigned.
 */
void CacheXmlParser::endRegionTimeToLive() {
  if (!flagExpirationAttribute_) {
    throw CacheXmlException(
        "XML: <region-time-to-live> cannot be without a "
        "<expiration-attributes>");
  }

  auto expireAttr =
      std::static_pointer_cast<ExpirationAttributes>(_stack.top());
  _stack.pop();

  auto regionAttributesFactory =
      std::static_pointer_cast<RegionAttributesFactory>(_stack.top());
  regionAttributesFactory->setRegionTimeToLive(expireAttr->getAction(),
                                               expireAttr->getTimeout());
  flagExpirationAttribute_ = false;
}

/**
 * When a <code>region-idle-time</code> element is finished, the
 * {@link ExpirationAttributes} are on top of the _stack followed by
 * the {@link RegionAttributesFactory} to which the expiration
 * attributes are assigned.
 */
void CacheXmlParser::endRegionIdleTime() {
  if (!flagExpirationAttribute_) {
    throw CacheXmlException(
        "XML: <region-idle-time> cannot be without <expiration-attributes>");
  }
  auto expireAttr =
      std::static_pointer_cast<ExpirationAttributes>(_stack.top());
  _stack.pop();
  auto regionAttributesFactory =
      std::static_pointer_cast<RegionAttributesFactory>(_stack.top());

  regionAttributesFactory->setRegionIdleTimeout(expireAttr->getAction(),
                                                expireAttr->getTimeout());
  flagExpirationAttribute_ = false;
}

/**
 * When a <code>entry-time-to-live</code> element is finished, the
 * {@link ExpirationAttributes} are on top of the _stack followed by
 * the {@link RegionAttributesFactory} to which the expiration
 * attributes are assigned.
 */
void CacheXmlParser::endEntryTimeToLive() {
  if (!flagExpirationAttribute_) {
    throw CacheXmlException(
        "XML: <entry-time-to-live> cannot be without <expiration-attributes>");
  }
  auto expireAttr =
      std::static_pointer_cast<ExpirationAttributes>(_stack.top());
  _stack.pop();
  auto regionAttributesFactory =
      std::static_pointer_cast<RegionAttributesFactory>(_stack.top());

  regionAttributesFactory->setEntryTimeToLive(expireAttr->getAction(),
                                              expireAttr->getTimeout());
  flagExpirationAttribute_ = false;
}

/**
 * When a <code>entry-idle-time</code> element is finished, the
 * {@link ExpirationAttributes} are on top of the _stack followed by
 * the {@link RegionAttributesFactory} to which the expiration
 * attributes are assigned.
 */
void CacheXmlParser::endEntryIdleTime() {
  if (!flagExpirationAttribute_) {
    throw CacheXmlException(
        "XML: <entry-idle-time> cannot be without <expiration-attributes>");
  }
  auto expireAttr =
      std::static_pointer_cast<ExpirationAttributes>(_stack.top());
  _stack.pop();
  auto regionAttributesFactory =
      std::static_pointer_cast<RegionAttributesFactory>(_stack.top());
  // TODO GEODE-3136: consider string parser here.
  regionAttributesFactory->setEntryIdleTimeout(expireAttr->getAction(),
                                               expireAttr->getTimeout());
  flagExpirationAttribute_ = false;
}

/**
 * When persistence-manager attributes is finished, it will set the attribute
 * factory.
 */
void CacheXmlParser::endPersistenceManager() {
  std::shared_ptr<std::string> libraryFunctionName =
      std::static_pointer_cast<std::string>(_stack.top());
  _stack.pop();
  std::shared_ptr<std::string> libraryName =
      std::static_pointer_cast<std::string>(_stack.top());
  _stack.pop();
  auto regionAttributesFactory =
      std::static_pointer_cast<RegionAttributesFactory>(_stack.top());
  if (config_ != nullptr) {
    regionAttributesFactory->setPersistenceManager(
        libraryName->c_str(), libraryFunctionName->c_str(), config_);
    config_ = nullptr;
  } else {
    regionAttributesFactory->setPersistenceManager(
        libraryName->c_str(), libraryFunctionName->c_str());
  }
}

CacheXmlParser::~CacheXmlParser() { _GEODE_SAFE_DELETE(cacheCreation_); }

}  // namespace client
}  // namespace geode
}  // namespace apache
