Move java api to extras/
[vpp.git] / extras / japi / java / jvpp-registry / io / fd / vpp / jvpp / future / AbstractFutureJVppInvoker.java
1 /*
2  * Copyright (c) 2016 Cisco and/or its affiliates.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at:
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package io.fd.vpp.jvpp.future;
18
19
20 import java.util.Map;
21 import java.util.Objects;
22 import java.util.concurrent.CompletableFuture;
23 import java.util.concurrent.CompletionStage;
24
25 import io.fd.vpp.jvpp.JVpp;
26 import io.fd.vpp.jvpp.JVppRegistry;
27 import io.fd.vpp.jvpp.VppInvocationException;
28 import io.fd.vpp.jvpp.dto.JVppDump;
29 import io.fd.vpp.jvpp.dto.JVppReply;
30 import io.fd.vpp.jvpp.dto.JVppReplyDump;
31 import io.fd.vpp.jvpp.dto.JVppRequest;
32
33 /**
34  * Future facade on top of JVpp
35  */
36 public abstract class AbstractFutureJVppInvoker implements FutureJVppInvoker {
37
38     private final JVpp jvpp;
39     private final JVppRegistry registry;
40
41     /**
42      * Guarded by self
43      */
44     private final Map<Integer, CompletableFuture<? extends JVppReply<?>>> requests;
45
46     protected AbstractFutureJVppInvoker(final JVpp jvpp, final JVppRegistry registry,
47                                         final Map<Integer, CompletableFuture<? extends JVppReply<?>>> requestMap) {
48         this.jvpp =  Objects.requireNonNull(jvpp, "jvpp should not be null");
49         this.registry =  Objects.requireNonNull(registry, "registry should not be null");
50         // Request map represents the shared state between this facade and it's callback
51         // where facade puts futures in and callback completes + removes them
52         this.requests = Objects.requireNonNull(requestMap, "Null requestMap");
53     }
54
55     protected final Map<Integer, CompletableFuture<? extends JVppReply<?>>> getRequests() {
56         synchronized (requests) {
57             return requests;
58         }
59     }
60
61     // TODO use Optional in Future, java8
62
63     @Override
64     @SuppressWarnings("unchecked")
65     public <REQ extends JVppRequest, REPLY extends JVppReply<REQ>> CompletionStage<REPLY> send(REQ req) {
66         try {
67             // jvpp.send() can go to waiting state if sending queue is full, putting it into same
68             // synchronization block as used by receiving part (synchronized(requests)) can lead
69             // to deadlock between these two sides or at least slowing sending process by slow
70             // reader
71             final CompletableFuture<REPLY> replyCompletableFuture;
72             final int contextId = jvpp.send(req);
73
74             if(req instanceof JVppDump) {
75                 throw new IllegalArgumentException("Send with empty reply dump has to be used in case of dump calls");
76             }
77
78             synchronized(requests) {
79                 CompletableFuture<? extends JVppReply<?>> replyFuture = requests.get(contextId);
80                 if (replyFuture == null) {
81                     // reply not yet received, put new future into map
82                     replyCompletableFuture = new CompletableFuture<>();
83                     requests.put(contextId, replyCompletableFuture);
84                 } else {
85                     // reply already received (should be completed by reader),
86                     // remove future from map and return it to caller
87                     replyCompletableFuture = (CompletableFuture<REPLY>) replyFuture;
88                     requests.remove(contextId);
89                 }
90             }
91
92             // TODO in case of timeouts/missing replies, requests from the map are not removed
93             // consider adding cancel method, that would remove requests from the map and cancel
94             // associated replyCompletableFuture
95
96             return replyCompletableFuture;
97         } catch (VppInvocationException ex) {
98             final CompletableFuture<REPLY> replyCompletableFuture = new CompletableFuture<>();
99             replyCompletableFuture.completeExceptionally(ex);
100             return replyCompletableFuture;
101         }
102     }
103
104     @Override
105     @SuppressWarnings("unchecked")
106     public <REQ extends JVppRequest, REPLY extends JVppReply<REQ>, DUMP extends JVppReplyDump<REQ, REPLY>> CompletionStage<DUMP> send(
107             REQ req, DUMP emptyReplyDump) {
108       try {
109           // jvpp.send() and registry.controlPing() can go to waiting state if sending queue is full,
110           // putting it into same synchronization block as used by receiving part (synchronized(requests))
111           // can lead to deadlock between these two sides or at least slowing sending process by slow reader
112           final CompletableDumpFuture<DUMP> replyDumpFuture;
113           final int contextId = jvpp.send(req);
114
115           if(!(req instanceof JVppDump)) {
116               throw new IllegalArgumentException("Send without empty reply dump has to be used in case of regular calls");
117           }
118
119           synchronized(requests) {
120               CompletableFuture<? extends JVppReply<?>> replyFuture = requests.get(contextId);
121               if (replyFuture == null) {
122                   // reply not received yet, put new future to map
123                   replyDumpFuture = new CompletableDumpFuture<>(contextId, emptyReplyDump);
124                   requests.put(contextId, replyDumpFuture);
125               } else {
126                   // reply already received, save existing future
127                   replyDumpFuture = (CompletableDumpFuture<DUMP>) replyFuture;
128               }
129           }
130
131           final int pingId = registry.controlPing(jvpp.getClass());
132
133           synchronized(requests) {
134               if (requests.remove(pingId) == null) {
135                   // reply not received yet, put future into map under pingId
136                   requests.put(pingId, replyDumpFuture);
137               } else {
138                   // reply already received, complete future
139                   // ping reply couldn't complete the future because it is not in map under
140                   // ping id
141                   replyDumpFuture.complete(replyDumpFuture.getReplyDump());
142                   requests.remove(contextId);
143               }
144           }
145
146           // TODO in case of timeouts/missing replies, requests from the map are not removed
147           // consider adding cancel method, that would remove requests from the map and cancel
148           // associated replyCompletableFuture
149
150           return replyDumpFuture;
151       } catch (VppInvocationException ex) {
152           final CompletableFuture<DUMP> replyCompletableFuture = new CompletableFuture<>();
153           replyCompletableFuture.completeExceptionally(ex);
154           return replyCompletableFuture;
155       }
156     }
157
158     public static final class CompletableDumpFuture<T extends JVppReplyDump<?, ?>> extends CompletableFuture<T> {
159         private final T replyDump;
160         private final int contextId;
161
162         public CompletableDumpFuture(final int contextId, final T emptyDump) {
163             this.contextId = contextId;
164             this.replyDump = emptyDump;
165         }
166
167         public int getContextId() {
168             return contextId;
169         }
170
171         public T getReplyDump() {
172             return replyDump;
173         }
174     }
175
176     @Override
177     public void close() throws Exception {
178         jvpp.close();
179     }
180 }