/**
   * Creates a new {@link org.ehcache.config.CacheConfigurationBuilder} seeded with the
   * cache-template configuration by the given {@code name} in the XML configuration parsed using
   * {@link #parseConfiguration()}
   *
   * @param name the unique name identifying the cache-template element in the XML
   * @param keyType the type of keys for the {@link org.ehcache.config.CacheConfigurationBuilder} to
   *     use, would need to match the {@code key-type} declared in the template if declared in XML
   * @param valueType the type of values for the {@link
   *     org.ehcache.config.CacheConfigurationBuilder} to use, would need to match the {@code
   *     value-type} declared in the template if declared in XML
   * @param <K> type of keys
   * @param <V> type of values
   * @return the preconfigured {@link org.ehcache.config.CacheConfigurationBuilder} or {@code null}
   *     if no cache-template for the provided {@code name}
   * @throws IllegalStateException if {@link #parseConfiguration()} hasn't yet been successfully
   *     invoked
   * @throws IllegalArgumentException if {@code keyType} or {@code valueType} don't match the
   *     declared type(s) of the template
   * @throws ClassNotFoundException if a {@link java.lang.Class} declared in the XML couldn't be
   *     found
   * @throws InstantiationException if a user provided {@link java.lang.Class} couldn't get
   *     instantiated
   * @throws IllegalAccessException if a method (including constructor) couldn't be invoked on a
   *     user provided type
   */
  @SuppressWarnings("unchecked")
  public <K, V> CacheConfigurationBuilder<K, V> newCacheConfigurationBuilderFromTemplate(
      final String name, final Class<K> keyType, final Class<V> valueType)
      throws InstantiationException, IllegalAccessException, ClassNotFoundException {

    final ConfigurationParser.CacheTemplate cacheTemplate = templates.get(name);
    if (cacheTemplate == null) {
      return null;
    }
    final ClassLoader defaultClassLoader = ClassLoading.getDefaultClassLoader();
    Class keyClass = getClassForName(cacheTemplate.keyType(), defaultClassLoader);
    Class valueClass = getClassForName(cacheTemplate.valueType(), defaultClassLoader);
    if (keyType != null && cacheTemplate.keyType() != null && !keyClass.isAssignableFrom(keyType)) {
      throw new IllegalArgumentException(
          "CacheTemplate '" + name + "' declares key type of " + cacheTemplate.keyType());
    }
    if (valueType != null
        && cacheTemplate.valueType() != null
        && !valueClass.isAssignableFrom(valueType)) {
      throw new IllegalArgumentException(
          "CacheTemplate '" + name + "' declares value type of " + cacheTemplate.valueType());
    }

    CacheConfigurationBuilder<K, V> builder =
        CacheConfigurationBuilder.newCacheConfigurationBuilder();
    builder =
        builder
            .usingEvictionPrioritizer(
                getInstanceOfName(
                    cacheTemplate.evictionPrioritizer(),
                    defaultClassLoader,
                    EvictionPrioritizer.class,
                    Eviction.Prioritizer.class))
            .evictionVeto(
                getInstanceOfName(
                    cacheTemplate.evictionVeto(), defaultClassLoader, EvictionVeto.class));
    final ConfigurationParser.Expiry parsedExpiry = cacheTemplate.expiry();
    if (parsedExpiry != null) {
      builder = builder.withExpiry(getExpiry(defaultClassLoader, parsedExpiry));
    }

    if (cacheTemplate.keySerializer() != null) {
      final Class<Serializer<?>> keySerializer =
          (Class<Serializer<?>>) getClassForName(cacheTemplate.keySerializer(), defaultClassLoader);
      builder =
          builder.add(
              new DefaultSerializerConfiguration(
                  keySerializer, DefaultSerializerConfiguration.Type.KEY));
    }
    if (cacheTemplate.valueSerializer() != null) {
      final Class<Serializer<?>> valueSerializer =
          (Class<Serializer<?>>)
              getClassForName(cacheTemplate.valueSerializer(), defaultClassLoader);
      builder =
          builder.add(
              new DefaultSerializerConfiguration(
                  valueSerializer, DefaultSerializerConfiguration.Type.VALUE));
    }
    final String loaderWriter = cacheTemplate.loaderWriter();
    if (loaderWriter != null) {
      final Class<CacheLoaderWriter<?, ?>> cacheLoaderWriterClass =
          (Class<CacheLoaderWriter<?, ?>>) getClassForName(loaderWriter, defaultClassLoader);
      builder = builder.add(new DefaultCacheLoaderWriterConfiguration(cacheLoaderWriterClass));
      if (cacheTemplate.writeBehind() != null) {
        WriteBehind writeBehind = cacheTemplate.writeBehind();
        WriteBehindConfigurationBuilder writeBehindConfigurationBuilder =
            WriteBehindConfigurationBuilder.newWriteBehindConfiguration()
                .concurrencyLevel(writeBehind.concurrency())
                .queueSize(writeBehind.maxQueueSize())
                .rateLimit(writeBehind.rateLimitPerSecond())
                .retry(writeBehind.retryAttempts(), writeBehind.retryAttemptsDelay())
                .delay(writeBehind.minWriteDelay(), writeBehind.maxWriteDelay());
        if (writeBehind.isBatched()) {
          writeBehindConfigurationBuilder =
              writeBehindConfigurationBuilder.batchSize(writeBehind.batchSize());
        }
        if (writeBehind.isCoalesced()) {
          writeBehindConfigurationBuilder = writeBehindConfigurationBuilder.enableCoalescing();
        }
        builder = builder.add(writeBehindConfigurationBuilder);
      }
    }
    if (cacheTemplate.listeners() != null) {
      for (ConfigurationParser.Listener listener : cacheTemplate.listeners()) {
        final Class<CacheEventListener<?, ?>> cacheEventListenerClass =
            (Class<CacheEventListener<?, ?>>)
                getClassForName(listener.className(), defaultClassLoader);
        final List<EventType> eventListToFireOn = listener.fireOn();
        Set<org.ehcache.event.EventType> eventSetToFireOn =
            new HashSet<org.ehcache.event.EventType>();
        for (EventType events : eventListToFireOn) {
          switch (events) {
            case CREATED:
              eventSetToFireOn.add(org.ehcache.event.EventType.CREATED);
              break;
            case EVICTED:
              eventSetToFireOn.add(org.ehcache.event.EventType.EVICTED);
              break;
            case EXPIRED:
              eventSetToFireOn.add(org.ehcache.event.EventType.EXPIRED);
              break;
            case UPDATED:
              eventSetToFireOn.add(org.ehcache.event.EventType.UPDATED);
              break;
            case REMOVED:
              eventSetToFireOn.add(org.ehcache.event.EventType.REMOVED);
              break;
            default:
              throw new IllegalArgumentException("Invalid Event Type provided");
          }
        }
        CacheEventListenerConfigurationBuilder listenerBuilder =
            CacheEventListenerConfigurationBuilder.newEventListenerConfiguration(
                    cacheEventListenerClass, eventSetToFireOn)
                .firingMode(EventFiring.valueOf(listener.eventFiring().value()))
                .eventOrdering(EventOrdering.valueOf(listener.eventOrdering().value()));
        builder = builder.add(listenerBuilder);
      }
    }
    ResourcePoolsBuilder resourcePoolsBuilder = newResourcePoolsBuilder();
    for (ResourcePool resourcePool : cacheTemplate.resourcePools()) {
      resourcePoolsBuilder =
          resourcePoolsBuilder.with(
              resourcePool.getType(),
              resourcePool.getSize(),
              resourcePool.getUnit(),
              resourcePool.isPersistent());
    }
    builder = builder.withResourcePools(resourcePoolsBuilder);
    for (ServiceConfiguration<?> serviceConfiguration : cacheTemplate.serviceConfigs()) {
      builder = builder.add(serviceConfiguration);
    }
    if (cacheTemplate.storeByValueOnHeap() != null) {
      final OnHeapStoreServiceConfiguration onHeapStoreServiceConfig =
          new OnHeapStoreServiceConfiguration();
      onHeapStoreServiceConfig.storeByValue(cacheTemplate.storeByValueOnHeap());
      builder = builder.add(onHeapStoreServiceConfig);
    }
    return builder;
  }
  @SuppressWarnings({"rawtypes", "unchecked"})
  private void parseConfiguration()
      throws ClassNotFoundException, IOException, SAXException, InstantiationException,
          IllegalAccessException {
    LOGGER.info("Loading Ehcache XML configuration from {}.", xml.getPath());
    ConfigurationParser configurationParser =
        new ConfigurationParser(xml.toExternalForm(), CORE_SCHEMA_URL);

    final ArrayList<ServiceCreationConfiguration<?>> serviceConfigs =
        new ArrayList<ServiceCreationConfiguration<?>>();

    for (ServiceType serviceType : configurationParser.getServiceElements()) {
      if (serviceType.getDefaultSerializers() != null) {
        DefaultSerializationProviderConfiguration configuration =
            new DefaultSerializationProviderConfiguration();

        for (SerializerType.Serializer serializer :
            serviceType.getDefaultSerializers().getSerializer()) {
          try {
            configuration.addSerializerFor(
                getClassForName(serializer.getType(), classLoader),
                (Class) getClassForName(serializer.getValue(), classLoader));
          } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
          }
        }
        serviceConfigs.add(configuration);
      } else if (serviceType.getPersistence() != null) {
        serviceConfigs.add(
            new CacheManagerPersistenceConfiguration(
                new File(serviceType.getPersistence().getDirectory())));
      } else {
        final ServiceCreationConfiguration<?> serviceConfiguration1 =
            configurationParser.parseExtension((Element) serviceType.getAny());
        serviceConfigs.add(serviceConfiguration1);
      }
    }

    for (ServiceCreationConfiguration<?> serviceConfiguration :
        Collections.unmodifiableList(serviceConfigs)) {
      serviceConfigurations.add(serviceConfiguration);
    }

    for (ConfigurationParser.CacheDefinition cacheDefinition :
        configurationParser.getCacheElements()) {
      CacheConfigurationBuilder<Object, Object> builder =
          CacheConfigurationBuilder.newCacheConfigurationBuilder();
      String alias = cacheDefinition.id();

      ClassLoader cacheClassLoader = cacheClassLoaders.get(alias);
      if (cacheClassLoader != null) {
        builder = builder.withClassLoader(cacheClassLoader);
      }

      if (cacheClassLoader == null) {
        if (classLoader != null) {
          cacheClassLoader = classLoader;
        } else {
          cacheClassLoader = ClassLoading.getDefaultClassLoader();
        }
      }

      Class keyType = getClassForName(cacheDefinition.keyType(), cacheClassLoader);
      Class valueType = getClassForName(cacheDefinition.valueType(), cacheClassLoader);
      if (cacheDefinition.keySerializer() != null) {
        Class keySerializer = getClassForName(cacheDefinition.keySerializer(), cacheClassLoader);
        builder =
            builder.add(
                new DefaultSerializerConfiguration(
                    keySerializer, DefaultSerializerConfiguration.Type.KEY));
      }
      if (cacheDefinition.valueSerializer() != null) {
        Class valueSerializer =
            getClassForName(cacheDefinition.valueSerializer(), cacheClassLoader);
        builder =
            builder.add(
                new DefaultSerializerConfiguration(
                    valueSerializer, DefaultSerializerConfiguration.Type.VALUE));
      }
      EvictionVeto evictionVeto =
          getInstanceOfName(cacheDefinition.evictionVeto(), cacheClassLoader, EvictionVeto.class);
      EvictionPrioritizer evictionPrioritizer =
          getInstanceOfName(
              cacheDefinition.evictionPrioritizer(),
              cacheClassLoader,
              EvictionPrioritizer.class,
              Eviction.Prioritizer.class);
      final ConfigurationParser.Expiry parsedExpiry = cacheDefinition.expiry();
      if (parsedExpiry != null) {
        builder = builder.withExpiry(getExpiry(cacheClassLoader, parsedExpiry));
      }
      ResourcePoolsBuilder resourcePoolsBuilder = newResourcePoolsBuilder();
      for (ResourcePool resourcePool : cacheDefinition.resourcePools()) {
        resourcePoolsBuilder =
            resourcePoolsBuilder.with(
                resourcePool.getType(),
                resourcePool.getSize(),
                resourcePool.getUnit(),
                resourcePool.isPersistent());
      }
      builder = builder.withResourcePools(resourcePoolsBuilder);
      for (ServiceConfiguration<?> serviceConfig : cacheDefinition.serviceConfigs()) {
        builder = builder.add(serviceConfig);
      }
      if (cacheDefinition.loaderWriter() != null) {
        final Class<CacheLoaderWriter<?, ?>> cacheLoaderWriterClass =
            (Class<CacheLoaderWriter<?, ?>>)
                getClassForName(cacheDefinition.loaderWriter(), cacheClassLoader);
        builder = builder.add(new DefaultCacheLoaderWriterConfiguration(cacheLoaderWriterClass));
        if (cacheDefinition.writeBehind() != null) {
          WriteBehind writeBehind = cacheDefinition.writeBehind();
          WriteBehindConfigurationBuilder writeBehindConfigurationBuilder =
              WriteBehindConfigurationBuilder.newWriteBehindConfiguration()
                  .concurrencyLevel(writeBehind.concurrency())
                  .queueSize(writeBehind.maxQueueSize())
                  .rateLimit(writeBehind.rateLimitPerSecond())
                  .retry(writeBehind.retryAttempts(), writeBehind.retryAttemptsDelay())
                  .delay(writeBehind.minWriteDelay(), writeBehind.maxWriteDelay());
          if (writeBehind.isBatched()) {
            writeBehindConfigurationBuilder =
                writeBehindConfigurationBuilder.batchSize(writeBehind.batchSize());
          }
          if (writeBehind.isCoalesced()) {
            writeBehindConfigurationBuilder = writeBehindConfigurationBuilder.enableCoalescing();
          }
          builder = builder.add(writeBehindConfigurationBuilder);
        }
      }
      if (cacheDefinition.listeners() != null) {
        for (ConfigurationParser.Listener listener : cacheDefinition.listeners()) {
          final Class<CacheEventListener<?, ?>> cacheEventListenerClass =
              (Class<CacheEventListener<?, ?>>)
                  getClassForName(listener.className(), cacheClassLoader);
          final List<EventType> eventListToFireOn = listener.fireOn();
          Set<org.ehcache.event.EventType> eventSetToFireOn =
              new HashSet<org.ehcache.event.EventType>();
          for (EventType events : eventListToFireOn) {
            switch (events) {
              case CREATED:
                eventSetToFireOn.add(org.ehcache.event.EventType.CREATED);
                break;
              case EVICTED:
                eventSetToFireOn.add(org.ehcache.event.EventType.EVICTED);
                break;
              case EXPIRED:
                eventSetToFireOn.add(org.ehcache.event.EventType.EXPIRED);
                break;
              case UPDATED:
                eventSetToFireOn.add(org.ehcache.event.EventType.UPDATED);
                break;
              case REMOVED:
                eventSetToFireOn.add(org.ehcache.event.EventType.REMOVED);
                break;
              default:
                throw new IllegalArgumentException("Invalid Event Type provided");
            }
          }
          CacheEventListenerConfigurationBuilder listenerBuilder =
              CacheEventListenerConfigurationBuilder.newEventListenerConfiguration(
                      cacheEventListenerClass, eventSetToFireOn)
                  .firingMode(EventFiring.valueOf(listener.eventFiring().value()))
                  .eventOrdering(EventOrdering.valueOf(listener.eventOrdering().value()));
          builder = builder.add(listenerBuilder);
        }
      }
      if (cacheDefinition.storeByValueOnHeap() != null) {
        final OnHeapStoreServiceConfiguration onHeapStoreServiceConfig =
            new OnHeapStoreServiceConfiguration();
        onHeapStoreServiceConfig.storeByValue(cacheDefinition.storeByValueOnHeap());
        builder = builder.add(onHeapStoreServiceConfig);
      }
      final CacheConfiguration<?, ?> config =
          builder.buildConfig(keyType, valueType, evictionVeto, evictionPrioritizer);
      cacheConfigurations.put(alias, config);
    }

    templates.putAll(configurationParser.getTemplates());
  }