summaryrefslogtreecommitdiffstats
path: root/java/jvpp-registry/io/fd/vpp/jvpp/JVppRegistryImpl.java
blob: baef14c3865ba4f1f9a97b0700a2f7d165d66165 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
/*
 * Copyright (c) 2016 Cisco and/or its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at:
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.fd.vpp.jvpp;

import static java.util.Objects.requireNonNull;

import io.fd.vpp.jvpp.callback.ControlPingCallback;
import io.fd.vpp.jvpp.callback.JVppCallback;
import io.fd.vpp.jvpp.dto.ControlPingReply;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Default implementation of JVppRegistry.
 */
public final class JVppRegistryImpl implements JVppRegistry, ControlPingCallback {

    private static final Logger LOG = Logger.getLogger(JVppRegistryImpl.class.getName());

    private final VppJNIConnection connection;
    // Unguarded concurrent map, no race conditions expected on top of that
    private final Map<String, JVppCallback> pluginRegistry;
    // Guarded by self
    private final Map<Integer, ControlPingCallback> pingCalls;

    public JVppRegistryImpl(final String clientName) throws IOException {
        connection = new VppJNIConnection(clientName);
        connection.connect();
        pluginRegistry = new ConcurrentHashMap<>();
        pingCalls = new HashMap<>();
    }

    public JVppRegistryImpl(final String clientName, final String shmPrefix) throws IOException {
        connection = new VppJNIConnection(clientName, shmPrefix);
        connection.connect();
        pluginRegistry = new ConcurrentHashMap<>();
        pingCalls = new HashMap<>();
    }

    @Override
    public VppConnection getConnection() {
        return connection;
    }

    @Override
    public void register(final JVpp jvpp, final JVppCallback callback) {
        requireNonNull(jvpp, "jvpp should not be null");
        requireNonNull(callback, "Callback should not be null");
        final String name = jvpp.getClass().getName();
        if (pluginRegistry.containsKey(name)) {
            throw new IllegalArgumentException(
                String.format("Callback for plugin %s was already registered", name));
        }
        jvpp.init(this, callback, connection.getConnectionInfo().queueAddress,
            connection.getConnectionInfo().clientIndex);
        pluginRegistry.put(name, callback);
    }

    @Override
    public void unregister(final String name) {
        requireNonNull(name, "Plugin name should not be null");
        final JVppCallback previous = pluginRegistry.remove(name);
        assertPluginWasRegistered(name, previous);
    }

    @Override
    public JVppCallback get(final String name) {
        requireNonNull(name, "Plugin name should not be null");
        JVppCallback value = pluginRegistry.get(name);
        assertPluginWasRegistered(name, value);
        return value;
    }

    private native int controlPing0() throws VppInvocationException;

    @Override
    public int controlPing(final Class<? extends JVpp> clazz) throws VppInvocationException {
        connection.checkActive();
        final String name = clazz.getName();

        final ControlPingCallback callback = (ControlPingCallback) pluginRegistry.get(clazz.getName());
        assertPluginWasRegistered(name, callback);

        // controlPing0 is sending function and can go to waiting in case of e. g. full queue
        // because of that it cant be in same synchronization block as used by reply handler function
        int context = controlPing0();
        if (context < 0) {
            throw new VppInvocationException("controlPing", context);
        }

        synchronized (pingCalls) {
            // if callback is in map it's because reply was already received
            EarlyControlPingReply earlyReplyCallback = (EarlyControlPingReply) pingCalls.remove(context);
            if(earlyReplyCallback == null) {
                pingCalls.put(context, callback);
            } else {
                callback.onControlPingReply(earlyReplyCallback.getReply());
            }
        }

        return context;
    }

    @Override
    public void onControlPingReply(final ControlPingReply reply) {
        final ControlPingCallback callback;
        synchronized (pingCalls) {
            callback = pingCalls.remove(reply.context);
            if (callback == null) {
                // reply received early, because we don't know callback to call
                // we wrap the reply and let the sender to call it
                pingCalls.put(reply.context, new EarlyControlPingReply(reply));
                return;
            }
        }
        // pass the reply to the callback registered by the ping caller
        callback.onControlPingReply(reply);
    }

    @Override
    public void onError(final VppCallbackException ex) {
        final int ctxId = ex.getCtxId();
        final ControlPingCallback callback;

        synchronized (pingCalls) {
            callback = pingCalls.get(ctxId);
        }
        if (callback == null) {
            LOG.log(Level.WARNING, "No callback was registered for reply id={0} ", ctxId);
            return;
        }
        // pass the error to the callback registered by the ping caller
        callback.onError(ex);
    }

    private static void assertPluginWasRegistered(final String name, final JVppCallback value) {
        if (value == null) {
            throw new IllegalArgumentException(String.format("Callback for plugin %s is not registered", name));
        }
    }

    @Override
    public void close() throws Exception {
        connection.close();
    }

    private static class EarlyControlPingReply implements ControlPingCallback {

        private final ControlPingReply reply;

        public EarlyControlPingReply(final ControlPingReply reply) {
            this.reply = reply;
        }

        public ControlPingReply getReply() {
            return reply;
        }

        @Override
        public void onError(VppCallbackException ex) {
            throw new IllegalStateException("Calling onError in EarlyControlPingReply");
        }

        @Override
        public void onControlPingReply(ControlPingReply reply) {
            throw new IllegalStateException("Calling onControlPingReply in EarlyControlPingReply");
        }
    }
}