blob: 53125a728530b517313ee00fe16cd33f4bdd9a9e [file] [log] [blame]
Simon Huntd2747a02015-04-30 22:41:16 -07001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2015-present Open Networking Foundation
Simon Huntd2747a02015-04-30 22:41:16 -07003 *
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 */
16package org.onosproject.ui;
17
18import com.fasterxml.jackson.databind.ObjectMapper;
Simon Huntda580882015-05-12 20:58:18 -070019import com.fasterxml.jackson.databind.node.ArrayNode;
Simon Huntd2747a02015-04-30 22:41:16 -070020import com.fasterxml.jackson.databind.node.ObjectNode;
21import org.onlab.osgi.ServiceDirectory;
Alan Deikman8d858752017-02-14 18:08:29 -080022import org.onosproject.codec.CodecContext;
23import org.onosproject.codec.CodecService;
24import org.onosproject.codec.JsonCodec;
Simon Hunt879ce452017-08-10 23:32:00 -070025import org.onosproject.ui.lion.LionBundle;
Simon Huntc54cd1b2015-05-11 13:43:44 -070026import org.slf4j.Logger;
27import org.slf4j.LoggerFactory;
Simon Huntd2747a02015-04-30 22:41:16 -070028
29import java.util.Collection;
30import java.util.Collections;
31import java.util.HashMap;
32import java.util.Map;
33import java.util.Set;
34
35import static com.google.common.base.Preconditions.checkArgument;
36import static com.google.common.base.Preconditions.checkNotNull;
37
38/**
Simon Huntda580882015-05-12 20:58:18 -070039 * Abstraction of an entity capable of processing JSON messages from the user
Simon Huntd2747a02015-04-30 22:41:16 -070040 * interface client.
41 * <p>
Simon Huntda580882015-05-12 20:58:18 -070042 * The message structure is:
Simon Huntd2747a02015-04-30 22:41:16 -070043 * </p>
44 * <pre>
45 * {
Simon Hunt8a0429a2017-01-06 16:52:47 -080046 * "event": "<em>event-type</em>",
Simon Huntd2747a02015-04-30 22:41:16 -070047 * "payload": {
48 * <em>arbitrary JSON object structure</em>
49 * }
50 * }
51 * </pre>
Simon Huntda580882015-05-12 20:58:18 -070052 * On {@link #init initialization} the handler will create and cache
53 * {@link RequestHandler} instances, each of which are bound to a particular
54 * <em>event-type</em>. On {@link #process arrival} of a new message,
55 * the <em>event-type</em> is determined, and the message dispatched to the
56 * corresponding <em>RequestHandler</em>'s
57 * {@link RequestHandler#process process} method.
Alan Deikman8d858752017-02-14 18:08:29 -080058 * <p>
59 * For convenience the implementation includes methods to obtain JSON
60 * generating objects (mapper, objectNode, arrayNode) as well as a
61 * JsonCodecContext for preparing and digesting messages to the UI
62 * client.
Simon Huntd2747a02015-04-30 22:41:16 -070063 */
Simon Hunta0ddb022015-05-01 09:53:01 -070064public abstract class UiMessageHandler {
Simon Huntd2747a02015-04-30 22:41:16 -070065
Simon Huntc54cd1b2015-05-11 13:43:44 -070066 private final Logger log = LoggerFactory.getLogger(getClass());
Simon Huntd2747a02015-04-30 22:41:16 -070067 private final Map<String, RequestHandler> handlerMap = new HashMap<>();
Simon Hunt879ce452017-08-10 23:32:00 -070068 private final Map<String, LionBundle> cachedLionBundles = new HashMap<>();
Thomas Vachuska26be4f32016-03-31 01:10:27 -070069
Simon Huntda580882015-05-12 20:58:18 -070070 private final ObjectMapper mapper = new ObjectMapper();
Simon Huntd2747a02015-04-30 22:41:16 -070071
72 private UiConnection connection;
73 private ServiceDirectory directory;
74
Alan Deikman8d858752017-02-14 18:08:29 -080075 private MessageCodecContext codecContext;
Simon Huntd2747a02015-04-30 22:41:16 -070076
Simon Huntd2747a02015-04-30 22:41:16 -070077 /**
Simon Huntda580882015-05-12 20:58:18 -070078 * Subclasses must create and return the collection of request handlers
79 * for the message types they handle.
80 * <p>
81 * Note that request handlers should be stateless. When we are
82 * {@link #destroy destroyed}, we will simply drop our references to them
83 * and allow them to be garbage collected.
Simon Huntd2747a02015-04-30 22:41:16 -070084 *
85 * @return the message handler instances
86 */
Simon Huntda580882015-05-12 20:58:18 -070087 protected abstract Collection<RequestHandler> createRequestHandlers();
Simon Huntd2747a02015-04-30 22:41:16 -070088
89 /**
90 * Returns the set of message types which this handler is capable of
91 * processing.
92 *
93 * @return set of message types
94 */
95 public Set<String> messageTypes() {
96 return Collections.unmodifiableSet(handlerMap.keySet());
97 }
98
99 /**
100 * Processes a JSON message from the user interface client.
101 *
102 * @param message JSON message
103 */
104 public void process(ObjectNode message) {
105 String type = JsonUtils.eventType(message);
Simon Huntd2747a02015-04-30 22:41:16 -0700106 ObjectNode payload = JsonUtils.payload(message);
Simon Hunt8a0429a2017-01-06 16:52:47 -0800107 exec(type, payload);
Simon Huntd2747a02015-04-30 22:41:16 -0700108 }
109
110 /**
111 * Finds the appropriate handler and executes the process method.
112 *
113 * @param eventType event type
Simon Huntd2747a02015-04-30 22:41:16 -0700114 * @param payload message payload
115 */
Simon Hunt8a0429a2017-01-06 16:52:47 -0800116 void exec(String eventType, ObjectNode payload) {
Simon Huntda580882015-05-12 20:58:18 -0700117 RequestHandler requestHandler = handlerMap.get(eventType);
118 if (requestHandler != null) {
Simon Hunt8a0429a2017-01-06 16:52:47 -0800119 requestHandler.process(payload);
Simon Hunte05cae42015-07-23 17:35:24 -0700120 } else {
121 log.warn("no request handler for event type {}", eventType);
Simon Hunta0ddb022015-05-01 09:53:01 -0700122 }
123 }
124
Simon Huntd2747a02015-04-30 22:41:16 -0700125 /**
126 * Initializes the handler with the user interface connection and
127 * service directory context.
128 *
129 * @param connection user interface connection
130 * @param directory service directory
131 */
132 public void init(UiConnection connection, ServiceDirectory directory) {
133 this.connection = connection;
134 this.directory = directory;
Simon Huntda580882015-05-12 20:58:18 -0700135
136 Collection<RequestHandler> handlers = createRequestHandlers();
137 checkNotNull(handlers, "Handlers cannot be null");
138 checkArgument(!handlers.isEmpty(), "Handlers cannot be empty");
139
140 for (RequestHandler h : handlers) {
141 h.setParent(this);
142 handlerMap.put(h.eventType(), h);
143 }
Simon Huntd2747a02015-04-30 22:41:16 -0700144 }
145
146 /**
147 * Destroys the message handler context.
148 */
149 public void destroy() {
150 this.connection = null;
151 this.directory = null;
Simon Huntda580882015-05-12 20:58:18 -0700152 handlerMap.clear();
Simon Huntd2747a02015-04-30 22:41:16 -0700153 }
154
155 /**
156 * Returns the user interface connection with which this handler was primed.
157 *
158 * @return user interface connection
159 */
160 public UiConnection connection() {
161 return connection;
162 }
163
164 /**
Simon Hunt8add9ee2016-09-20 17:05:07 -0700165 * Returns the service directory with which this handler was primed.
Simon Huntd2747a02015-04-30 22:41:16 -0700166 *
Simon Hunt8add9ee2016-09-20 17:05:07 -0700167 * @return service directory
Simon Huntd2747a02015-04-30 22:41:16 -0700168 */
169 public ServiceDirectory directory() {
170 return directory;
171 }
172
173 /**
Simon Hunt8add9ee2016-09-20 17:05:07 -0700174 * Returns an implementation of the specified service class.
Simon Huntd2747a02015-04-30 22:41:16 -0700175 *
176 * @param serviceClass service class
177 * @param <T> type of service
178 * @return implementation class
179 * @throws org.onlab.osgi.ServiceNotFoundException if no implementation found
180 */
181 protected <T> T get(Class<T> serviceClass) {
182 return directory.get(serviceClass);
183 }
184
Simon Huntda580882015-05-12 20:58:18 -0700185 /**
Simon Hunt879ce452017-08-10 23:32:00 -0700186 * Returns the set of identifiers for localization bundles that the
187 * message handler would like injected into itself, so that it can use
188 * those bundles in composing localized data to ship to the client.
189 * <p>
190 * This default implementation returns an empty set.
191 * <p>
192 * Subclasses that wish to have localization bundles injected should
193 * override this method and return the set of bundle identifiers.
194 *
195 * @return the set of identifiers of required localization bundles
196 */
197 public Set<String> requiredLionBundles() {
198 return Collections.emptySet();
199 }
200
201 /**
202 * Invoked during initialization to cache any requested localization
203 * bundles in the handler's context, so that it may subsequently look
204 * up localization strings when composing data for the client.
205 *
206 * @param bundle the bundle to cache
207 */
208 public void cacheLionBundle(LionBundle bundle) {
209 cachedLionBundles.put(bundle.id(), bundle);
210 }
211
212 /**
213 * Returns the localization bundle with the given identifier, if we
214 * requested to have it cached during initialization; null otherwise.
215 *
216 * @param id the lion bundle identifier
217 * @return the associated lion bundle
218 */
219 protected LionBundle getLionBundle(String id) {
220 return cachedLionBundles.get(id);
221 }
222
223 /**
Simon Huntda580882015-05-12 20:58:18 -0700224 * Returns a freshly minted object node.
225 *
226 * @return new object node
227 */
228 protected ObjectNode objectNode() {
229 return mapper.createObjectNode();
230 }
231
232 /**
233 * Returns a freshly minted array node.
234 *
235 * @return new array node
236 */
237 protected ArrayNode arrayNode() {
238 return mapper.createArrayNode();
239 }
Simon Hunt52560662015-08-27 22:46:44 -0700240
241 /**
242 * Sends the specified data to the client.
243 * It is expected that the data is in the prescribed JSON format for
244 * events to the client.
245 *
246 * @param data data to be sent
247 */
248 protected synchronized void sendMessage(ObjectNode data) {
249 UiConnection connection = connection();
250 if (connection != null) {
251 connection.sendMessage(data);
252 }
253 }
Alan Deikman8d858752017-02-14 18:08:29 -0800254
255 /**
256 * Obtain a CodecContext to be used in encoding and decoding objects
257 * that have a registered JsonCodec for their class. This method
258 * instantiates a private inner class which is returned on
259 * subsequent calls.
260 *
261 * @return a CodecContext.
262 */
263 protected CodecContext getJsonCodecContext() {
264 if (codecContext != null) {
265 return codecContext;
266 }
267 codecContext = new MessageCodecContext();
268 return codecContext;
269 }
270
271 private class MessageCodecContext implements CodecContext {
272
273 CodecService cs = get(CodecService.class);
274
275 @Override
276 public ObjectMapper mapper() {
277 return mapper;
278 }
279
280 @Override
281 public <T> JsonCodec<T> codec(Class<T> entityClass) {
282 return cs.getCodec(entityClass);
283 }
284
285 @Override
286 public <T> T getService(Class<T> serviceClass) {
287 return get(serviceClass);
288 }
289 }
Simon Huntd2747a02015-04-30 22:41:16 -0700290}