cb648091203ba170014e084c30f01f93bf3f2b56
[controller.git] / opendaylight / md-sal / cds-access-client / src / main / java / org / opendaylight / controller / cluster / access / client / BackendInfoResolver.java
1 /*
2  * Copyright (c) 2016 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.controller.cluster.access.client;
9
10 import akka.actor.ActorRef;
11 import com.google.common.base.Preconditions;
12 import java.util.Optional;
13 import java.util.concurrent.CompletableFuture;
14 import java.util.concurrent.CompletionStage;
15 import java.util.concurrent.ConcurrentHashMap;
16 import java.util.concurrent.ConcurrentMap;
17 import java.util.concurrent.ExecutionException;
18 import java.util.concurrent.Future;
19 import javax.annotation.Nonnull;
20 import javax.annotation.concurrent.ThreadSafe;
21 import org.slf4j.Logger;
22 import org.slf4j.LoggerFactory;
23
24 /**
25  * Caching resolver which resolves a cookie to a leader {@link ActorRef}. This class needs to be specialized by the
26  * client. It is used by {@link ClientActorBehavior} for request dispatch. Results are cached until they are invalidated
27  * by either the client actor (when a message timeout is detected) and by the specific frontend (on explicit
28  * invalidation or when updated information becomes available).
29  *
30  * @author Robert Varga
31  */
32 @ThreadSafe
33 public abstract class BackendInfoResolver<T extends BackendInfo> {
34     private static final Logger LOG = LoggerFactory.getLogger(BackendInfoResolver.class);
35     private final ConcurrentMap<Long, CompletableFuture<T>> backends = new ConcurrentHashMap<>();
36
37     /**
38      * Return the currently-resolved backend information, if available. This method is guaranteed not to block, but will
39      * initiate resolution of the information if there is none.
40      *
41      * @param cookie Backend cookie
42      * @return Backend information, if available
43      */
44     public final Optional<T> getFutureBackendInfo(final Long cookie) {
45         final Future<T> f = lookupBackend(cookie);
46         if (f.isDone()) {
47             try {
48                 return Optional.of(f.get());
49             } catch (InterruptedException | ExecutionException e) {
50                 LOG.debug("Resolution of {} failed", f, e);
51             }
52         }
53
54         return Optional.empty();
55     }
56
57     /**
58      * Invalidate a particular instance of {@link BackendInfo}, typically as a response to a request timing out. If
59      * the provided information is not the one currently cached this method does nothing.
60      *
61      * @param cookie Backend cookie
62      * @param info Previous information to be invalidated
63      */
64     public final void invalidateBackend(final long cookie, final @Nonnull CompletionStage<? extends BackendInfo> info) {
65         if (backends.remove(cookie, Preconditions.checkNotNull(info))) {
66             LOG.trace("Invalidated cache %s -> %s", Long.toUnsignedString(cookie), info);
67             invalidateBackendInfo(info);
68         }
69     }
70
71     /**
72      * Request new resolution of a particular backend identified by a cookie. This method is invoked when a client
73      * requests information which is not currently cached.
74      *
75      * @param cookie Backend cookie
76      * @return A {@link CompletableFuture} resulting in information about the backend
77      */
78     protected abstract @Nonnull CompletableFuture<T> resolveBackendInfo(final @Nonnull Long cookie);
79
80     /**
81      * Invalidate previously-resolved shard information. This method is invoked when a timeout is detected
82      * and the information may need to be refreshed.
83      *
84      * @param info Previous promise of backend information
85      */
86     protected abstract void invalidateBackendInfo(@Nonnull CompletionStage<? extends BackendInfo> info);
87
88     // This is what the client needs to start processing. For as long as we do not have this, we should not complete
89     // this stage until we have this information
90     final CompletionStage<? extends T> getBackendInfo(final Long cookie) {
91         return lookupBackend(cookie);
92     }
93
94     private CompletableFuture<T> lookupBackend(final Long cookie) {
95         return backends.computeIfAbsent(Preconditions.checkNotNull(cookie), this::resolveBackendInfo);
96     }
97 }