blob: c38d741485ac8f4861732ee184e6365e790a024b [file] [log] [blame]
Simon Huntd2747a02015-04-30 22:41:16 -07001/*
Brian O'Connor5ab426f2016-04-09 01:19:45 -07002 * Copyright 2015-present Open Networking Laboratory
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 Huntc54cd1b2015-05-11 13:43:44 -070025import org.slf4j.Logger;
26import org.slf4j.LoggerFactory;
Simon Huntd2747a02015-04-30 22:41:16 -070027
28import java.util.Collection;
29import java.util.Collections;
30import java.util.HashMap;
31import java.util.Map;
32import java.util.Set;
33
34import static com.google.common.base.Preconditions.checkArgument;
35import static com.google.common.base.Preconditions.checkNotNull;
36
37/**
Simon Huntda580882015-05-12 20:58:18 -070038 * Abstraction of an entity capable of processing JSON messages from the user
Simon Huntd2747a02015-04-30 22:41:16 -070039 * interface client.
40 * <p>
Simon Huntda580882015-05-12 20:58:18 -070041 * The message structure is:
Simon Huntd2747a02015-04-30 22:41:16 -070042 * </p>
43 * <pre>
44 * {
Simon Hunt8a0429a2017-01-06 16:52:47 -080045 * "event": "<em>event-type</em>",
Simon Huntd2747a02015-04-30 22:41:16 -070046 * "payload": {
47 * <em>arbitrary JSON object structure</em>
48 * }
49 * }
50 * </pre>
Simon Huntda580882015-05-12 20:58:18 -070051 * On {@link #init initialization} the handler will create and cache
52 * {@link RequestHandler} instances, each of which are bound to a particular
53 * <em>event-type</em>. On {@link #process arrival} of a new message,
54 * the <em>event-type</em> is determined, and the message dispatched to the
55 * corresponding <em>RequestHandler</em>'s
56 * {@link RequestHandler#process process} method.
Alan Deikman8d858752017-02-14 18:08:29 -080057 * <p>
58 * For convenience the implementation includes methods to obtain JSON
59 * generating objects (mapper, objectNode, arrayNode) as well as a
60 * JsonCodecContext for preparing and digesting messages to the UI
61 * client.
Simon Huntd2747a02015-04-30 22:41:16 -070062 */
Simon Hunta0ddb022015-05-01 09:53:01 -070063public abstract class UiMessageHandler {
Simon Huntd2747a02015-04-30 22:41:16 -070064
Simon Huntc54cd1b2015-05-11 13:43:44 -070065 private final Logger log = LoggerFactory.getLogger(getClass());
Simon Huntd2747a02015-04-30 22:41:16 -070066 private final Map<String, RequestHandler> handlerMap = new HashMap<>();
Thomas Vachuska26be4f32016-03-31 01:10:27 -070067
Simon Huntda580882015-05-12 20:58:18 -070068 private final ObjectMapper mapper = new ObjectMapper();
Simon Huntd2747a02015-04-30 22:41:16 -070069
70 private UiConnection connection;
71 private ServiceDirectory directory;
72
Alan Deikman8d858752017-02-14 18:08:29 -080073 private MessageCodecContext codecContext;
Simon Huntd2747a02015-04-30 22:41:16 -070074
Simon Huntd2747a02015-04-30 22:41:16 -070075 /**
Simon Huntda580882015-05-12 20:58:18 -070076 * Subclasses must create and return the collection of request handlers
77 * for the message types they handle.
78 * <p>
79 * Note that request handlers should be stateless. When we are
80 * {@link #destroy destroyed}, we will simply drop our references to them
81 * and allow them to be garbage collected.
Simon Huntd2747a02015-04-30 22:41:16 -070082 *
83 * @return the message handler instances
84 */
Simon Huntda580882015-05-12 20:58:18 -070085 protected abstract Collection<RequestHandler> createRequestHandlers();
Simon Huntd2747a02015-04-30 22:41:16 -070086
87 /**
88 * Returns the set of message types which this handler is capable of
89 * processing.
90 *
91 * @return set of message types
92 */
93 public Set<String> messageTypes() {
94 return Collections.unmodifiableSet(handlerMap.keySet());
95 }
96
97 /**
98 * Processes a JSON message from the user interface client.
99 *
100 * @param message JSON message
101 */
102 public void process(ObjectNode message) {
103 String type = JsonUtils.eventType(message);
Simon Huntd2747a02015-04-30 22:41:16 -0700104 ObjectNode payload = JsonUtils.payload(message);
Simon Hunt8a0429a2017-01-06 16:52:47 -0800105 exec(type, payload);
Simon Huntd2747a02015-04-30 22:41:16 -0700106 }
107
108 /**
109 * Finds the appropriate handler and executes the process method.
110 *
111 * @param eventType event type
Simon Huntd2747a02015-04-30 22:41:16 -0700112 * @param payload message payload
113 */
Simon Hunt8a0429a2017-01-06 16:52:47 -0800114 void exec(String eventType, ObjectNode payload) {
Simon Huntda580882015-05-12 20:58:18 -0700115 RequestHandler requestHandler = handlerMap.get(eventType);
116 if (requestHandler != null) {
Simon Hunt8a0429a2017-01-06 16:52:47 -0800117 requestHandler.process(payload);
Simon Hunte05cae42015-07-23 17:35:24 -0700118 } else {
119 log.warn("no request handler for event type {}", eventType);
Simon Hunta0ddb022015-05-01 09:53:01 -0700120 }
121 }
122
Simon Huntd2747a02015-04-30 22:41:16 -0700123 /**
124 * Initializes the handler with the user interface connection and
125 * service directory context.
126 *
127 * @param connection user interface connection
128 * @param directory service directory
129 */
130 public void init(UiConnection connection, ServiceDirectory directory) {
131 this.connection = connection;
132 this.directory = directory;
Simon Huntda580882015-05-12 20:58:18 -0700133
134 Collection<RequestHandler> handlers = createRequestHandlers();
135 checkNotNull(handlers, "Handlers cannot be null");
136 checkArgument(!handlers.isEmpty(), "Handlers cannot be empty");
137
138 for (RequestHandler h : handlers) {
139 h.setParent(this);
140 handlerMap.put(h.eventType(), h);
141 }
Simon Huntd2747a02015-04-30 22:41:16 -0700142 }
143
144 /**
145 * Destroys the message handler context.
146 */
147 public void destroy() {
148 this.connection = null;
149 this.directory = null;
Simon Huntda580882015-05-12 20:58:18 -0700150 handlerMap.clear();
Simon Huntd2747a02015-04-30 22:41:16 -0700151 }
152
153 /**
154 * Returns the user interface connection with which this handler was primed.
155 *
156 * @return user interface connection
157 */
158 public UiConnection connection() {
159 return connection;
160 }
161
162 /**
Simon Hunt8add9ee2016-09-20 17:05:07 -0700163 * Returns the service directory with which this handler was primed.
Simon Huntd2747a02015-04-30 22:41:16 -0700164 *
Simon Hunt8add9ee2016-09-20 17:05:07 -0700165 * @return service directory
Simon Huntd2747a02015-04-30 22:41:16 -0700166 */
167 public ServiceDirectory directory() {
168 return directory;
169 }
170
171 /**
Simon Hunt8add9ee2016-09-20 17:05:07 -0700172 * Returns an implementation of the specified service class.
Simon Huntd2747a02015-04-30 22:41:16 -0700173 *
174 * @param serviceClass service class
175 * @param <T> type of service
176 * @return implementation class
177 * @throws org.onlab.osgi.ServiceNotFoundException if no implementation found
178 */
179 protected <T> T get(Class<T> serviceClass) {
180 return directory.get(serviceClass);
181 }
182
Simon Huntda580882015-05-12 20:58:18 -0700183 /**
184 * Returns a freshly minted object node.
185 *
186 * @return new object node
187 */
188 protected ObjectNode objectNode() {
189 return mapper.createObjectNode();
190 }
191
192 /**
193 * Returns a freshly minted array node.
194 *
195 * @return new array node
196 */
197 protected ArrayNode arrayNode() {
198 return mapper.createArrayNode();
199 }
Simon Hunt52560662015-08-27 22:46:44 -0700200
201 /**
202 * Sends the specified data to the client.
203 * It is expected that the data is in the prescribed JSON format for
204 * events to the client.
205 *
206 * @param data data to be sent
207 */
208 protected synchronized void sendMessage(ObjectNode data) {
209 UiConnection connection = connection();
210 if (connection != null) {
211 connection.sendMessage(data);
212 }
213 }
Alan Deikman8d858752017-02-14 18:08:29 -0800214
215 /**
216 * Obtain a CodecContext to be used in encoding and decoding objects
217 * that have a registered JsonCodec for their class. This method
218 * instantiates a private inner class which is returned on
219 * subsequent calls.
220 *
221 * @return a CodecContext.
222 */
223 protected CodecContext getJsonCodecContext() {
224 if (codecContext != null) {
225 return codecContext;
226 }
227 codecContext = new MessageCodecContext();
228 return codecContext;
229 }
230
231 private class MessageCodecContext implements CodecContext {
232
233 CodecService cs = get(CodecService.class);
234
235 @Override
236 public ObjectMapper mapper() {
237 return mapper;
238 }
239
240 @Override
241 public <T> JsonCodec<T> codec(Class<T> entityClass) {
242 return cs.getCodec(entityClass);
243 }
244
245 @Override
246 public <T> T getService(Class<T> serviceClass) {
247 return get(serviceClass);
248 }
249 }
Simon Huntd2747a02015-04-30 22:41:16 -0700250}