/*
 * 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.geode.internal.cache.xmlcache;

import static javax.xml.XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI;
import static org.apache.geode.internal.cache.xmlcache.XmlGeneratorUtils.addAttribute;
import static org.apache.geode.management.internal.configuration.utils.XmlConstants.W3C_XML_SCHEMA_INSTANCE_ATTRIBUTE_SCHEMA_LOCATION;
import static org.apache.geode.management.internal.configuration.utils.XmlConstants.W3C_XML_SCHEMA_INSTANCE_PREFIX;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.InetSocketAddress;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeSet;

import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.stream.StreamResult;

import org.apache.commons.lang3.exception.ExceptionUtils;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.DTDHandler;
import org.xml.sax.EntityResolver;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.AttributesImpl;

import org.apache.geode.CancelException;
import org.apache.geode.InternalGemFireException;
import org.apache.geode.annotations.Immutable;
import org.apache.geode.cache.AttributesFactory;
import org.apache.geode.cache.Cache;
import org.apache.geode.cache.CacheListener;
import org.apache.geode.cache.CacheTransactionManager;
import org.apache.geode.cache.CustomExpiry;
import org.apache.geode.cache.DataPolicy;
import org.apache.geode.cache.Declarable;
import org.apache.geode.cache.DiskStore;
import org.apache.geode.cache.DiskStoreFactory;
import org.apache.geode.cache.DiskWriteAttributes;
import org.apache.geode.cache.DynamicRegionFactory;
import org.apache.geode.cache.EvictionAction;
import org.apache.geode.cache.EvictionAlgorithm;
import org.apache.geode.cache.EvictionAttributes;
import org.apache.geode.cache.ExpirationAction;
import org.apache.geode.cache.ExpirationAttributes;
import org.apache.geode.cache.FixedPartitionAttributes;
import org.apache.geode.cache.InterestPolicy;
import org.apache.geode.cache.MembershipAttributes;
import org.apache.geode.cache.MirrorType;
import org.apache.geode.cache.PartitionAttributes;
import org.apache.geode.cache.PartitionAttributesFactory;
import org.apache.geode.cache.PartitionResolver;
import org.apache.geode.cache.Region;
import org.apache.geode.cache.RegionAttributes;
import org.apache.geode.cache.RegionShortcut;
import org.apache.geode.cache.Scope;
import org.apache.geode.cache.SubscriptionAttributes;
import org.apache.geode.cache.TransactionListener;
import org.apache.geode.cache.asyncqueue.AsyncEventListener;
import org.apache.geode.cache.asyncqueue.AsyncEventQueue;
import org.apache.geode.cache.client.ClientCache;
import org.apache.geode.cache.client.ClientRegionShortcut;
import org.apache.geode.cache.client.Pool;
import org.apache.geode.cache.client.PoolFactory;
import org.apache.geode.cache.client.PoolManager;
import org.apache.geode.cache.client.internal.PoolImpl;
import org.apache.geode.cache.execute.Function;
import org.apache.geode.cache.execute.FunctionService;
import org.apache.geode.cache.partition.PartitionListener;
import org.apache.geode.cache.query.Index;
import org.apache.geode.cache.query.internal.index.HashIndex;
import org.apache.geode.cache.query.internal.index.PrimaryKeyIndex;
import org.apache.geode.cache.server.CacheServer;
import org.apache.geode.cache.server.ServerLoadProbe;
import org.apache.geode.cache.util.ObjectSizer;
import org.apache.geode.cache.wan.GatewayEventFilter;
import org.apache.geode.cache.wan.GatewayEventSubstitutionFilter;
import org.apache.geode.cache.wan.GatewayReceiver;
import org.apache.geode.cache.wan.GatewaySender;
import org.apache.geode.cache.wan.GatewayTransportFilter;
import org.apache.geode.distributed.Role;
import org.apache.geode.internal.Assert;
import org.apache.geode.internal.cache.AbstractRegion;
import org.apache.geode.internal.cache.CacheConfig;
import org.apache.geode.internal.cache.ClientSubscriptionConfigImpl;
import org.apache.geode.internal.cache.ColocationHelper;
import org.apache.geode.internal.cache.DiskWriteAttributesImpl;
import org.apache.geode.internal.cache.GemFireCacheImpl;
import org.apache.geode.internal.cache.InternalCache;
import org.apache.geode.internal.cache.LocalRegion;
import org.apache.geode.internal.cache.PartitionAttributesImpl;
import org.apache.geode.internal.cache.PartitionedRegion;
import org.apache.geode.internal.cache.control.MemoryThresholds;
import org.apache.geode.internal.cache.extension.Extensible;
import org.apache.geode.internal.cache.extension.Extension;
import org.apache.geode.internal.cache.persistence.DefaultDiskDirs;
import org.apache.geode.internal.size.SizeClassOnceObjectSizer;
import org.apache.geode.management.internal.configuration.utils.XmlConstants;
import org.apache.geode.pdx.ReflectionBasedAutoSerializer;

/**
 * Generates a declarative XML file that describes a given {@link Cache} instance. This class was
 * developed for testing purposes, but it is conceivable that it could be used in the product as
 * well.
 *
 * @since GemFire 3.0
 */
@SuppressWarnings("deprecation")
public class CacheXmlGenerator extends CacheXml implements XMLReader {

  /** An empty <code>Attributes</code> */
  @Immutable
  private static final Attributes EMPTY = new AttributesImpl();

  /** The content handler to which SAX events are generated */
  private ContentHandler handler;

  /** The Cache that we're generating XML for */
  private final Cache cache;

  /**
   * Will the generated XML file reference an XML schema instead of the DTD?
   */
  private final boolean useSchema;
  private boolean includeKeysValues = true;
  private final boolean generateDefaults;

  /** The <code>CacheCreation</code> from which XML is generated */
  private final CacheCreation creation;

  /**
   * Examines the given <code>Cache</code> and from it generates XML data that is written to the
   * given <code>PrintWriter</code>. The schema/dtd for the current version of GemFire is used.
   *
   * @param useSchema Should the generated XML reference a schema (as opposed to a DTD)? For
   *        versions 8.1 or newer this should be true, otherwise false.
   * @param version The version of GemFire whose DTD/schema should be used in the generated XML. See
   *        {@link #VERSION_4_0}.
   *
   * @since GemFire 4.0
   */
  public static void generate(Cache cache, PrintWriter pw, boolean useSchema, String version) {
    new CacheXmlGenerator(cache, useSchema, version, true).generate(pw);
  }

  /**
   * Examines the given <code>Cache</code> and from it generates XML data that is written to the
   * given <code>PrintWriter</code>. The schema/dtd for the current version of GemFire is used.
   *
   * @param includeKeysValues true if the xml should include keys and values false otherwise
   */
  public static void generate(Cache cache, PrintWriter pw, boolean includeKeysValues) {
    new CacheXmlGenerator(cache, true, VERSION_LATEST, includeKeysValues).generate(pw);
  }

  /**
   * @param useSchema Should the generated XML reference a schema (as opposed to a DTD)? As of 8.1
   *        this value is ignored and always true.
   * @param includeDefaults set to false to cause generated xml to not have defaults values.
   * @deprecated Only used for rolling upgrades
   */
  @Deprecated
  public static void generate(Cache cache, PrintWriter pw, boolean useSchema,
      boolean includeKeysValues, boolean includeDefaults) {
    new CacheXmlGenerator(cache, true, VERSION_LATEST, includeKeysValues, includeDefaults)
        .generate(pw);
  }

  /**
   * @param includeDefaults set to false to cause generated xml to not have defaults values.
   */
  public static void generate(Cache cache, PrintWriter pw, boolean includeKeysValues,
      boolean includeDefaults) {
    new CacheXmlGenerator(cache, true, VERSION_LATEST, includeKeysValues, includeDefaults)
        .generate(pw);
  }

  /**
   * Examines the given <code>Cache</code> and from it generates XML data that is written to the
   * given <code>PrintWriter</code>.
   */
  public static void generate(Cache cache, PrintWriter pw) {
    new CacheXmlGenerator(cache, true, VERSION_LATEST, true).generate(pw);
  }

  /**
   * Examines the given <code>ClientCache</code> and from it generates XML data that is written to
   * the given <code>PrintWriter</code>. The schema/dtd for the current version of GemFire is used.
   *
   * @param useSchema Should the generated XML reference a schema (as opposed to a DTD)? For
   *        versions 8.1 or newer this should be true, otherwise false.
   * @param version The version of GemFire whose DTD/schema should be used in the generated XML. See
   *        {@link #VERSION_4_0}.
   *
   * @since GemFire 6.5
   */
  public static void generate(ClientCache cache, PrintWriter pw, boolean useSchema,
      String version) {
    new CacheXmlGenerator(cache, useSchema, version, true).generate(pw);
  }

  /**
   * Examines the given <code>ClientCache</code> and from it generates XML data that is written to
   * the given <code>PrintWriter</code>. The schema/dtd for the current version of GemFire is used.
   *
   * @param includeKeysValues true if the xml should include keys and values false otherwise
   */
  public static void generate(ClientCache cache, PrintWriter pw, boolean includeKeysValues) {
    new CacheXmlGenerator(cache, true, VERSION_LATEST, includeKeysValues).generate(pw);
  }

  /**
   * Examines the given <code>Cache</code> and from it generates XML data that is written to the
   * given <code>PrintWriter</code>.
   */
  public static void generate(ClientCache cache, PrintWriter pw) {
    new CacheXmlGenerator(cache, true, VERSION_LATEST, true).generate(pw);
  }

  /**
   * Writes a default cache.xml to pw.
   */
  public static void generateDefault(PrintWriter pw) {
    (new CacheXmlGenerator()).generate(pw);
  }

  /**
   * Creates a new <code>CacheXmlGenerator</code> that generates XML for a given <code>Cache</code>.
   */
  private CacheXmlGenerator(Cache cache, boolean useSchema, String version,
      boolean includeKeysValues) {
    this(cache, useSchema, version, includeKeysValues, true);
  }

  private CacheXmlGenerator(Cache cache, boolean useSchema, String version,
      boolean includeKeysValues, boolean generateDefaults) {
    this.cache = cache;
    this.useSchema = useSchema;
    this.version = CacheXmlVersion.valueForVersion(version);
    this.includeKeysValues = includeKeysValues;
    this.generateDefaults = generateDefaults;

    if (cache instanceof CacheCreation) {
      creation = (CacheCreation) cache;
      creation.startingGenerate();

    } else if (cache instanceof GemFireCacheImpl) {
      if (((InternalCache) cache).isClient()) {
        creation = new ClientCacheCreation();
        if (generateDefaults() || cache.getCopyOnRead()) {
          creation.setCopyOnRead(cache.getCopyOnRead());
        }
      } else {
        // if we are not generating defaults then create the CacheCreation for parsing
        // so that we can fetch the actual PoolManager and not a fake.
        creation = new CacheCreation(!generateDefaults);
        if (generateDefaults() || cache.getLockLease() != GemFireCacheImpl.DEFAULT_LOCK_LEASE) {
          creation.setLockLease(cache.getLockLease());
        }
        if (generateDefaults() || cache.getLockTimeout() != GemFireCacheImpl.DEFAULT_LOCK_TIMEOUT) {
          creation.setLockTimeout(cache.getLockTimeout());
        }
        if (generateDefaults()
            || cache.getSearchTimeout() != GemFireCacheImpl.DEFAULT_SEARCH_TIMEOUT) {
          creation.setSearchTimeout(cache.getSearchTimeout());
        }
        if (generateDefaults() || cache.isServer()) {
          creation.setIsServer(cache.isServer());
        }
        if (generateDefaults() || cache.getCopyOnRead()) {
          creation.setCopyOnRead(cache.getCopyOnRead());
        }
      }
    } else {
      // if we are not generating defaults then create the CacheCreation for parsing
      // so that we can fetch the actual PoolManager and not a fake.
      creation = new CacheCreation(!generateDefaults);
      if (generateDefaults() || cache.getLockLease() != GemFireCacheImpl.DEFAULT_LOCK_LEASE) {
        creation.setLockLease(cache.getLockLease());
      }
      if (generateDefaults() || cache.getLockTimeout() != GemFireCacheImpl.DEFAULT_LOCK_TIMEOUT) {
        creation.setLockTimeout(cache.getLockTimeout());
      }
      if (generateDefaults()
          || cache.getSearchTimeout() != GemFireCacheImpl.DEFAULT_SEARCH_TIMEOUT) {
        creation.setSearchTimeout(cache.getSearchTimeout());
      }
      if (generateDefaults() || cache.isServer()) {
        creation.setIsServer(cache.isServer());
      }
      if (generateDefaults() || cache.getCopyOnRead()) {
        creation.setCopyOnRead(cache.getCopyOnRead());
      }
    }
  }

  /**
   * Creates a new <code>CacheXmlGenerator</code> that generates XML for a given
   * <code>ClientCache</code>.
   */
  private CacheXmlGenerator(ClientCache cache, boolean useSchema, String version,
      boolean includeKeysValues) {
    this.cache = (Cache) cache;
    this.useSchema = useSchema;
    this.version = CacheXmlVersion.valueForVersion(version);
    this.includeKeysValues = includeKeysValues;

    generateDefaults = true;

    if (cache instanceof ClientCacheCreation) {
      creation = (ClientCacheCreation) cache;
      creation.startingGenerate();
    } else {
      creation = new ClientCacheCreation();
      if (cache.getCopyOnRead()) {
        creation.setCopyOnRead(cache.getCopyOnRead());
      }
    }
  }

  /**
   * return true if default values should be generated.
   */
  private boolean generateDefaults() {
    return generateDefaults;
  }

  /**
   * Creates a generator for a default cache.
   */
  private CacheXmlGenerator() {
    cache = null;
    useSchema = true;
    version = CacheXmlVersion.valueForVersion(VERSION_LATEST);
    generateDefaults = true;

    creation = new CacheCreation();
    creation.setLockLease(GemFireCacheImpl.DEFAULT_LOCK_LEASE);
    creation.setLockTimeout(GemFireCacheImpl.DEFAULT_LOCK_TIMEOUT);
    creation.setSearchTimeout(GemFireCacheImpl.DEFAULT_SEARCH_TIMEOUT);
    // No cache proxy
    creation.setIsServer(false);
    creation.setCopyOnRead(GemFireCacheImpl.DEFAULT_COPY_ON_READ);
  }

  /**
   * Writes the generator's state to pw
   */
  private void generate(PrintWriter pw) {
    // Use JAXP's transformation API to turn SAX events into pretty
    // XML text
    try {
      Source src = new SAXSource(this, new InputSource());
      Result res = new StreamResult(pw);

      TransformerFactory xFactory = TransformerFactory.newInstance();
      Transformer xform = xFactory.newTransformer();
      xform.setOutputProperty(OutputKeys.METHOD, "xml");
      xform.setOutputProperty(OutputKeys.INDENT, "yes");
      if (!useSchema) {
        // set the doctype system and public ids from version for older DTDs.
        xform.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, version.getSystemId());
        xform.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC, version.getPublicId());
      }
      xform.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
      xform.transform(src, res);
      pw.flush();

    } catch (Exception ex) {
      if (ExceptionUtils.getRootCause(ex) instanceof CancelException) {
        throw (CancelException) ExceptionUtils.getRootCause(ex);
      }
      throw new RuntimeException("An Exception was thrown while generating XML.", ex);
    }
  }

  /**
   * Called by the transformer to parse the "input source". We ignore the input source and, instead,
   * generate SAX events to the {@link #setContentHandler ContentHandler}.
   */
  @Override
  public void parse(InputSource input) throws SAXException {
    Assert.assertTrue(handler != null);

    boolean isClientCache = creation instanceof ClientCacheCreation;

    handler.startDocument();

    AttributesImpl atts = new AttributesImpl();
    if (useSchema) {
      if (null == version.getSchemaLocation()) {
        throw new IllegalStateException("No schema for version " + version.getVersion());
      }
      // add schema location for cache schema.
      handler.startPrefixMapping(W3C_XML_SCHEMA_INSTANCE_PREFIX, W3C_XML_SCHEMA_INSTANCE_NS_URI);
      addAttribute(atts, W3C_XML_SCHEMA_INSTANCE_PREFIX,
          W3C_XML_SCHEMA_INSTANCE_ATTRIBUTE_SCHEMA_LOCATION,
          version.getNamespace() + " " + version.getSchemaLocation());

      // add cache schema to default prefix.
      handler.startPrefixMapping(XmlConstants.DEFAULT_PREFIX, version.getNamespace());
      addAttribute(atts, VERSION, version.getVersion());
    }

    // Don't generate XML for attributes that are not set.

    if (creation.hasLockLease()) {
      atts.addAttribute("", "", LOCK_LEASE, "", String.valueOf(creation.getLockLease()));
    }
    if (creation.hasLockTimeout()) {
      atts.addAttribute("", "", LOCK_TIMEOUT, "", String.valueOf(creation.getLockTimeout()));
    }
    if (creation.hasSearchTimeout()) {
      atts.addAttribute("", "", SEARCH_TIMEOUT, "", String.valueOf(creation.getSearchTimeout()));
    }
    if (version.compareTo(CacheXmlVersion.GEMFIRE_5_1) >= 0) {
      if (creation.hasMessageSyncInterval()) {
        atts.addAttribute("", "", MESSAGE_SYNC_INTERVAL, "",
            String.valueOf(creation.getMessageSyncInterval()));
      }
    }
    if (version.compareTo(CacheXmlVersion.GEMFIRE_4_0) >= 0) {
      if (creation.hasServer()) {
        atts.addAttribute("", "", IS_SERVER, "", String.valueOf(creation.isServer()));
      }
      if (creation.hasCopyOnRead()) {
        atts.addAttribute("", "", COPY_ON_READ, "", String.valueOf(creation.getCopyOnRead()));
      }
    }
    if (isClientCache) {
      handler.startElement("", CLIENT_CACHE, CLIENT_CACHE, atts);
    } else {
      handler.startElement("", CACHE, CACHE, atts);
    }
    if (cache != null) {
      if (!isClientCache) {
        generate(cache.getCacheTransactionManager());
      } else if (version.compareTo(CacheXmlVersion.GEMFIRE_6_6) >= 0) {
        generate(cache.getCacheTransactionManager());
      }

      generateDynamicRegionFactory(cache);

      if (!isClientCache) {
        if (version.compareTo(CacheXmlVersion.GEMFIRE_7_0) >= 0) {
          Set<GatewaySender> senderSet = cache.getGatewaySenders();
          for (GatewaySender sender : senderSet) {
            generateGatewaySender(sender);
          }
          generateGatewayReceiver(cache);
          generateAsyncEventQueue(cache);
        }
      }

      if (!isClientCache && version.compareTo(CacheXmlVersion.GEMFIRE_7_0) >= 0) {
        if (cache.getGatewayConflictResolver() != null) {
          generate(GATEWAY_CONFLICT_RESOLVER, cache.getGatewayConflictResolver());
        }
      }

      if (!isClientCache) {
        for (CacheServer bridge : cache.getCacheServers()) {
          generate(bridge);
        }
      }

      if (version.compareTo(CacheXmlVersion.GEMFIRE_5_7) >= 0) {
        Iterator pools;
        if (cache instanceof GemFireCacheImpl) {
          pools = PoolManager.getAll().values().iterator();
        } else {
          pools = creation.getPools().values().iterator();
        }
        while (pools.hasNext()) {
          Pool cp = (Pool) pools.next();
          generate(cp);
        }
      }

      if (version.compareTo(CacheXmlVersion.GEMFIRE_6_5) >= 0) {
        if (cache instanceof GemFireCacheImpl) {
          InternalCache gfc = (InternalCache) cache;
          for (DiskStore ds : gfc.listDiskStores()) {
            generate(ds);
          }
        } else {
          for (DiskStore ds : creation.listDiskStores()) {
            generate(ds);
          }
        }
      }
      if (version.compareTo(CacheXmlVersion.GEMFIRE_6_6) >= 0) {
        generatePdx();
      }

      if (version.compareTo(CacheXmlVersion.GEMFIRE_4_1) >= 0) {
        Map namedAttributes = cache.listRegionAttributes();
        for (Object o : namedAttributes.entrySet()) {
          Map.Entry entry = (Map.Entry) o;
          String id = (String) entry.getKey();
          RegionAttributes attrs = (RegionAttributes) entry.getValue();
          // Since CacheCreation predefines these even in later versions
          // we need to exclude them in all versions.
          // It would be better if CacheCreation could only predefine them
          // for versions 6.5 and later but that is not easy to do
          {
            if (creation instanceof ClientCacheCreation) {
              try {
                ClientRegionShortcut.valueOf(id);
                // skip this client region since id mapped to one of the enum types
                continue;
              } catch (IllegalArgumentException ignore) {
                // id is not a shortcut so go ahead and call generate
              }
            } else {
              try {
                RegionShortcut.valueOf(id);
                // skip this region since id mapped to one of the enum types
                continue;
              } catch (IllegalArgumentException ignore) {
                // id is not a shortcut so go ahead and call generate
              }
            }
          }
          generate(id, attrs);
        }
      }

      if (cache instanceof GemFireCacheImpl) {
        generateRegions();
      } else {
        TreeSet<Region<?, ?>> rSet = new TreeSet<>(new RegionComparator());
        rSet.addAll(cache.rootRegions());
        for (Region<?, ?> region : rSet) {
          generateRegion(region);
        }
      }

      if (version.compareTo(CacheXmlVersion.GEMFIRE_5_8) >= 0) {
        generateFunctionService();
      }
      if (version.compareTo(CacheXmlVersion.GEMFIRE_6_0) >= 0) {
        generateResourceManager();
        generateSerializerRegistration();
      }
      if (!isClientCache) {
        if (version.compareTo(CacheXmlVersion.GEMFIRE_6_5) >= 0) {
          if (cache instanceof GemFireCacheImpl) {
            InternalCache internalCache = (InternalCache) cache;
            for (File file : internalCache.getBackupFiles()) {
              generateBackupFile(file);
            }
          } else {
            for (File file : creation.getBackupFiles()) {
              generateBackupFile(file);
            }
          }
        }
      }
      if (version.compareTo(CacheXmlVersion.GEMFIRE_6_6) >= 0) {
        generateInitializer();
      }
    }

    if (cache instanceof Extensible) {
      @SuppressWarnings("unchecked")
      final Extensible<Cache> extensible = (Extensible<Cache>) cache;
      generate(extensible);
    }

    if (isClientCache) {
      handler.endElement("", CLIENT_CACHE, CLIENT_CACHE);
    } else {
      handler.endElement("", CACHE, CACHE);
    }
    handler.endDocument();
  }

  private void generatePdx() throws SAXException {
    AttributesImpl atts = new AttributesImpl();
    CacheConfig config = ((InternalCache) cache).getCacheConfig();
    if (config.pdxReadSerializedUserSet) {
      if (generateDefaults() || cache.getPdxReadSerialized()) {
        atts.addAttribute("", "", READ_SERIALIZED, "",
            Boolean.toString(cache.getPdxReadSerialized()));
      }
    }
    if (config.pdxIgnoreUnreadFieldsUserSet) {
      if (generateDefaults() || cache.getPdxIgnoreUnreadFields()) {
        atts.addAttribute("", "", IGNORE_UNREAD_FIELDS, "",
            Boolean.toString(cache.getPdxIgnoreUnreadFields()));
      }
    }
    if (config.pdxPersistentUserSet) {
      if (generateDefaults() || cache.getPdxPersistent()) {
        atts.addAttribute("", "", PERSISTENT, "", Boolean.toString(cache.getPdxPersistent()));
      }
    }
    if (config.pdxDiskStoreUserSet) {
      if (generateDefaults()
          || cache.getPdxDiskStore() != null && !cache.getPdxDiskStore().equals("")) {
        atts.addAttribute("", "", DISK_STORE_NAME, "", cache.getPdxDiskStore());
      }
    }
    if (!generateDefaults() && cache.getPdxSerializer() == null && atts.getLength() == 0) {
      return;
    }
    handler.startElement("", PDX, PDX, atts);

    if (cache.getPdxSerializer() != null) {
      generate(PDX_SERIALIZER, cache.getPdxSerializer());
    }
    handler.endElement("", PDX, PDX);
  }

  private void generateInitializer() throws SAXException {
    if (cache.getInitializer() != null) {
      generate(cache.getInitializer(), cache.getInitializerProps());
    }
  }

  private void generateRegion(Region root) throws SAXException {
    if (version.compareTo(CacheXmlVersion.GEMFIRE_5_0) >= 0) {
      generate(root, REGION);
    } else {
      generate(root, VM_ROOT_REGION);
    }
  }

  private void generateRegions() throws SAXException {
    Set<Region<?, ?>> colocatedChildRegions = new HashSet<>();
    Set<Region> generatedRegions = new HashSet<>();

    // Merge from persist_Nov10 - iterate the regions in order for persistent recovery.
    TreeSet<Region<?, ?>> rSet = new TreeSet<>(new RegionComparator());
    rSet.addAll(cache.rootRegions());
    for (Region<?, ?> root : rSet) {
      Assert.assertTrue(root instanceof LocalRegion);
      if (root instanceof PartitionedRegion) {
        PartitionedRegion pr = (PartitionedRegion) root;
        if (pr.getColocatedWith() != null) {
          colocatedChildRegions.add(root);
        } else {
          generateRegion(root); // normal PR or root in colocated chain
          generatedRegions.add(root);
        }
      } else {
        // normal non pr regions, but they can have PR as subregions
        boolean found = false;
        for (Region<?, ?> subregion : root.subregions(false)) {
          Assert.assertTrue(subregion instanceof LocalRegion);
          if (subregion instanceof PartitionedRegion) {
            PartitionedRegion pr = (PartitionedRegion) subregion;
            if (pr.getColocatedWith() != null) {
              colocatedChildRegions.add(root);
              found = true;
              break;
            }
          }
        }
        if (!found) {
          generateRegion(root); // normal non pr regions
          generatedRegions.add(root);
        }
      }
    }
    TreeSet<Region<?, ?>> rColSet = new TreeSet<>(new RegionComparator());
    rColSet.addAll(colocatedChildRegions);
    for (Region<?, ?> root : rColSet) {
      Assert.assertTrue(root instanceof LocalRegion);
      if (root instanceof PartitionedRegion) {
        PartitionedRegion pr = (PartitionedRegion) root;
        PartitionedRegion colocatedWithPr = ColocationHelper.getColocatedRegion(pr);
        if (colocatedWithPr != null && !generatedRegions.contains(colocatedWithPr)) {
          generateRegion(colocatedWithPr);
          generatedRegions.add(colocatedWithPr);
        }
        if (!generatedRegions.contains(root)) {
          generateRegion(root);
          generatedRegions.add(root);
        }
      } else {
        generateRegion(root);
        generatedRegions.add(root);
      }
    }
  }

  /**
   * Generate a resource-manager element
   */
  private void generateResourceManager() throws SAXException {
    AttributesImpl atts = new AttributesImpl();
    if (cache instanceof CacheCreation && creation.hasResourceManager()) {
      boolean generateIt = false;
      if (creation.getResourceManager().hasCriticalHeap()) {
        float chp = creation.getResourceManager().getCriticalHeapPercentage();
        if (generateDefaults() || chp != MemoryThresholds.DEFAULT_CRITICAL_PERCENTAGE) {
          atts.addAttribute("", "", CRITICAL_HEAP_PERCENTAGE, "", String.valueOf(chp));
          generateIt = true;
        }
      }
      if (creation.getResourceManager().hasEvictionHeap()) {
        float ehp = creation.getResourceManager().getEvictionHeapPercentage();
        if (generateDefaults() || ehp != MemoryThresholds.DEFAULT_EVICTION_PERCENTAGE) {
          atts.addAttribute("", "", EVICTION_HEAP_PERCENTAGE, "", String.valueOf(ehp));
          generateIt = true;
        }
      }

      if (version.compareTo(CacheXmlVersion.GEODE_1_0) >= 0) {
        if (creation.getResourceManager().hasCriticalOffHeap()) {
          float chp = creation.getResourceManager().getCriticalOffHeapPercentage();
          if (generateDefaults() || chp != MemoryThresholds.DEFAULT_CRITICAL_PERCENTAGE) {
            atts.addAttribute("", "", CRITICAL_OFF_HEAP_PERCENTAGE, "", String.valueOf(chp));
            generateIt = true;
          }
        }
        if (creation.getResourceManager().hasEvictionOffHeap()) {
          float ehp = creation.getResourceManager().getEvictionOffHeapPercentage();
          if (generateDefaults() || ehp != MemoryThresholds.DEFAULT_EVICTION_PERCENTAGE) {
            atts.addAttribute("", "", EVICTION_OFF_HEAP_PERCENTAGE, "", String.valueOf(ehp));
            generateIt = true;
          }
        }
      }
      if (generateIt) {
        generateResourceManagerElement(atts);
      }
    } else if (cache instanceof GemFireCacheImpl) {
      {
        int chp = (int) cache.getResourceManager().getCriticalHeapPercentage();
        if (generateDefaults() || chp != MemoryThresholds.DEFAULT_CRITICAL_PERCENTAGE) {
          atts.addAttribute("", "", CRITICAL_HEAP_PERCENTAGE, "", String.valueOf(chp));
        }
      }
      {
        int ehp = (int) cache.getResourceManager().getEvictionHeapPercentage();
        if (generateDefaults() || ehp != MemoryThresholds.DEFAULT_EVICTION_PERCENTAGE) {
          atts.addAttribute("", "", EVICTION_HEAP_PERCENTAGE, "", String.valueOf(ehp));
        }
      }

      if (version.compareTo(CacheXmlVersion.GEODE_1_0) >= 0) {
        {
          int chp = (int) cache.getResourceManager().getCriticalOffHeapPercentage();
          if (generateDefaults() || chp != MemoryThresholds.DEFAULT_CRITICAL_PERCENTAGE) {
            atts.addAttribute("", "", CRITICAL_OFF_HEAP_PERCENTAGE, "", String.valueOf(chp));
          }
        }
        {
          int ehp = (int) cache.getResourceManager().getEvictionOffHeapPercentage();
          if (generateDefaults() || ehp != MemoryThresholds.DEFAULT_EVICTION_PERCENTAGE) {
            atts.addAttribute("", "", EVICTION_OFF_HEAP_PERCENTAGE, "", String.valueOf(ehp));
          }
        }
      }
      if (generateDefaults() || atts.getLength() > 0) {
        generateResourceManagerElement(atts);
      }
    }
  }

  private void generateResourceManagerElement(AttributesImpl atts) throws SAXException {
    handler.startElement("", RESOURCE_MANAGER, RESOURCE_MANAGER, atts);
    handler.endElement("", RESOURCE_MANAGER, RESOURCE_MANAGER);
  }

  private void generateBackupFile(File file) throws SAXException {
    handler.startElement("", BACKUP, BACKUP, EMPTY);
    handler.characters(file.getPath().toCharArray(), 0, file.getPath().length());
    handler.endElement("", BACKUP, BACKUP);
  }

  /**
   * Generates the <code>serializer-registration</code> element.
   *
   */
  private void generateSerializerRegistration() throws SAXException {
    final SerializerCreation sc = creation.getSerializerCreation();
    if (sc == null) {
      return;
    }

    handler.startElement("", TOP_SERIALIZER_REGISTRATION, TOP_SERIALIZER_REGISTRATION, EMPTY);
    for (Class c : sc.getSerializerRegistrations()) {
      handler.startElement("", SERIALIZER_REGISTRATION, SERIALIZER_REGISTRATION, EMPTY);
      handler.startElement("", CLASS_NAME, CLASS_NAME, EMPTY);
      handler.characters(c.getName().toCharArray(), 0, c.getName().length());
      handler.endElement("", CLASS_NAME, CLASS_NAME);
