/*
 * Copyright Terracotta, Inc.
 *
 * Licensed 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.ehcache.xml;

import org.ehcache.config.ResourcePool;
import org.ehcache.config.ResourceUnit;
import org.ehcache.config.SizedResourcePool;
import org.ehcache.config.units.EntryUnit;
import org.ehcache.config.units.MemoryUnit;
import org.ehcache.core.config.SizedResourcePoolImpl;
import org.ehcache.xml.exceptions.XmlConfigurationException;
import org.ehcache.xml.model.BaseCacheType;
import org.ehcache.xml.model.CacheLoaderWriterType;
import org.ehcache.xml.model.CacheTemplateType;
import org.ehcache.xml.model.CacheType;
import org.ehcache.xml.model.ConfigType;
import org.ehcache.xml.model.CopierType;
import org.ehcache.xml.model.Disk;
import org.ehcache.xml.model.DiskStoreSettingsType;
import org.ehcache.xml.model.EventFiringType;
import org.ehcache.xml.model.EventOrderingType;
import org.ehcache.xml.model.EventType;
import org.ehcache.xml.model.ExpiryType;
import org.ehcache.xml.model.Heap;
import org.ehcache.xml.model.ListenersType;
import org.ehcache.xml.model.MemoryType;
import org.ehcache.xml.model.ObjectFactory;
import org.ehcache.xml.model.Offheap;
import org.ehcache.xml.model.PersistableMemoryType;
import org.ehcache.xml.model.PersistenceType;
import org.ehcache.xml.model.ResourceType;
import org.ehcache.xml.model.ResourcesType;
import org.ehcache.xml.model.SerializerType;
import org.ehcache.xml.model.ServiceType;
import org.ehcache.xml.model.SizeofType;
import org.ehcache.xml.model.TimeType;
import org.ehcache.spi.service.ServiceConfiguration;
import org.ehcache.spi.service.ServiceCreationConfiguration;
import org.ehcache.core.internal.util.ClassLoading;
import org.w3c.dom.Element;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.SchemaFactory;

import java.io.IOException;
import java.math.BigInteger;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import org.ehcache.xml.model.ThreadPoolReferenceType;
import org.ehcache.xml.model.ThreadPoolsType;

import static java.util.Collections.emptySet;
import static java.util.Collections.singleton;

/**
 * Provides support for parsing a cache configuration expressed in XML.
 */
class ConfigurationParser {

  private static final SchemaFactory XSD_SCHEMA_FACTORY = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);

  private static final URL CORE_SCHEMA_URL = XmlConfiguration.class.getResource("/ehcache-core.xsd");
  private static final String CORE_SCHEMA_NAMESPACE = "http://www.ehcache.org/v3";
  private static final String CORE_SCHEMA_ROOT_ELEMENT = "config";
  private static final String CORE_SCHEMA_JAXB_MODEL_PACKAGE = ConfigType.class.getPackage().getName();

  private final Map<URI, CacheManagerServiceConfigurationParser<?>> xmlParsers = new HashMap<URI, CacheManagerServiceConfigurationParser<?>>();
  private final Map<URI, CacheServiceConfigurationParser<?>> cacheXmlParsers = new HashMap<URI, CacheServiceConfigurationParser<?>>();
  private final Unmarshaller unmarshaller;
  private final Map<URI, CacheResourceConfigurationParser> resourceXmlParsers = new HashMap<URI, CacheResourceConfigurationParser>();
  private final ConfigType config;

  public ConfigurationParser(String xml) throws IOException, SAXException, JAXBException, ParserConfigurationException {
    Collection<Source> schemaSources = new ArrayList<Source>();
    schemaSources.add(new StreamSource(CORE_SCHEMA_URL.openStream()));

    for (CacheManagerServiceConfigurationParser<?> parser : ClassLoading.libraryServiceLoaderFor(CacheManagerServiceConfigurationParser.class)) {
      schemaSources.add(parser.getXmlSchema());
      xmlParsers.put(parser.getNamespace(), parser);
    }
    for (CacheServiceConfigurationParser<?> parser : ClassLoading.libraryServiceLoaderFor(CacheServiceConfigurationParser.class)) {
      schemaSources.add(parser.getXmlSchema());
      cacheXmlParsers.put(parser.getNamespace(), parser);
    }
    // Parsers for /config/cache/resources extensions
    for (CacheResourceConfigurationParser parser : ClassLoading.libraryServiceLoaderFor(CacheResourceConfigurationParser.class)) {
      schemaSources.add(parser.getXmlSchema());
      resourceXmlParsers.put(parser.getNamespace(), parser);
    }

    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    factory.setNamespaceAware(true);
    factory.setIgnoringComments(true);
    factory.setIgnoringElementContentWhitespace(true);
    factory.setSchema(XSD_SCHEMA_FACTORY.newSchema(schemaSources.toArray(new Source[schemaSources.size()])));

    DocumentBuilder domBuilder = factory.newDocumentBuilder();
    domBuilder.setErrorHandler(new FatalErrorHandler());
    Element dom = domBuilder.parse(xml).getDocumentElement();
    if (!CORE_SCHEMA_ROOT_ELEMENT.equals(dom.getLocalName()) || !CORE_SCHEMA_NAMESPACE.equals(dom.getNamespaceURI())) {
      throw new XmlConfigurationException("Expecting {" + CORE_SCHEMA_NAMESPACE + "}" + CORE_SCHEMA_ROOT_ELEMENT
          + " element; found {" + dom.getNamespaceURI() + "}" + dom.getLocalName());
    }

    Class<ConfigType> configTypeClass = ConfigType.class;
    JAXBContext jc = JAXBContext.newInstance(CORE_SCHEMA_JAXB_MODEL_PACKAGE, configTypeClass.getClassLoader());
    this.unmarshaller = jc.createUnmarshaller();
    this.config = unmarshaller.unmarshal(dom, configTypeClass).getValue();
  }

  public Iterable<ServiceType> getServiceElements() {
    return config.getService();
  }

  public SerializerType getDefaultSerializers() {
    return config.getDefaultSerializers();
  }

  public CopierType getDefaultCopiers() {
    return config.getDefaultCopiers();
  }

  public PersistenceType getPersistence() {
    return config.getPersistence();
  }

  public ThreadPoolReferenceType getEventDispatch() {
    return config.getEventDispatch();
  }

  public ThreadPoolReferenceType getWriteBehind() {
    return config.getWriteBehind();
  }

  public ThreadPoolReferenceType getDiskStore() {
    return config.getDiskStore();
  }

  public ThreadPoolsType getThreadPools() {
    return config.getThreadPools();
  }

  public SizeOfEngineLimits getHeapStore() {
    SizeofType type = config.getHeapStore();
    return type == null ? null : new XmlSizeOfEngineLimits(type);
  }

  public Iterable<CacheDefinition> getCacheElements() {
    List<CacheDefinition> cacheCfgs = new ArrayList<CacheDefinition>();
    final List<BaseCacheType> cacheOrCacheTemplate = config.getCacheOrCacheTemplate();
    for (BaseCacheType baseCacheType : cacheOrCacheTemplate) {
      if(baseCacheType instanceof CacheType) {
        final CacheType cacheType = (CacheType)baseCacheType;

        final BaseCacheType[] sources;
        if(cacheType.getUsesTemplate() != null) {
          sources = new BaseCacheType[2];
          sources[0] = cacheType;
          sources[1] = (BaseCacheType) cacheType.getUsesTemplate();
        } else {
          sources = new BaseCacheType[1];
          sources[0] = cacheType;
        }

        cacheCfgs.add(new CacheDefinition() {
          @Override
          public String id() {
            return cacheType.getAlias();
          }

          @Override
          public String keyType() {
            String value = null;
            for (BaseCacheType source : sources) {
              value = source.getKeyType() != null ? source.getKeyType().getValue() : null;
              if (value != null) break;
            }
            if (value == null) {
              for (BaseCacheType source : sources) {
                value = JaxbHelper.findDefaultValue(source, "keyType");
                if (value != null) break;
              }
            }
            return value;
          }

          @Override
          public String keySerializer() {
            String value = null;
            for (BaseCacheType source : sources) {
              value = source.getKeyType() != null ? source.getKeyType().getSerializer() : null;
              if (value != null) break;
            }
            return value;
          }

          @Override
          public String keyCopier() {
            String value = null;
            for (BaseCacheType source : sources) {
              value = source.getKeyType() != null ? source.getKeyType().getCopier() : null;
              if (value != null) break;
            }
            return value;
          }

          @Override
          public String valueType() {
            String value = null;
            for (BaseCacheType source : sources) {
              value = source.getValueType() != null ? source.getValueType().getValue() : null;
              if (value != null) break;
            }
            if (value == null) {
              for (BaseCacheType source : sources) {
                value = JaxbHelper.findDefaultValue(source, "valueType");
                if (value != null) break;
              }
            }
            return value;
          }

          @Override
          public String valueSerializer() {
            String value = null;
            for (BaseCacheType source : sources) {
              value = source.getValueType() != null ? source.getValueType().getSerializer() : null;
              if (value != null) break;
            }
            return value;
          }

          @Override
          public String valueCopier() {
            String value = null;
            for (BaseCacheType source : sources) {
              value = source.getValueType() != null ? source.getValueType().getCopier() : null;
              if (value != null) break;
            }
            return value;
          }

          @Override
          public String evictionAdvisor() {
            String value = null;
            for (BaseCacheType source : sources) {
              value = source.getEvictionAdvisor();
              if (value != null) break;
            }
            return value;
          }

          @Override
          public Expiry expiry() {
            ExpiryType value = null;
            for (BaseCacheType source : sources) {
              value = source.getExpiry();
              if (value != null) break;
            }
            if (value != null) {
              return new XmlExpiry(value);
            } else {
              return null;
            }
          }

          @Override
          public String loaderWriter() {
            String configClass = null;
            for (BaseCacheType source : sources) {
              final CacheLoaderWriterType loaderWriter = source.getLoaderWriter();
              if (loaderWriter != null) {
                configClass = loaderWriter.getClazz();
                break;
              }
            }
            return configClass;
          }

          @Override
          public ListenersConfig listenersConfig() {
            ListenersType base = null;
            ArrayList<ListenersType> additionals = new ArrayList<ListenersType>();
            for (BaseCacheType source : sources) {
              if (source.getListeners() != null) {
                if (base == null) {
                  base = source.getListeners();
                } else {
                  additionals.add(source.getListeners());
                }
              }
            }
            return base != null ? new XmlListenersConfig(base, additionals.toArray(new ListenersType[0])) : null;
          }


          @Override
          public Iterable<ServiceConfiguration<?>> serviceConfigs() {
            Collection<ServiceConfiguration<?>> configs = new ArrayList<ServiceConfiguration<?>>();
            for (BaseCacheType source : sources) {
              for (Element child : source.getServiceConfiguration()) {
                configs.add(parseCacheExtension(child));
              }
            }
            return configs;
          }

          @Override
          public Collection<ResourcePool> resourcePools() {
            for (BaseCacheType source : sources) {
              Heap heapResource = source.getHeap();
              if (heapResource != null) {
                return singleton(parseResource(heapResource));
              } else {
                ResourcesType resources = source.getResources();
                if (resources != null) {
                  return parseResources(resources);
                }
              }
            }
            return emptySet();
          }

          @Override
          public WriteBehind writeBehind() {
            for (BaseCacheType source : sources) {
              final CacheLoaderWriterType loaderWriter = source.getLoaderWriter();
              final CacheLoaderWriterType.WriteBehind writebehind = loaderWriter != null ? loaderWriter.getWriteBehind() : null;
              if (writebehind != null) {
                return new XmlWriteBehind(writebehind);
              }
            }
            return null;
          }

          @Override
          public DiskStoreSettings diskStoreSettings() {
            DiskStoreSettingsType value = null;
            for (BaseCacheType source : sources) {
              value = source.getDiskStoreSettings();
              if (value != null) break;
            }
            if (value != null) {
              return new XmlDiskStoreSettings(value);
            } else {
              return null;
            }
          }

          @Override
          public SizeOfEngineLimits heapStoreSettings() {
            SizeofType sizeofType = null;
            for (BaseCacheType source : sources) {
              sizeofType = source.getHeapStoreSettings();
              if (sizeofType != null) break;
            }
            return sizeofType != null ? new XmlSizeOfEngineLimits(sizeofType) : null;
          }
        });
      }
    }

    return Collections.unmodifiableList(cacheCfgs);
  }

  public Map<String, CacheTemplate> getTemplates() {
    final Map<String, CacheTemplate> templates = new HashMap<String, CacheTemplate>();
    final List<BaseCacheType> cacheOrCacheTemplate = config.getCacheOrCacheTemplate();
    for (BaseCacheType baseCacheType : cacheOrCacheTemplate) {
      if (baseCacheType instanceof CacheTemplateType) {
        final CacheTemplateType cacheTemplate = (CacheTemplateType)baseCacheType;
        templates.put(cacheTemplate.getName(), new CacheTemplate() {

          @Override
          public String keyType() {
            String keyType = cacheTemplate.getKeyType() != null ? cacheTemplate.getKeyType().getValue() : null;
            if (keyType == null) {
              keyType = JaxbHelper.findDefaultValue(cacheTemplate, "keyType");
            }
            return keyType;
          }

          @Override
          public String keySerializer() {
            return cacheTemplate.getKeyType() != null ? cacheTemplate.getKeyType().getSerializer() : null;
          }

          @Override
          public String keyCopier() {
            return cacheTemplate.getKeyType() != null ? cacheTemplate.getKeyType().getCopier() : null;
          }

          @Override
          public String valueType() {
            String valueType = cacheTemplate.getValueType() != null ? cacheTemplate.getValueType().getValue() : null;
            if (valueType == null) {
              valueType = JaxbHelper.findDefaultValue(cacheTemplate, "valueType");
            }
            return valueType;
          }

          @Override
          public String valueSerializer() {
            return cacheTemplate.getValueType() != null ? cacheTemplate.getValueType().getSerializer() : null;
          }

          @Override
          public String valueCopier() {
            return cacheTemplate.getValueType() != null ? cacheTemplate.getValueType().getCopier() : null;
          }

          @Override
          public String evictionAdvisor() {
            return cacheTemplate.getEvictionAdvisor();
          }

          @Override
          public Expiry expiry() {
            ExpiryType cacheTemplateExpiry = cacheTemplate.getExpiry();
            if (cacheTemplateExpiry != null) {
              return new XmlExpiry(cacheTemplateExpiry);
            } else {
              return null;
            }
          }

          @Override
          public ListenersConfig listenersConfig() {
            final ListenersType integration = cacheTemplate.getListeners();
            return integration != null ? new XmlListenersConfig(integration) : null;
          }

          @Override
          public String loaderWriter() {
            final CacheLoaderWriterType loaderWriter = cacheTemplate.getLoaderWriter();
            return loaderWriter != null ? loaderWriter.getClazz() : null;
          }

          @Override
          public Iterable<ServiceConfiguration<?>> serviceConfigs() {
            Collection<ServiceConfiguration<?>> configs = new ArrayList<ServiceConfiguration<?>>();
            for (Element child : cacheTemplate.getServiceConfiguration()) {
              configs.add(parseCacheExtension(child));
            }
            return configs;
          }

          @Override
          public Collection<ResourcePool> resourcePools() {
            Heap heapResource = cacheTemplate.getHeap();
            if (heapResource != null) {
              return singleton(parseResource(heapResource));
            } else {
              ResourcesType resources = cacheTemplate.getResources();
              if (resources != null) {
                return parseResources(resources);
              }
            }

            return emptySet();
          }

          @Override
          public WriteBehind writeBehind() {
            final CacheLoaderWriterType loaderWriter = cacheTemplate.getLoaderWriter();
            final CacheLoaderWriterType.WriteBehind writebehind = loaderWriter != null ? loaderWriter.getWriteBehind(): null;
            return writebehind != null ? new XmlWriteBehind(writebehind) : null;
          }

          @Override
          public DiskStoreSettings diskStoreSettings() {
            final DiskStoreSettingsType diskStoreSettings = cacheTemplate.getDiskStoreSettings();
            return diskStoreSettings == null ? null : new XmlDiskStoreSettings(diskStoreSettings);
          }

          @Override
          public SizeOfEngineLimits heapStoreSettings() {
            SizeofType type = cacheTemplate.getHeapStoreSettings();
            return type == null ? null : new XmlSizeOfEngineLimits(type);
          }
        });
      }
    }
    return Collections.unmodifiableMap(templates);
  }

  private Collection<ResourcePool> parseResources(ResourcesType resources) {
    Collection<ResourcePool> resourcePools = new ArrayList<ResourcePool>();
    for (Element resource : resources.getResource()) {
      resourcePools.add(parseResource(resource));
    }
    return resourcePools;
  }

  private ResourcePool parseResource(Heap resource) {
    ResourceType heapResource = resource.getValue();
    return new SizedResourcePoolImpl<SizedResourcePool>(org.ehcache.config.ResourceType.Core.HEAP,
            heapResource.getValue().longValue(), parseUnit(heapResource), false);
  }

  private ResourcePool parseResource(Element element) {
    if (!CORE_SCHEMA_NAMESPACE.equals(element.getNamespaceURI())) {
      return parseResourceExtension(element);
    }
    try {
      Object resource = unmarshaller.unmarshal(element);
      if (resource instanceof Heap) {
        ResourceType heapResource = ((Heap) resource).getValue();
        return new SizedResourcePoolImpl<SizedResourcePool>(org.ehcache.config.ResourceType.Core.HEAP,
                heapResource.getValue().longValue(), parseUnit(heapResource), false);
      } else if (resource instanceof Offheap) {
        MemoryType offheapResource = ((Offheap) resource).getValue();
        return new SizedResourcePoolImpl<SizedResourcePool>(org.ehcache.config.ResourceType.Core.OFFHEAP,
                offheapResource.getValue().longValue(), parseMemory(offheapResource), false);
      } else if (resource instanceof Disk) {
        PersistableMemoryType diskResource = ((Disk) resource).getValue();
        return new SizedResourcePoolImpl<SizedResourcePool>(org.ehcache.config.ResourceType.Core.DISK,
                diskResource.getValue().longValue(), parseMemory(diskResource), diskResource.isPersistent());
      } else {
        // Someone updated the core resources without updating *this* code ...
        throw new AssertionError("Unrecognized resource: " + element + " / " + resource.getClass().getName());
      }
    } catch (JAXBException e) {
      throw new IllegalArgumentException("Can't find parser for resource: " + element, e);
    }
  }

  private static ResourceUnit parseUnit(ResourceType resourceType) {
    if (resourceType.getUnit().value().equalsIgnoreCase("entries")) {
      return EntryUnit.ENTRIES;
    } else {
      return MemoryUnit.valueOf(resourceType.getUnit().value().toUpperCase());
    }
  }

  private static MemoryUnit parseMemory(MemoryType memoryType) {
    return MemoryUnit.valueOf(memoryType.getUnit().value().toUpperCase());
  }

  ServiceCreationConfiguration<?> parseExtension(final Element element) {
    URI namespace = URI.create(element.getNamespaceURI());
    final CacheManagerServiceConfigurationParser<?> cacheManagerServiceConfigurationParser = xmlParsers.get(namespace);
    if(cacheManagerServiceConfigurationParser == null) {
      throw new IllegalArgumentException("Can't find parser for namespace: " + namespace);
    }
    return cacheManagerServiceConfigurationParser.parseServiceCreationConfiguration(element);
  }

  ServiceConfiguration<?> parseCacheExtension(final Element element) {
    URI namespace = URI.create(element.getNamespaceURI());
    final CacheServiceConfigurationParser<?> xmlConfigurationParser = cacheXmlParsers.get(namespace);
    if(xmlConfigurationParser == null) {
      throw new IllegalArgumentException("Can't find parser for namespace: " + namespace);
    }
    return xmlConfigurationParser.parseServiceConfiguration(element);
  }

  ResourcePool parseResourceExtension(final Element element) {
    URI namespace = URI.create(element.getNamespaceURI());
    final CacheResourceConfigurationParser xmlConfigurationParser = resourceXmlParsers.get(namespace);
    if (xmlConfigurationParser == null) {
      throw new XmlConfigurationException("Can't find parser for namespace: " + namespace);
    }
    return xmlConfigurationParser.parseResourceConfiguration(element);
  }

  static class FatalErrorHandler implements ErrorHandler {

    @Override
    public void warning(SAXParseException exception) throws SAXException {
      throw exception;
    }

    @Override
    public void error(SAXParseException exception) throws SAXException {
      throw exception;
    }

    @Override
    public void fatalError(SAXParseException exception) throws SAXException {
      throw exception;
    }
  }

  interface CacheTemplate {

    String keyType();

    String keySerializer();

    String keyCopier();

    String valueType();

    String valueSerializer();

    String valueCopier();

    String evictionAdvisor();

    Expiry expiry();

    String loaderWriter();

    ListenersConfig listenersConfig();

    Iterable<ServiceConfiguration<?>> serviceConfigs();

    Collection<ResourcePool> resourcePools();

    WriteBehind writeBehind();

    DiskStoreSettings diskStoreSettings();

    SizeOfEngineLimits heapStoreSettings();

  }

  interface CacheDefinition extends CacheTemplate {

    String id();

  }

  interface ListenersConfig {

    int dispatcherConcurrency();

    String threadPool();

    Iterable<Listener> listeners();
  }

  interface Listener {

    String className();

    EventFiringType eventFiring();

    EventOrderingType eventOrdering();

    List<EventType> fireOn();

  }

  interface Expiry {

    boolean isUserDef();

    boolean isTTI();

    boolean isTTL();

    String type();

    long value();

    TimeUnit unit();

  }

  interface WriteBehind {

    int maxQueueSize();

    int concurrency();

    String threadPool();

    Batching batching();
  }

  interface Batching {

    boolean isCoalesced();

    int batchSize();

    long maxDelay();

    TimeUnit maxDelayUnit();
  }

  interface DiskStoreSettings {

    int writerConcurrency();

    String threadPool();
  }


  interface SizeOfEngineLimits {

    long getMaxObjectGraphSize();

    long getMaxObjectSize();

    MemoryUnit getUnit();
  }

  private static class XmlListenersConfig implements ListenersConfig {

    final int dispatcherConcurrency;
    final String threadPool;
    final Iterable<Listener> listeners;

    private XmlListenersConfig(final ListenersType type, final ListenersType... others) {
      this.dispatcherConcurrency = type.getDispatcherConcurrency().intValue();
      String threadPool = type.getDispatcherThreadPool();
      Set<Listener> listenerSet = new HashSet<Listener>();
      final List<ListenersType.Listener> xmlListeners = type.getListener();
      extractListeners(listenerSet, xmlListeners);

      for (ListenersType other : others) {
        if (threadPool == null && other.getDispatcherThreadPool() != null) {
          threadPool = other.getDispatcherThreadPool();
        }
        extractListeners(listenerSet, other.getListener());
      }

      this.threadPool = threadPool;
      this.listeners = !listenerSet.isEmpty() ? listenerSet : null;
    }

    private void extractListeners(Set<Listener> listenerSet, List<ListenersType.Listener> xmlListeners) {
      if(xmlListeners != null) {
        for(final ListenersType.Listener listener : xmlListeners) {
          listenerSet.add(new Listener() {
            @Override
            public String className() {
              return listener.getClazz();
            }

            @Override
            public EventFiringType eventFiring() {
              return listener.getEventFiringMode();
            }

            @Override
            public EventOrderingType eventOrdering() {
              return listener.getEventOrderingMode();
            }

            @Override
            public List<EventType> fireOn() {
              return listener.getEventsToFireOn();
            }
          });
        }
      }
    }

    @Override
    public int dispatcherConcurrency() {
      return dispatcherConcurrency;
    }

    @Override
    public String threadPool() {
      return threadPool;
    }

    @Override
    public Iterable<Listener> listeners() {
      return listeners;
    }

  }

  private static class XmlExpiry implements Expiry {

    final ExpiryType type;

    private XmlExpiry(final ExpiryType type) {
      this.type = type;
    }

    @Override
    public boolean isUserDef() {
      return type != null && type.getClazz() != null;
    }

    @Override
    public boolean isTTI() {
      return type != null && type.getTti() != null;
    }

    @Override
    public boolean isTTL() {
      return type != null && type.getTtl() != null;
    }

    @Override
    public String type() {
      return type.getClazz();
    }

    @Override
    public long value() {
      final TimeType time;
      if(isTTI()) {
        time = type.getTti();
      } else {
        time = type.getTtl();
      }
      return time == null ? 0L : time.getValue().longValue();
    }

    @Override
    public TimeUnit unit() {
      final TimeType time;
      if(isTTI()) {
        time = type.getTti();
      } else {
        time = type.getTtl();
      }
      if(time != null) {
        return convertToJavaTimeUnit(time.getUnit());
      }
      return null;
    }
  }

  private static class XmlSizeOfEngineLimits implements SizeOfEngineLimits {

    private final SizeofType sizeoflimits;

    private XmlSizeOfEngineLimits(SizeofType sizeoflimits) {
      this.sizeoflimits = sizeoflimits;
    }

    @Override
    public long getMaxObjectGraphSize() {
      SizeofType.MaxObjectGraphSize value = sizeoflimits.getMaxObjectGraphSize();
      if (value == null) {
        return new BigInteger(JaxbHelper.findDefaultValue(sizeoflimits, "maxObjectGraphSize")).longValue();
      } else {
        return value.getValue().longValue();
      }
    }

    @Override
    public long getMaxObjectSize() {
      MemoryType value = sizeoflimits.getMaxObjectSize();
      if (value == null) {
        return new BigInteger(JaxbHelper.findDefaultValue(sizeoflimits, "maxObjectSize")).longValue();
      } else {
        return value.getValue().longValue();
      }
    }

    @Override
    public MemoryUnit getUnit() {
      MemoryType value = sizeoflimits.getMaxObjectSize();
      if (value == null) {
        return MemoryUnit.valueOf(new ObjectFactory().createMemoryType().getUnit().value().toUpperCase());
      } else {
        return MemoryUnit.valueOf(value.getUnit().value().toUpperCase());
      }
    }

  }

  private static class XmlWriteBehind implements WriteBehind {

    private final CacheLoaderWriterType.WriteBehind writebehind;

    private XmlWriteBehind(CacheLoaderWriterType.WriteBehind writebehind) {
      this.writebehind = writebehind;
    }

    @Override
    public int maxQueueSize() {
      return this.writebehind.getSize().intValue();
    }

    @Override
    public int concurrency() {
      return this.writebehind.getConcurrency().intValue() ;
    }

    @Override
    public String threadPool() {
      return this.writebehind.getThreadPool();
    }

    @Override
    public Batching batching() {
      CacheLoaderWriterType.WriteBehind.Batching batching = writebehind.getBatching();
      if (batching == null) {
        return null;
      } else {
        return new XmlBatching(batching);
      }
    }

  }

  private static class XmlBatching implements Batching {

    private final CacheLoaderWriterType.WriteBehind.Batching batching;

    private XmlBatching(CacheLoaderWriterType.WriteBehind.Batching batching) {
      this.batching = batching;
    }

    @Override
    public boolean isCoalesced() {
      return this.batching.isCoalesce();
    }

    @Override
    public int batchSize() {
      return this.batching.getBatchSize().intValue();
    }

    @Override
    public long maxDelay() {
      return this.batching.getMaxWriteDelay().getValue().longValue();
    }

    @Override
    public TimeUnit maxDelayUnit() {
      return convertToJavaTimeUnit(this.batching.getMaxWriteDelay().getUnit());
    }

  }

  private static class XmlDiskStoreSettings implements DiskStoreSettings {

    private final DiskStoreSettingsType diskStoreSettings;

    private XmlDiskStoreSettings(DiskStoreSettingsType diskStoreSettings) {
      this.diskStoreSettings = diskStoreSettings;
    }

    @Override
    public int writerConcurrency() {
      return this.diskStoreSettings.getWriterConcurrency().intValue();
    }

    @Override
    public String threadPool() {
      return this.diskStoreSettings.getThreadPool();
    }

  }

  private static TimeUnit convertToJavaTimeUnit(org.ehcache.xml.model.TimeUnit unit) {
    switch (unit) {
      case NANOS:
        return TimeUnit.NANOSECONDS;
      case MICROS:
      return TimeUnit.MICROSECONDS;
      case MILLIS:
        return TimeUnit.MILLISECONDS;
      case SECONDS:
        return TimeUnit.SECONDS;
      case MINUTES:
        return TimeUnit.MINUTES;
      case HOURS:
        return TimeUnit.HOURS;
      case DAYS:
        return TimeUnit.DAYS;
      default:
        throw new IllegalArgumentException("Unknown time unit: " + unit);
    }
  }
}
