Principles

The configuration derivation features allows a new Ehcache configuration object to be derived via a transformation on an existing configuration object. This can be useful for:

  • pre-processing an externally sourced configuration, adding additional settings before creating the cache manager.

  • processing the configuration of an existing cache manager to generate a new configuration.

The basis of the configuration derivation API is the Configuration.derive() method that generates a builder seeded with the configurations values

FluentConfigurationBuilder<?> derivedBuilder = configuration.derive(); (1)
Configuration configurationCopy = derivedBuilder.build(); (2)
1 Creates a builder seeded with the configuration’s state.
2 Configurations built using the builder are then functionally identical to the original configuration.

Core Configuration Changes

The configuration builder returned by the derive method provide direct methods for modifying core configuration concepts:

setting a custom classloader:
Configuration withClassLoader = configuration.derive()
  .withClassLoader(classLoader)
  .build();
adding a cache:
Configuration withCache = configuration.derive()
  .withCache("cache", CacheConfigurationBuilder.newCacheConfigurationBuilder(
    Long.class, String.class, ResourcePoolsBuilder.heap(10)))
  .build();
<ehcache>
</ehcache>

<ehcache>
  <cache alias="cache">
    <key-type>Long.class</key-type>
    <value-type>Object.class</value-type>
    <heap>10</heap>
  </cache>
</ehcache>
removing a cache:
Configuration withoutCache = configuration.derive()
  .withoutCache("cache")
  .build();
<ehcache>
  <cache alias="cache">
    <key-type>Long.class</key-type>
    <value-type>Object.class</value-type>
    <heap>10</heap>
  </cache>
</ehcache>

<ehcache>
</ehcache>

Updating a cache configuration uses a UnaryOperator that is run against a cache configuration builder seeded using the existing cache configuration.

updating a cache, by adding a resource:
Configuration withOffHeap = configuration.derive()
  .updateCache("cache", cache -> cache.updateResourcePools(
    resources -> ResourcePoolsBuilder.newResourcePoolsBuilder(resources)
      .offheap(100, MemoryUnit.MB)
      .build()))
  .build();
<ehcache>
  <cache alias="cache">
    <key-type>Long.class</key-type>
    <value-type>Object.class</value-type>
    <heap>10</heap>
  </cache>
</ehcache>

<ehcache>
  <cache alias="cache">
    <key-type>Long.class</key-type>
    <value-type>Object.class</value-type>
    <resources>
      <heap>10</heap>
      <offheap unit="MB">100</offheap>
    </resources>
  </cache>
</ehcache>

Extended Configuration Changes

Ehcache is a pluggable system, so modifying many of the more complex configurations requires modifying both service creation configurations and service configurations:

adding a service creation configuration (constraining the default thread pool)
Configuration withBoundedThreads = configuration.derive()
  .withService(new PooledExecutionServiceConfiguration()
    .addDefaultPool("default", 1, 16))
  .build();
<ehcache>
</ehcache>

<ehcache>
  <thread-pools>
    <thread-pool alias="default" default="true"
                 min-size="1" max-size="16"/>
  </thread-pools>
</ehcache>
updating a service creation configuration (changing the persistence path)
Configuration withUpdatedPersistence = configuration.derive()
  .updateServices(DefaultPersistenceConfiguration.class,
    existing -> new File("/var/persistence/path"))
  .build();
<ehcache>
  <persistence directory="/some/thing/else"/>
</ehcache>

<ehcache>
  <persistence directory="/var/persistence/path"/>
</ehcache>
adding a service configuration (setting a resilience strategy)
Configuration withThrowingStrategy = configuration.derive()
  .updateCache("cache", existing -> existing.withService(
    new DefaultResilienceStrategyConfiguration(new ThrowingResilienceStrategy<>())
  ))
  .build();
<ehcache>
  <cache alias="cache">
    <key-type>Long.class</key-type>
    <value-type>Object.class</value-type>
    <heap>10</heap>
  </cache>
</ehcache>

<ehcache>
  <cache alias="cache">
    <key-type>Long.class</key-type>
    <value-type>Object.class</value-type>
    <heap>10</heap>
    <resilience>
      com.example.ThrowingResilienceStrategy
    </resilience>
  </cache>
</ehcache>
updating a service configuration (changing a clustered cache’s consistency)
Configuration changedConsistency = configuration.derive()
  .updateCache("cache", cache -> cache.updateServices(
    ClusteredStoreConfiguration.class,
    existing -> Consistency.EVENTUAL)
  )
  .build();
<ehcache>
  <service>
    <tc:cluster>
      <tc:connection url="terracotta://example.com/cachemanager"/>
    </tc:cluster>
  </service>

  <cache alias="cache">
    <resources>
      <tc:clustered-dedicated unit="MB">50</tc:clustered-dedicated>
    </resources>
    <tc:clustered-store consistency="strong"/>
  </cache>
</ehcache>

<ehcache>
  <service>
    <tc:cluster>
      <tc:connection url="terracotta://example.com/cachemanager"/>
    </tc:cluster>
  </service>

  <cache alias="cache">
    <resources>
      <tc:clustered-dedicated unit="MB">50</tc:clustered-dedicated>
    </resources>
    <tc:clustered-store consistency="eventual"/>
  </cache>
</ehcache>

Removing a service

Removing a service often involves removing both service creation and a service configuration instances since a service instance its configuration are usually strongly coupled:

removing a service (making a cache manager non-clustered)
Configuration withoutClustering = configuration.derive()
  .updateCaches(cache -> cache (1)
    .withoutServices(ClusteredStoreConfiguration.class) (2)
    .updateResourcePools(existing -> {
      ResourcePoolsBuilder poolsBuilder = ResourcePoolsBuilder.newResourcePoolsBuilder(); (3)
      for (ResourcePool pool : existing.getResourceTypeSet().stream() (4)
        .filter(p -> !(p instanceof ClusteredResourceType)) (5)
        .map(existing::getPoolForResource)
        .toArray(ResourcePool[]::new)) {
        poolsBuilder = poolsBuilder.with(pool); (6)
      }
      return poolsBuilder.build();
    }))
  .withoutServices(ClusteringServiceConfiguration.class) (7)
  .build();
1 From all cache configurations…​
2 remove any existing ClusteredStoreConfiguration instances.
3 Create a new resource pool builder…​
4 From the existing resource pools…​
5 filter out any clustered resources.
6 Add all remaining pools to the new resource pools instance
7 Finally remove the clustering service creation configuration
<ehcache>
  <service>
    <tc:cluster>
      <tc:connection url="terracotta://example.com/cachemanager"/>
    </tc:cluster>
  </service>

  <cache alias="cache">
    <resources>
      <heap>100</heap>
      <tc:clustered-dedicated unit="MB">50</tc:clustered-dedicated>
    </resources>
    <tc:clustered-store consistency="strong"/>
  </cache>
</ehcache>

<ehcache>
  <cache alias="cache">
    <resources>
      <heap>100</heap>
    </resources>
  </cache>
</ehcache>