Repository: mageddo/dns-proxy-server
Pull request #377 (2023-03-19 03:15:29)

Smarter Cache for loading with Docker and LocalDB Solvers
Relates to #376 


Patch:
diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md
index fa9f8a9d..e6899c95 100644
--- a/RELEASE-NOTES.md
+++ b/RELEASE-NOTES.md
@@ -1,5 +1,6 @@
-## 3.14.0
+## 3.14.1
 * Support for regex on localdb solver and docker container hostname and HOSTNAMES env
+* Smarter Cache for loading with Docker and LocalDB Solvers see #376
 
 ## 3.13.1
 * Caching remote solved hostnames for 5 minutes
diff --git a/gradle.properties b/gradle.properties
index 09670f26..8ec10bc5 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,1 +1,1 @@
-version=3.14.0-snapshot
+version=3.14.1-snapshot
diff --git a/src/main/java/com/mageddo/dnsproxyserver/di/module/ModuleSolver.java b/src/main/java/com/mageddo/dnsproxyserver/di/module/ModuleSolver.java
index aad35009..56154fb1 100644
--- a/src/main/java/com/mageddo/dnsproxyserver/di/module/ModuleSolver.java
+++ b/src/main/java/com/mageddo/dnsproxyserver/di/module/ModuleSolver.java
@@ -1,9 +1,12 @@
 package com.mageddo.dnsproxyserver.di.module;
 
+import com.mageddo.dnsproxyserver.server.dns.solver.CacheName;
 import com.mageddo.dnsproxyserver.server.dns.solver.Solver;
+import com.mageddo.dnsproxyserver.server.dns.solver.SolverCache;
+import com.mageddo.dnsproxyserver.server.dns.solver.CacheName.Name;
+import com.mageddo.dnsproxyserver.server.dns.solver.SolverCachedRemote;
 import com.mageddo.dnsproxyserver.server.dns.solver.SolverDocker;
 import com.mageddo.dnsproxyserver.server.dns.solver.SolverLocalDB;
-import com.mageddo.dnsproxyserver.server.dns.solver.SolverRemote;
 import com.mageddo.dnsproxyserver.server.dns.solver.SolverSystem;
 import dagger.Module;
 import dagger.Provides;
@@ -14,12 +17,27 @@ import java.util.Set;
 
 @Module
 public interface ModuleSolver {
+
   @Provides
   @Singleton
   @ElementsIntoSet
   static Set<Solver> solvers(
-      SolverDocker o1, SolverRemote o3, SolverLocalDB o4, SolverSystem o5
+    SolverSystem o1, SolverDocker o2, SolverLocalDB o3, SolverCachedRemote o4
   ) {
-    return Set.of(o1, o3, o4, o5);
+    return Set.of(o1, o2, o3, o4);
+  }
+
+  @Provides
+  @Singleton
+  @CacheName(name = Name.REMOTE)
+  static SolverCache remoteCache(){
+    return new SolverCache(Name.REMOTE);
+  }
+
+  @Provides
+  @Singleton
+  @CacheName(name = Name.GLOBAL)
+  static SolverCache globalCache(){
+    return new SolverCache(Name.GLOBAL);
   }
 }
diff --git a/src/main/java/com/mageddo/dnsproxyserver/server/dns/Messages.java b/src/main/java/com/mageddo/dnsproxyserver/server/dns/Messages.java
index 95a20c48..b6594b2f 100644
--- a/src/main/java/com/mageddo/dnsproxyserver/server/dns/Messages.java
+++ b/src/main/java/com/mageddo/dnsproxyserver/server/dns/Messages.java
@@ -185,7 +185,7 @@ public class Messages {
   /**
    * Set the id of the query into the response, se the response will match if the query;
    */
-  public static Message idMatches(Message req, Message res) {
+  public static Message mergeId(Message req, Message res) {
     final var reqId = req.getHeader().getID();
     res.getHeader().setID(reqId);
     return res;
diff --git a/src/main/java/com/mageddo/dnsproxyserver/server/dns/RequestHandlerDefault.java b/src/main/java/com/mageddo/dnsproxyserver/server/dns/RequestHandlerDefault.java
index da452222..ea552519 100644
--- a/src/main/java/com/mageddo/dnsproxyserver/server/dns/RequestHandlerDefault.java
+++ b/src/main/java/com/mageddo/dnsproxyserver/server/dns/RequestHandlerDefault.java
@@ -1,46 +1,45 @@
 package com.mageddo.dnsproxyserver.server.dns;
 
+import com.mageddo.dnsproxyserver.server.dns.solver.CacheName;
+import com.mageddo.dnsproxyserver.server.dns.solver.CacheName.Name;
 import com.mageddo.dnsproxyserver.server.dns.solver.Response;
-import com.mageddo.dnsproxyserver.server.dns.solver.Solver;
-import com.mageddo.dnsproxyserver.server.dns.solver.SolversCache;
+import com.mageddo.dnsproxyserver.server.dns.solver.SolverCache;
+import com.mageddo.dnsproxyserver.server.dns.solver.SolverProvider;
 import com.mageddo.dnsproxyserver.utils.Classes;
-import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.ClassUtils;
-import org.apache.commons.lang3.Validate;
 import org.apache.commons.lang3.time.StopWatch;
 import org.xbill.DNS.Message;
 
 import javax.inject.Inject;
 import javax.inject.Singleton;
-import java.util.ArrayList;
-import java.util.List;
 import java.util.Optional;
 
 import static com.mageddo.dnsproxyserver.server.dns.Messages.simplePrint;
 
 @Slf4j
 @Singleton
-@RequiredArgsConstructor(onConstructor = @__({@Inject}))
 public class RequestHandlerDefault implements RequestHandler {
 
-  private final SolversCache cache;
-  private final List<Solver> solvers = new ArrayList<>();
+  private final SolverProvider solverProvider;
+  private final SolverCache cache;
 
-  @Override
-  public Message handle(Message query, String kind){
-    return this.solve(query, kind);
+  @Inject
+  public RequestHandlerDefault(
+    SolverProvider solverProvider,
+    @CacheName(name = Name.GLOBAL) SolverCache cache
+  ) {
+    this.solverProvider = solverProvider;
+    this.cache = cache;
   }
 
-  public RequestHandlerDefault bind(Solver solver) {
-    this.solvers.add(solver);
-    return this;
+  @Override
+  public Message handle(Message query, String kind) {
+    return this.solve(query, kind);
   }
 
   Message solve(Message query, String kind) {
 
-    Validate.isTrue(!this.solvers.isEmpty(), "At least one solver is required");
-
     final var stopWatch = StopWatch.createStarted();
     try {
       final var r = Optional
@@ -59,7 +58,8 @@ public class RequestHandlerDefault implements RequestHandler {
 
   Response solve0(Message reqMsg) {
     final var stopWatch = StopWatch.createStarted();
-    for (final var solver : this.solvers) {
+    final var solvers = this.solverProvider.getSolvers();
+    for (final var solver : solvers) {
       stopWatch.split();
       final var solverName = Classes.findSimpleName(solver);
       try {
diff --git a/src/main/java/com/mageddo/dnsproxyserver/server/dns/ServerStarter.java b/src/main/java/com/mageddo/dnsproxyserver/server/dns/ServerStarter.java
index 0f65cd48..282a904f 100644
--- a/src/main/java/com/mageddo/dnsproxyserver/server/dns/ServerStarter.java
+++ b/src/main/java/com/mageddo/dnsproxyserver/server/dns/ServerStarter.java
@@ -2,28 +2,22 @@ package com.mageddo.dnsproxyserver.server.dns;
 
 import com.mageddo.dnsproxyserver.config.Configs;
 import com.mageddo.dnsproxyserver.dnsconfigurator.DpsIpDiscover;
-import com.mageddo.dnsproxyserver.server.dns.solver.Solver;
-import com.mageddo.dnsproxyserver.server.dns.solver.SolverProvider;
 import lombok.Getter;
 import lombok.extern.slf4j.Slf4j;
 
-import javax.enterprise.inject.Instance;
 import javax.inject.Inject;
 import javax.inject.Singleton;
-import java.util.List;
 
 @Slf4j
 @Getter
 @Singleton
 public class ServerStarter {
 
-  private final List<Solver> solvers;
   private final SimpleServer server;
   private final DpsIpDiscover dpsIpDiscover;
 
   @Inject
-  public ServerStarter(Instance<Solver> solvers, SimpleServer server, DpsIpDiscover dpsIpDiscover) {
-    this.solvers = new SolverProvider(solvers).getSolvers();
+  public ServerStarter(SimpleServer server, DpsIpDiscover dpsIpDiscover) {
     this.server = server;
     this.dpsIpDiscover = dpsIpDiscover;
   }
@@ -33,8 +27,7 @@ public class ServerStarter {
     final var port = config.getDnsServerPort();
     this.server.start(
       port,
-      config.getServerProtocol(),
-      this.solvers
+      config.getServerProtocol()
     );
     log.debug("status=startingDnsServer, protocol={}, port={}", config.getServerProtocol(), port);
     return this;
diff --git a/src/main/java/com/mageddo/dnsproxyserver/server/dns/SimpleServer.java b/src/main/java/com/mageddo/dnsproxyserver/server/dns/SimpleServer.java
index 474a7ce5..a7bd73e5 100644
--- a/src/main/java/com/mageddo/dnsproxyserver/server/dns/SimpleServer.java
+++ b/src/main/java/com/mageddo/dnsproxyserver/server/dns/SimpleServer.java
@@ -1,12 +1,10 @@
 package com.mageddo.dnsproxyserver.server.dns;
 
-import com.mageddo.dnsproxyserver.server.dns.solver.Solver;
 import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 
 import javax.inject.Inject;
 import javax.inject.Singleton;
-import java.util.List;
 
 @Slf4j
 @Singleton
@@ -17,11 +15,8 @@ public class SimpleServer {
   private final TCPServer tcpServer;
   private final RequestHandlerDefault requestHandler;
 
-  public void start(int port, Protocol protocol, List<Solver> solvers) {
-
-    solvers.forEach(this.requestHandler::bind);
+  public void start(int port, Protocol protocol) {
     this.start0(port, protocol);
-
   }
 
   void start0(int port, Protocol protocol) {
diff --git a/src/main/java/com/mageddo/dnsproxyserver/server/dns/solver/CacheName.java b/src/main/java/com/mageddo/dnsproxyserver/server/dns/solver/CacheName.java
new file mode 100644
index 00000000..54097650
--- /dev/null
+++ b/src/main/java/com/mageddo/dnsproxyserver/server/dns/solver/CacheName.java
@@ -0,0 +1,26 @@
+package com.mageddo.dnsproxyserver.server.dns.solver;
+
+import org.apache.commons.lang3.EnumUtils;
+
+import javax.inject.Qualifier;
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Qualifier
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+public @interface CacheName {
+
+  Name name();
+
+  enum Name {
+    REMOTE,
+    GLOBAL,
+    ;
+
+    public static Name fromName(String name) {
+      return EnumUtils.getEnumIgnoreCase(Name.class, name);
+    }
+  }
+}
diff --git a/src/main/java/com/mageddo/dnsproxyserver/server/dns/solver/Response.java b/src/main/java/com/mageddo/dnsproxyserver/server/dns/solver/Response.java
index 4accf616..76ffd59f 100644
--- a/src/main/java/com/mageddo/dnsproxyserver/server/dns/solver/Response.java
+++ b/src/main/java/com/mageddo/dnsproxyserver/server/dns/solver/Response.java
@@ -7,6 +7,7 @@ import lombok.Value;
 import org.xbill.DNS.Message;
 
 import java.time.Duration;
+import java.time.LocalDateTime;
 
 @Value
 @Builder(toBuilder = true)
@@ -24,6 +25,9 @@ public class Response {
   @NonNull
   private Duration ttl;
 
+  @NonNull
+  private LocalDateTime createdAt;
+
   public static Response of(Message message){
     return of(message, Messages.DEFAULT_TTL_DURATION);
   }
@@ -33,6 +37,7 @@ public class Response {
       .builder()
       .message(message)
       .ttl(ttl)
+      .createdAt(LocalDateTime.now())
       .build();
   }
 
diff --git a/src/main/java/com/mageddo/dnsproxyserver/server/dns/solver/SolversCache.java b/src/main/java/com/mageddo/dnsproxyserver/server/dns/solver/SolverCache.java
similarity index 79%
rename from src/main/java/com/mageddo/dnsproxyserver/server/dns/solver/SolversCache.java
rename to src/main/java/com/mageddo/dnsproxyserver/server/dns/solver/SolverCache.java
index 3bac6c22..b5e8e1b6 100644
--- a/src/main/java/com/mageddo/dnsproxyserver/server/dns/solver/SolversCache.java
+++ b/src/main/java/com/mageddo/dnsproxyserver/server/dns/solver/SolverCache.java
@@ -4,12 +4,12 @@ import com.mageddo.commons.caching.LruTTLCache;
 import com.mageddo.commons.lang.Objects;
 import com.mageddo.commons.lang.tuple.Pair;
 import com.mageddo.dnsproxyserver.server.dns.Messages;
+import com.mageddo.dnsproxyserver.server.dns.solver.CacheName.Name;
+import lombok.NonNull;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.xbill.DNS.Message;
 
-import javax.inject.Inject;
-import javax.inject.Singleton;
 import java.time.Duration;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -21,19 +21,25 @@ import static com.mageddo.dnsproxyserver.server.dns.Messages.findQuestionHostnam
 import static com.mageddo.dnsproxyserver.server.dns.Messages.findQuestionType;
 
 @Slf4j
-@Singleton
-@RequiredArgsConstructor(onConstructor = @__({@Inject}))
-public class SolversCache {
+@RequiredArgsConstructor
+public class SolverCache {
 
   private final LruTTLCache cache = new LruTTLCache(2048, Duration.ofSeconds(5), false);
 
+  @NonNull
+  private final Name name;
+
   public Message handle(Message query, Function<Message, Response> delegate) {
+    return Objects.mapOrNull(this.handleRes(query, delegate), Response::getMessage);
+  }
+
+  public Response handleRes(Message query, Function<Message, Response> delegate) {
     final var key = buildKey(query);
     final var res = this.cache.computeIfAbsent0(key, (k) -> {
       log.trace("status=lookup, key={}, req={}", key, Messages.simplePrint(query));
       final var _res = delegate.apply(query);
       if (_res == null) {
-        log.debug("status=noAnswer, action=cant-cache, k={}", k);
+        log.debug("status=noAnswer, action=cantCache, k={}", k);
         return null;
       }
       final var ttl = _res.getTtl();
@@ -43,7 +49,7 @@ public class SolversCache {
     if (res == null) {
       return null;
     }
-    return Objects.mapOrNull(res.getMessage(), it -> Messages.idMatches(query, it));
+    return res.withMessage(Messages.mergeId(query, res.getMessage()));
   }
 
   static String buildKey(Message reqMsg) {
@@ -74,4 +80,8 @@ public class SolversCache {
     return tmpMap;
   }
 
+  public Name name() {
+    return this.name;
+  }
+
 }
diff --git a/src/main/java/com/mageddo/dnsproxyserver/server/dns/solver/SolverCacheFactory.java b/src/main/java/com/mageddo/dnsproxyserver/server/dns/solver/SolverCacheFactory.java
new file mode 100644
index 00000000..343e1b12
--- /dev/null
+++ b/src/main/java/com/mageddo/dnsproxyserver/server/dns/solver/SolverCacheFactory.java
@@ -0,0 +1,76 @@
+package com.mageddo.dnsproxyserver.server.dns.solver;
+
+import com.mageddo.dnsproxyserver.server.dns.solver.CacheName.Name;
+import lombok.extern.slf4j.Slf4j;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import static com.mageddo.dnsproxyserver.server.dns.solver.CacheName.Name.GLOBAL;
+import static com.mageddo.dnsproxyserver.server.dns.solver.CacheName.Name.REMOTE;
+
+@Slf4j
+@Singleton
+public class SolverCacheFactory {
+
+  private final SolverCache remote;
+  private final SolverCache global;
+
+  @Inject
+  public SolverCacheFactory(
+    @CacheName(name = REMOTE)
+    SolverCache remote,
+
+    @CacheName(name = GLOBAL)
+    SolverCache global
+  ) {
+    this.remote = remote;
+    this.global = global;
+  }
+
+  public SolverCache getInstance(Name name) {
+    return switch (name) {
+      case GLOBAL -> this.global;
+      case REMOTE -> this.remote;
+    };
+  }
+
+  public List<SolverCache> findInstances(Name name) {
+    if (name == null) {
+      return this.getCaches();
+    }
+    return Collections.singletonList(this.getInstance(name));
+  }
+
+  public Map<String, Map<String, CacheEntry>> findCachesAsMap(Name name) {
+    return this.findInstances(name)
+      .stream()
+      .collect(Collectors.toMap(it -> it.name().name(), SolverCache::asMap))
+      ;
+  }
+
+  private List<SolverCache> getCaches() {
+    return List.of(this.remote, this.global);
+  }
+
+  public void clear(Name name) {
+    if (name == null) {
+      for (final var cache : this.getCaches()) {
+        cache.clear();
+      }
+      return;
+    }
+    this.getInstance(name).clear();
+  }
+
+  public Map<String, Integer> findInstancesSizeMap(Name name) {
+    return this.findInstances(name)
+      .stream()
+      .collect(Collectors.toMap(it -> it.name().name(), SolverCache::getSize))
+      ;
+  }
+}
diff --git a/src/main/java/com/mageddo/dnsproxyserver/server/dns/solver/SolverCachedRemote.java b/src/main/java/com/mageddo/dnsproxyserver/server/dns/solver/SolverCachedRemote.java
new file mode 100644
index 00000000..5433662f
--- /dev/null
+++ b/src/main/java/com/mageddo/dnsproxyserver/server/dns/solver/SolverCachedRemote.java
@@ -0,0 +1,40 @@
+package com.mageddo.dnsproxyserver.server.dns.solver;
+
+import com.mageddo.dnsproxyserver.server.dns.Messages;
+import com.mageddo.dnsproxyserver.server.dns.solver.CacheName.Name;
+import lombok.extern.slf4j.Slf4j;
+import org.xbill.DNS.Message;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import java.time.Duration;
+
+@Slf4j
+@Singleton
+public class SolverCachedRemote implements Solver {
+
+  private final SolverRemote solverRemote;
+  private final SolverCache solversCache;
+
+  @Inject
+  public SolverCachedRemote(
+    SolverRemote solverRemote,
+    @CacheName(name = Name.REMOTE) SolverCache cache
+  ) {
+    this.solverRemote = solverRemote;
+    this.solversCache = cache;
+  }
+
+  @Override
+  public Response handle(Message query) {
+    final var res = this.solversCache.handleRes(query, query_ -> {
+      log.debug("status=remoteHotLoading, query={}", Messages.simplePrint(query));
+      return this.solverRemote.handle(query);
+    });
+    return res
+      .toBuilder()
+      .ttl(Duration.ofSeconds(20))
+      .build()
+      ;
+  }
+}
diff --git a/src/main/java/com/mageddo/dnsproxyserver/server/dns/solver/SolverLocalDB.java b/src/main/java/com/mageddo/dnsproxyserver/server/dns/solver/SolverLocalDB.java
index 55ea91ae..ccc2994f 100644
--- a/src/main/java/com/mageddo/dnsproxyserver/server/dns/solver/SolverLocalDB.java
+++ b/src/main/java/com/mageddo/dnsproxyserver/server/dns/solver/SolverLocalDB.java
@@ -4,6 +4,7 @@ import com.mageddo.dnsproxyserver.config.Config;
 import com.mageddo.dnsproxyserver.config.Config.Entry.Type;
 import com.mageddo.dnsproxyserver.config.ConfigDAO;
 import com.mageddo.dnsproxyserver.server.dns.Messages;
+import dagger.Lazy;
 import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.time.StopWatch;
@@ -19,7 +20,7 @@ import java.time.Duration;
 public class SolverLocalDB implements Solver {
 
   private final ConfigDAO configDAO;
-  private final SolverDelegate solverDelegate;
+  private final Lazy<SolverDelegate> solverDelegate;
 
   @Override
   public Response handle(Message query) {
@@ -54,7 +55,7 @@ public class SolverLocalDB implements Solver {
           Duration.ofSeconds(entry.getTtl())
         );
       }
-      return this.solverDelegate.solve(query, entry);
+      return this.solverDelegate.get().solve(query, entry);
     });
     if (res != null) {
       return res;
diff --git a/src/main/java/com/mageddo/dnsproxyserver/server/dns/solver/SolverProvider.java b/src/main/java/com/mageddo/dnsproxyserver/server/dns/solver/SolverProvider.java
index 4bccf42e..976f1dd4 100644
--- a/src/main/java/com/mageddo/dnsproxyserver/server/dns/solver/SolverProvider.java
+++ b/src/main/java/com/mageddo/dnsproxyserver/server/dns/solver/SolverProvider.java
@@ -1,13 +1,11 @@
 package com.mageddo.dnsproxyserver.server.dns.solver;
 
-import com.mageddo.dnsproxyserver.di.Context;
 import lombok.extern.slf4j.Slf4j;
 
 import javax.enterprise.inject.Instance;
 import javax.inject.Inject;
 import javax.inject.Singleton;
 import java.util.List;
-import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.stream.Collectors;
 
 @Slf4j
@@ -15,18 +13,13 @@ import java.util.stream.Collectors;
 public class SolverProvider {
 
   private List<Solver> solvers;
-  private final AtomicBoolean count = new AtomicBoolean();
 
   @Inject
-  public SolverProvider() {
-  }
-
   public SolverProvider(Instance<Solver> solvers) {
     this.solvers = sorted(solvers);
   }
 
   public List<Solver> getSolversExcludingLocalDB() {
-    this.lazyLoad();
     return this.solvers
         .stream()
         .filter(it -> it.getClass() != SolverLocalDB.class)
@@ -35,20 +28,9 @@ public class SolverProvider {
   }
 
   public List<Solver> getSolvers() {
-    this.lazyLoad();
     return this.solvers;
   }
 
-  void lazyLoad() {
-    if (this.count.compareAndSet(false, true)) {
-      this.solvers = sorted(Context
-          .create()
-          .solvers()
-      );
-      log.debug("status=instantesSet, size={}", this.solvers.size());
-    }
-  }
-
   static List<Solver> sorted(Instance<Solver> solvers) {
     return Solvers.sorted(solvers.stream().toList());
   }
diff --git a/src/main/java/com/mageddo/dnsproxyserver/server/dns/solver/Solvers.java b/src/main/java/com/mageddo/dnsproxyserver/server/dns/solver/Solvers.java
index dccd6b02..069cd12b 100644
--- a/src/main/java/com/mageddo/dnsproxyserver/server/dns/solver/Solvers.java
+++ b/src/main/java/com/mageddo/dnsproxyserver/server/dns/solver/Solvers.java
@@ -11,7 +11,7 @@ import java.util.Map;
 public class Solvers {
 
   private static final Map<String, Integer> priorities = Priorities.build(
-    "SolverCached", "SolverSystem", "SolverDocker", "SolverLocalDB", "SolverRemote"
+    "SolverCached", "SolverSystem", "SolverDocker", "SolverLocalDB", "SolverCachedRemote"
   );
 
   public static List<Solver> sorted(Collection<Solver> source) {
diff --git a/src/main/java/com/mageddo/dnsproxyserver/server/rest/CacheResource.java b/src/main/java/com/mageddo/dnsproxyserver/server/rest/CacheResource.java
index 9b0d943d..e5fe35bc 100644
--- a/src/main/java/com/mageddo/dnsproxyserver/server/rest/CacheResource.java
+++ b/src/main/java/com/mageddo/dnsproxyserver/server/rest/CacheResource.java
@@ -1,14 +1,17 @@
 package com.mageddo.dnsproxyserver.server.rest;
 
-import com.mageddo.dnsproxyserver.server.dns.solver.SolversCache;
+import com.mageddo.dnsproxyserver.server.dns.solver.SolverCacheFactory;
+import com.mageddo.dnsproxyserver.server.dns.solver.CacheName.Name;
+import com.mageddo.dnsproxyserver.server.rest.reqres.CacheEntryResV1;
 import com.mageddo.http.HttpMapper;
+import com.mageddo.http.Request;
 import com.mageddo.http.WebServer;
+import com.sun.net.httpserver.HttpExchange;
 import lombok.RequiredArgsConstructor;
 
 import javax.inject.Inject;
 import javax.inject.Singleton;
 import javax.ws.rs.core.Response;
-import java.util.Map;
 
 import static com.mageddo.http.codec.Encoders.encodeJson;
 
@@ -16,28 +19,31 @@ import static com.mageddo.http.codec.Encoders.encodeJson;
 @RequiredArgsConstructor(onConstructor = @__({@Inject}))
 public class CacheResource implements HttpMapper {
 
-  private final SolversCache cache;
+  public static final String CACHE_NAME_PARAM = "name";
+  private final SolverCacheFactory factory;
 
   @Override
   public void map(WebServer server) {
 
     server.get(
       "/v1/caches/size",
-      exchange -> encodeJson(
-        exchange,
-        Response.Status.OK,
-        Map.of("size", this.cache.getSize())
-      )
+      exchange -> {
+        encodeJson(
+          exchange,
+          Response.Status.OK,
+          this.factory.findInstancesSizeMap(buildCacheName(exchange))
+        );
+      }
     );
 
     server.delete(
       "/v1/caches",
       exchange -> {
-        this.cache.clear();
+        this.factory.clear(buildCacheName(exchange));
         encodeJson(
           exchange,
           Response.Status.OK,
-          Map.of("size", this.cache.getSize())
+          this.factory.findInstancesSizeMap(buildCacheName(exchange))
         );
       }
     );
@@ -47,9 +53,13 @@ public class CacheResource implements HttpMapper {
       exchange -> encodeJson(
         exchange,
         Response.Status.OK,
-        this.cache.asMap()
+        CacheEntryResV1.of(this.factory.findCachesAsMap(buildCacheName(exchange)))
       )
     );
   }
 
+  private static Name buildCacheName(HttpExchange exchange) {
+    return Name.fromName(Request.queryParam(exchange, CACHE_NAME_PARAM));
+  }
+
 }
diff --git a/src/main/java/com/mageddo/dnsproxyserver/server/rest/reqres/CacheEntryResV1.java b/src/main/java/com/mageddo/dnsproxyserver/server/rest/reqres/CacheEntryResV1.java
index 115e4323..e8732d72 100644
--- a/src/main/java/com/mageddo/dnsproxyserver/server/rest/reqres/CacheEntryResV1.java
+++ b/src/main/java/com/mageddo/dnsproxyserver/server/rest/reqres/CacheEntryResV1.java
@@ -4,7 +4,9 @@ import com.mageddo.dnsproxyserver.server.dns.solver.CacheEntry;
 import lombok.Data;
 import lombok.experimental.Accessors;
 
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.stream.Collectors;
 
 @Data
@@ -30,4 +32,19 @@ public class CacheEntryResV1 {
       ;
   }
 
+  public static Map<String, Map<String, CacheEntryResV1>> of(Map<String, Map<String, CacheEntry>> cache) {
+    final var m = new HashMap<String, Map<String, CacheEntryResV1>>();
+    cache
+      .keySet()
+      .forEach(k -> m.computeIfAbsent(k, (k_) -> {
+        final var v = new HashMap<String, CacheEntryResV1>();
+        cache
+          .get(k)
+          .forEach((k2, v2) -> {
+            v.put(k2, of(v2));
+          });
+        return v;
+      }));
+    return m;
+  }
 }


Test patch:
diff --git a/src/test/java/com/mageddo/dnsproxyserver/server/CacheResourceCompTest.java b/src/test/java/com/mageddo/dnsproxyserver/server/CacheResourceCompTest.java
index bc1f475b..b28f7c81 100644
--- a/src/test/java/com/mageddo/dnsproxyserver/server/CacheResourceCompTest.java
+++ b/src/test/java/com/mageddo/dnsproxyserver/server/CacheResourceCompTest.java
@@ -1,20 +1,32 @@
 package com.mageddo.dnsproxyserver.server;
 
 import com.mageddo.dnsproxyserver.config.Configs;
-import testing.ContextSupplier;
+import com.mageddo.dnsproxyserver.server.dns.solver.CacheName;
+import com.mageddo.dnsproxyserver.server.dns.solver.CacheName.Name;
+import com.mageddo.dnsproxyserver.server.dns.solver.SolverCache;
+import com.mageddo.dnsproxyserver.templates.MessageTemplates;
+import com.mageddo.dnsproxyserver.templates.ResponseTemplates;
 import dagger.sheath.junit.DaggerTest;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
+import testing.ContextSupplier;
 import testing.Events;
 
+import javax.inject.Inject;
 import javax.ws.rs.core.Response;
 
 import static io.restassured.RestAssured.given;
 import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.Matchers.nullValue;
 
 @DaggerTest(initializer = ContextSupplier.class, eventsHandler = Events.class)
 class CacheResourceCompTest {
 
+  @Inject
+  @CacheName(name = Name.GLOBAL)
+  SolverCache cache;
+
   @BeforeEach
   void beforeEach(){
     Configs
@@ -37,7 +49,51 @@ class CacheResourceCompTest {
     // assert
     response
       .statusCode(Response.Status.OK.getStatusCode())
-      .body(equalTo("{\"size\":0}"))
+      .body(equalTo("""
+        {"GLOBAL":0,"REMOTE":0}"""))
+      .log()
+    ;
+  }
+
+  @Test
+  void mustFindCaches() {
+    // arrange
+    this.cache.handle(MessageTemplates.acmeAQuery(), ResponseTemplates::to);
+
+    // act
+    final var response = given()
+      .get("/v1/caches")
+      .then()
+      .log()
+      .ifValidationFails();
+
+    // assert
+    response
+      .statusCode(Response.Status.OK.getStatusCode())
+      .body("GLOBAL", notNullValue())
+      .body("REMOTE.'A-acme.com'", notNullValue())
+      .body("REMOTE.'A-acme.com'.ttl", equalTo("PT5S"))
+      .log()
+    ;
+  }
+
+  @Test
+  void mustFilterCaches() {
+    // arrange
+
+    // act
+    final var response = given()
+      .param("name", "GLOBAL")
+      .get("/v1/caches")
+      .then()
+      .log()
+      .ifValidationFails();
+
+    // assert
+    response
+      .statusCode(Response.Status.OK.getStatusCode())
+      .body("GLOBAL", notNullValue())
+      .body("REMOTE", nullValue())
       .log()
     ;
   }
diff --git a/src/test/java/com/mageddo/dnsproxyserver/server/dns/solver/RemoteResolversCompTest.java b/src/test/java/com/mageddo/dnsproxyserver/server/dns/solver/RemoteResolversCompTest.java
index 11fac25c..3a7a4417 100644
--- a/src/test/java/com/mageddo/dnsproxyserver/server/dns/solver/RemoteResolversCompTest.java
+++ b/src/test/java/com/mageddo/dnsproxyserver/server/dns/solver/RemoteResolversCompTest.java
@@ -1,16 +1,15 @@
 package com.mageddo.dnsproxyserver.server.dns.solver;
 
-import com.mageddo.dnsproxyserver.di.Context;
-import testing.Events;
 import dagger.sheath.junit.DaggerTest;
 import org.junit.jupiter.api.Test;
+import testing.ContextSupplier;
+import testing.Events;
 
 import javax.inject.Inject;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
-
-@DaggerTest(component = Context.class, eventsHandler = Events.class)
+@DaggerTest(initializer = ContextSupplier.class, eventsHandler = Events.class)
 class RemoteResolversCompTest {
 
   @Inject
diff --git a/src/test/java/com/mageddo/dnsproxyserver/server/dns/ServerStarterTest.java b/src/test/java/com/mageddo/dnsproxyserver/server/dns/solver/SolverProviderCompTest.java
similarity index 51%
rename from src/test/java/com/mageddo/dnsproxyserver/server/dns/ServerStarterTest.java
rename to src/test/java/com/mageddo/dnsproxyserver/server/dns/solver/SolverProviderCompTest.java
index 38802d92..9be788cc 100644
--- a/src/test/java/com/mageddo/dnsproxyserver/server/dns/ServerStarterTest.java
+++ b/src/test/java/com/mageddo/dnsproxyserver/server/dns/solver/SolverProviderCompTest.java
@@ -1,19 +1,19 @@
-package com.mageddo.dnsproxyserver.server.dns;
+package com.mageddo.dnsproxyserver.server.dns.solver;
 
-import com.mageddo.dnsproxyserver.di.Context;
 import dagger.sheath.junit.DaggerTest;
-import org.apache.commons.lang3.ClassUtils;
 import org.junit.jupiter.api.Test;
+import testing.ContextSupplier;
+import testing.Events;
 
 import javax.inject.Inject;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
-@DaggerTest(component = Context.class)
-class ServerStarterTest {
+@DaggerTest(initializer = ContextSupplier.class, eventsHandler = Events.class)
+class SolverProviderCompTest {
 
   @Inject
-  ServerStarter serverStarter;
+  SolverProvider provider;
 
   @Test
   void mustCreateSolverListInRightOrder(){
@@ -21,13 +21,12 @@ class ServerStarterTest {
     // arrange
 
     // act
-    final var names = this.serverStarter.getSolvers()
+    final var names = this.provider.getSolvers()
       .stream()
-      .map(ClassUtils::getSimpleName)
+      .map(Solver::name)
       .toList();
 
-
     // assert
-    assertEquals("[SolverSystem, SolverDocker, SolverLocalDB, SolverRemote]", names.toString());
+    assertEquals("[SolverSystem, SolverDocker, SolverLocalDB, SolverCachedRemote]", names.toString());
   }
 }
diff --git a/src/test/java/com/mageddo/dnsproxyserver/server/dns/solver/SolversCacheTest.java b/src/test/java/com/mageddo/dnsproxyserver/server/dns/solver/SolversCacheTest.java
index 19abe450..4f59ec89 100644
--- a/src/test/java/com/mageddo/dnsproxyserver/server/dns/solver/SolversCacheTest.java
+++ b/src/test/java/com/mageddo/dnsproxyserver/server/dns/solver/SolversCacheTest.java
@@ -1,6 +1,7 @@
 package com.mageddo.dnsproxyserver.server.dns.solver;
 
 import com.mageddo.dnsproxyserver.server.dns.Messages;
+import com.mageddo.dnsproxyserver.server.dns.solver.CacheName.Name;
 import com.mageddo.dnsproxyserver.templates.MessageTemplates;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
@@ -15,7 +16,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 @ExtendWith(MockitoExtension.class)
 class SolversCacheTest {
 
-  SolversCache cache = new SolversCache();
+  SolverCache cache = new SolverCache(Name.GLOBAL);
 
   @Test
   void mustCacheAndGetValidResponse(){
diff --git a/src/test/java/com/mageddo/dnsproxyserver/templates/ResponseTemplates.java b/src/test/java/com/mageddo/dnsproxyserver/templates/ResponseTemplates.java
new file mode 100644
index 00000000..6c78e556
--- /dev/null
+++ b/src/test/java/com/mageddo/dnsproxyserver/templates/ResponseTemplates.java
@@ -0,0 +1,13 @@
+package com.mageddo.dnsproxyserver.templates;
+
+import com.mageddo.dnsproxyserver.server.dns.Messages;
+import com.mageddo.dnsproxyserver.server.dns.solver.Response;
+import org.xbill.DNS.Message;
+
+import java.time.Duration;
+
+public class ResponseTemplates {
+  public static Response to(Message query) {
+    return Response.of(Messages.aAnswer(query, "0.0.0.0"), Duration.ofSeconds(5));
+  }
+}
diff --git a/src/test/java/dagger/sheath/CtxWrapper.java b/src/test/java/dagger/sheath/CtxWrapper.java
index e6b353bb..dd39da91 100644
--- a/src/test/java/dagger/sheath/CtxWrapper.java
+++ b/src/test/java/dagger/sheath/CtxWrapper.java
@@ -89,7 +89,7 @@ public class CtxWrapper {
       final var p = BindingMethod.findBindingMap(this);
       if (p != null) {
         log.debug("status=providerByBindings, type={}", type);
-        return new ProviderWrapper(p.get(type), type);
+        return ProviderWrapper.from(p.get(type), type);
       }
     }
     return null;
@@ -101,9 +101,8 @@ public class CtxWrapper {
       if (field == null) {
         return null;
       }
-//      Validate.notNull(field, "No provider found for type: %s", type);
       final Object provider = FieldUtils.readField(field, this.ctx, true);
-      return new ProviderWrapper(provider, type);
+      return ProviderWrapper.from(provider, type);
     } catch (IllegalAccessException e) {
       throw new IllegalStateException(e);
     }
diff --git a/src/test/java/dagger/sheath/ProviderWrapper.java b/src/test/java/dagger/sheath/ProviderWrapper.java
index 2afa2f72..3a70876c 100644
--- a/src/test/java/dagger/sheath/ProviderWrapper.java
+++ b/src/test/java/dagger/sheath/ProviderWrapper.java
@@ -1,7 +1,9 @@
 package dagger.sheath;
 
+import dagger.internal.DelegateFactory;
 import dagger.internal.DoubleCheck;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.ClassUtils;
 import org.apache.commons.lang3.Validate;
 import org.apache.commons.lang3.reflect.FieldUtils;
 import org.apache.commons.lang3.reflect.MethodUtils;
@@ -17,7 +19,7 @@ public class ProviderWrapper {
   private final Class<?> type;
   private final Object provider;
 
-  public ProviderWrapper(Object provider, Class<?> type) {
+  private ProviderWrapper(Object provider, Class<?> type) {
     this.provider = provider;
     this.type = type;
   }
@@ -65,4 +67,35 @@ public class ProviderWrapper {
       throw new IllegalStateException(e);
     }
   }
+
+  public static ProviderWrapper from(Object o, Class<?> type) {
+    try {
+      if (o instanceof DelegateFactory<?>) {
+        final var delegateField = FieldUtils.getField(DelegateFactory.class, "delegate", true);
+        final var provider = FieldUtils.readField(delegateField, o);
+        validateIsProviderClass(provider);
+        return new ProviderWrapper(provider, type);
+      }
+      if (isDoubleCheckClass(o)) {
+        return new ProviderWrapper(o, type);
+      }
+      final var providerClass = ClassUtils.getName(o);
+      Validate.isTrue(isGeneratedFactory(providerClass), "Unknown dagger provider: %s", providerClass);
+      return new ProviderWrapper(o, type);
+    } catch (IllegalAccessException e) {
+      throw new IllegalStateException(e);
+    }
+  }
+
+  private static boolean isGeneratedFactory(String providerClass) {
+    return !providerClass.startsWith("dagger.");
+  }
+
+  private static void validateIsProviderClass(Object provider) {
+    Validate.isTrue(isDoubleCheckClass(provider), "Not the expected provider class!: %s", provider);
+  }
+
+  private static boolean isDoubleCheckClass(Object provider) {
+    return Objects.equals(DoubleCheck.class, provider.getClass());
+  }
 }