blob: b8c1cdfbc2b66d35013534943361fe0451cd743e [file] [log] [blame]
tom8cc5aa72014-09-19 15:14:43 -07001package org.onlab.onos.store.device.impl;
2
Yuta HIGUCHIb3b2ac42014-09-21 23:37:11 -07003import static com.google.common.base.Preconditions.checkArgument;
4import static com.google.common.base.Preconditions.checkNotNull;
5import static org.onlab.onos.net.device.DeviceEvent.Type.DEVICE_AVAILABILITY_CHANGED;
6import static org.onlab.onos.net.device.DeviceEvent.Type.DEVICE_MASTERSHIP_CHANGED;
7import static org.onlab.onos.net.device.DeviceEvent.Type.DEVICE_REMOVED;
8import static org.onlab.onos.net.device.DeviceEvent.Type.PORT_ADDED;
9import static org.onlab.onos.net.device.DeviceEvent.Type.PORT_REMOVED;
10import static org.onlab.onos.net.device.DeviceEvent.Type.PORT_UPDATED;
11import static org.slf4j.LoggerFactory.getLogger;
tom8cc5aa72014-09-19 15:14:43 -070012
Yuta HIGUCHIb3b2ac42014-09-21 23:37:11 -070013import java.net.URI;
tom8cc5aa72014-09-19 15:14:43 -070014import java.util.ArrayList;
15import java.util.Collections;
16import java.util.HashMap;
17import java.util.HashSet;
18import java.util.Iterator;
19import java.util.List;
20import java.util.Map;
21import java.util.Objects;
22import java.util.Set;
tom8cc5aa72014-09-19 15:14:43 -070023
Yuta HIGUCHIb3b2ac42014-09-21 23:37:11 -070024import org.apache.felix.scr.annotations.Activate;
25import org.apache.felix.scr.annotations.Component;
26import org.apache.felix.scr.annotations.Deactivate;
27import org.apache.felix.scr.annotations.Reference;
28import org.apache.felix.scr.annotations.ReferenceCardinality;
29import org.apache.felix.scr.annotations.Service;
30import org.onlab.onos.net.DefaultDevice;
31import org.onlab.onos.net.DefaultPort;
32import org.onlab.onos.net.Device;
33import org.onlab.onos.net.DeviceId;
34import org.onlab.onos.net.Element;
35import org.onlab.onos.net.MastershipRole;
36import org.onlab.onos.net.Port;
37import org.onlab.onos.net.PortNumber;
38import org.onlab.onos.net.device.DeviceDescription;
39import org.onlab.onos.net.device.DeviceEvent;
40import org.onlab.onos.net.device.DeviceStore;
41import org.onlab.onos.net.device.PortDescription;
42import org.onlab.onos.net.provider.ProviderId;
tomdc66b382014-09-22 17:05:47 -070043import org.onlab.onos.store.StoreService;
Yuta HIGUCHIb3b2ac42014-09-21 23:37:11 -070044import org.onlab.util.KryoPool;
45import org.slf4j.Logger;
46
47import com.google.common.base.Optional;
48import com.google.common.cache.CacheBuilder;
49import com.google.common.cache.CacheLoader;
50import com.google.common.cache.LoadingCache;
51import com.google.common.collect.ImmutableList;
52import com.google.common.collect.ImmutableSet;
53import com.google.common.collect.ImmutableSet.Builder;
54import com.hazelcast.core.EntryAdapter;
55import com.hazelcast.core.EntryEvent;
56import com.hazelcast.core.HazelcastInstance;
57import com.hazelcast.core.IMap;
58import com.hazelcast.core.ISet;
59import com.hazelcast.core.MapEvent;
60
61import de.javakaffee.kryoserializers.URISerializer;
62
tom8cc5aa72014-09-19 15:14:43 -070063
64/**
65 * Manages inventory of infrastructure devices using Hazelcast-backed map.
66 */
67@Component(immediate = true)
68@Service
69public class DistributedDeviceStore implements DeviceStore {
70
Yuta HIGUCHIb3b2ac42014-09-21 23:37:11 -070071 /**
72 * An IMap EntryListener, which reflects each remote event to cache.
73 *
74 * @param <K> IMap key type after deserialization
75 * @param <V> IMap value type after deserialization
76 */
77 public static final class RemoteEventHandler<K, V> extends
78 EntryAdapter<byte[], byte[]> {
79
80 private LoadingCache<K, Optional<V>> cache;
81
82 /**
83 * Constructor.
84 *
85 * @param cache cache to update
86 */
87 public RemoteEventHandler(
88 LoadingCache<K, Optional<V>> cache) {
89 this.cache = checkNotNull(cache);
90 }
91
92 @Override
93 public void mapCleared(MapEvent event) {
94 cache.invalidateAll();
95 }
96
97 @Override
98 public void entryUpdated(EntryEvent<byte[], byte[]> event) {
99 cache.put(POOL.<K>deserialize(event.getKey()),
100 Optional.of(POOL.<V>deserialize(
101 event.getValue())));
102 }
103
104 @Override
105 public void entryRemoved(EntryEvent<byte[], byte[]> event) {
106 cache.invalidate(POOL.<DeviceId>deserialize(event.getKey()));
107 }
108
109 @Override
110 public void entryAdded(EntryEvent<byte[], byte[]> event) {
111 entryUpdated(event);
112 }
113 }
114
115 /**
116 * CacheLoader to wrap Map value with Optional,
117 * to handle negative hit on underlying IMap.
118 *
119 * @param <K> IMap key type after deserialization
120 * @param <V> IMap value type after deserialization
121 */
122 public static final class OptionalCacheLoader<K, V> extends
123 CacheLoader<K, Optional<V>> {
124
125 private IMap<byte[], byte[]> rawMap;
126
127 /**
128 * Constructor.
129 *
130 * @param rawMap underlying IMap
131 */
132 public OptionalCacheLoader(IMap<byte[], byte[]> rawMap) {
133 this.rawMap = checkNotNull(rawMap);
134 }
135
136 @Override
137 public Optional<V> load(K key) throws Exception {
138 byte[] keyBytes = serialize(key);
139 byte[] valBytes = rawMap.get(keyBytes);
140 if (valBytes == null) {
141 return Optional.absent();
142 }
143 V dev = deserialize(valBytes);
144 return Optional.of(dev);
145 }
146 }
147
tom8cc5aa72014-09-19 15:14:43 -0700148 private final Logger log = getLogger(getClass());
149
150 public static final String DEVICE_NOT_FOUND = "Device with ID %s not found";
151
Yuta HIGUCHIb3b2ac42014-09-21 23:37:11 -0700152 // FIXME Slice out types used in common to separate pool/namespace.
153 private static final KryoPool POOL = KryoPool.newBuilder()
154 .register(URI.class, new URISerializer())
155 .register(
156 ArrayList.class,
157
158 ProviderId.class,
159 Device.Type.class,
160
161 DeviceId.class,
162 DefaultDevice.class,
163 MastershipRole.class,
164 HashMap.class,
165 Port.class,
166 Element.class
167 )
168 .register(PortNumber.class, new PortNumberSerializer())
169 .register(DefaultPort.class, new DefaultPortSerializer())
170 .build()
171 .populate(10);
172
173 // private IMap<DeviceId, DefaultDevice> cache;
174 private IMap<byte[], byte[]> rawDevices;
175 private LoadingCache<DeviceId, Optional<DefaultDevice>> devices;
176
177 // private IMap<DeviceId, MastershipRole> roles;
178 private IMap<byte[], byte[]> rawRoles;
179 private LoadingCache<DeviceId, Optional<MastershipRole>> roles;
180
181 // private ISet<DeviceId> availableDevices;
182 private ISet<byte[]> availableDevices;
183
184 // TODO DevicePorts is very inefficient consider restructuring.
185 // private IMap<DeviceId, Map<PortNumber, Port>> devicePorts;
186 private IMap<byte[], byte[]> rawDevicePorts;
187 private LoadingCache<DeviceId, Optional<Map<PortNumber, Port>>> devicePorts;
188
189 // FIXME change to protected once we remove DistributedDeviceManagerTest.
190 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
tomdc66b382014-09-22 17:05:47 -0700191 protected StoreService storeService;
192
Yuta HIGUCHIb3b2ac42014-09-21 23:37:11 -0700193 /*protected*/public HazelcastInstance theInstance;
194
tom8cc5aa72014-09-19 15:14:43 -0700195
196 @Activate
197 public void activate() {
198 log.info("Started");
tomdc66b382014-09-22 17:05:47 -0700199 theInstance = storeService.getHazelcastInstance();
Yuta HIGUCHIb3b2ac42014-09-21 23:37:11 -0700200
201 // IMap event handler needs value
202 final boolean includeValue = true;
203
204 // TODO decide on Map name scheme to avoid collision
205 rawDevices = theInstance.getMap("devices");
206 devices = new AbsentInvalidatingLoadingCache<DeviceId, DefaultDevice>(
207 CacheBuilder.newBuilder()
208 .build(new OptionalCacheLoader<DeviceId, DefaultDevice>(rawDevices)));
209 // refresh/populate cache based on notification from other instance
210 rawDevices.addEntryListener(
211 new RemoteEventHandler<DeviceId, DefaultDevice>(devices),
212 includeValue);
213
214 rawRoles = theInstance.getMap("roles");
215 roles = new AbsentInvalidatingLoadingCache<DeviceId, MastershipRole>(
216 CacheBuilder.newBuilder()
217 .build(new OptionalCacheLoader<DeviceId, MastershipRole>(rawRoles)));
218 // refresh/populate cache based on notification from other instance
219 rawRoles.addEntryListener(
220 new RemoteEventHandler<DeviceId, MastershipRole>(roles),
221 includeValue);
222
223 // TODO cache avai
224 availableDevices = theInstance.getSet("availableDevices");
225
226 rawDevicePorts = theInstance.getMap("devicePorts");
227 devicePorts = new AbsentInvalidatingLoadingCache<DeviceId, Map<PortNumber, Port>>(
228 CacheBuilder.newBuilder()
229 .build(new OptionalCacheLoader<DeviceId, Map<PortNumber, Port>>(rawDevicePorts)));
230 // refresh/populate cache based on notification from other instance
231 rawDevicePorts.addEntryListener(
232 new RemoteEventHandler<DeviceId, Map<PortNumber, Port>>(devicePorts),
233 includeValue);
234
tom8cc5aa72014-09-19 15:14:43 -0700235 }
236
237 @Deactivate
238 public void deactivate() {
239 log.info("Stopped");
240 }
241
242 @Override
243 public int getDeviceCount() {
Yuta HIGUCHIb3b2ac42014-09-21 23:37:11 -0700244 // TODO IMap size or cache size?
245 return rawDevices.size();
tom8cc5aa72014-09-19 15:14:43 -0700246 }
247
248 @Override
249 public Iterable<Device> getDevices() {
Yuta HIGUCHIb3b2ac42014-09-21 23:37:11 -0700250// TODO Revisit if we ever need to do this.
251// log.info("{}:{}", rawMap.size(), cache.size());
252// if (rawMap.size() != cache.size()) {
253// for (Entry<byte[], byte[]> e : rawMap.entrySet()) {
254// final DeviceId key = deserialize(e.getKey());
255// final DefaultDevice val = deserialize(e.getValue());
256// cache.put(key, val);
257// }
258// }
259
260 // TODO builder v.s. copyOf. Guava semms to be using copyOf?
261 Builder<Device> builder = ImmutableSet.<Device>builder();
262 for (Optional<DefaultDevice> e : devices.asMap().values()) {
263 if (e.isPresent()) {
264 builder.add(e.get());
265 }
266 }
267 return builder.build();
tom8cc5aa72014-09-19 15:14:43 -0700268 }
269
270 @Override
271 public Device getDevice(DeviceId deviceId) {
Yuta HIGUCHIb3b2ac42014-09-21 23:37:11 -0700272 // TODO revisit if ignoring exception is safe.
273 return devices.getUnchecked(deviceId).orNull();
tom8cc5aa72014-09-19 15:14:43 -0700274 }
275
276 @Override
277 public DeviceEvent createOrUpdateDevice(ProviderId providerId, DeviceId deviceId,
278 DeviceDescription deviceDescription) {
Yuta HIGUCHIb3b2ac42014-09-21 23:37:11 -0700279 DefaultDevice device = devices.getUnchecked(deviceId).orNull();
tom8cc5aa72014-09-19 15:14:43 -0700280 if (device == null) {
281 return createDevice(providerId, deviceId, deviceDescription);
282 }
283 return updateDevice(providerId, device, deviceDescription);
284 }
285
286 // Creates the device and returns the appropriate event if necessary.
287 private DeviceEvent createDevice(ProviderId providerId, DeviceId deviceId,
288 DeviceDescription desc) {
289 DefaultDevice device = new DefaultDevice(providerId, deviceId, desc.type(),
290 desc.manufacturer(),
291 desc.hwVersion(), desc.swVersion(),
292 desc.serialNumber());
Yuta HIGUCHIb3b2ac42014-09-21 23:37:11 -0700293
tom8cc5aa72014-09-19 15:14:43 -0700294 synchronized (this) {
Yuta HIGUCHIb3b2ac42014-09-21 23:37:11 -0700295 final byte[] deviceIdBytes = serialize(deviceId);
296 rawDevices.put(deviceIdBytes, serialize(device));
297 devices.put(deviceId, Optional.of(device));
298
299 availableDevices.add(deviceIdBytes);
tom8cc5aa72014-09-19 15:14:43 -0700300
301 // For now claim the device as a master automatically.
Yuta HIGUCHIb3b2ac42014-09-21 23:37:11 -0700302 rawRoles.put(deviceIdBytes, serialize(MastershipRole.MASTER));
303 roles.put(deviceId, Optional.of(MastershipRole.MASTER));
tom8cc5aa72014-09-19 15:14:43 -0700304 }
305 return new DeviceEvent(DeviceEvent.Type.DEVICE_ADDED, device, null);
306 }
307
308 // Updates the device and returns the appropriate event if necessary.
309 private DeviceEvent updateDevice(ProviderId providerId, DefaultDevice device,
310 DeviceDescription desc) {
311 // We allow only certain attributes to trigger update
312 if (!Objects.equals(device.hwVersion(), desc.hwVersion()) ||
Yuta HIGUCHIb3b2ac42014-09-21 23:37:11 -0700313 !Objects.equals(device.swVersion(), desc.swVersion())) {
314
tom8cc5aa72014-09-19 15:14:43 -0700315 DefaultDevice updated = new DefaultDevice(providerId, device.id(),
316 desc.type(),
317 desc.manufacturer(),
318 desc.hwVersion(),
319 desc.swVersion(),
320 desc.serialNumber());
321 synchronized (this) {
Yuta HIGUCHIb3b2ac42014-09-21 23:37:11 -0700322 devices.put(device.id(), Optional.of(updated));
323 availableDevices.add(serialize(device.id()));
tom8cc5aa72014-09-19 15:14:43 -0700324 }
325 return new DeviceEvent(DeviceEvent.Type.DEVICE_UPDATED, device, null);
326 }
327
328 // Otherwise merely attempt to change availability
329 synchronized (this) {
Yuta HIGUCHIb3b2ac42014-09-21 23:37:11 -0700330 boolean added = availableDevices.add(serialize(device.id()));
tom8cc5aa72014-09-19 15:14:43 -0700331 return !added ? null :
332 new DeviceEvent(DEVICE_AVAILABILITY_CHANGED, device, null);
333 }
334 }
335
336 @Override
337 public DeviceEvent markOffline(DeviceId deviceId) {
338 synchronized (this) {
Yuta HIGUCHIb3b2ac42014-09-21 23:37:11 -0700339 Device device = devices.getUnchecked(deviceId).orNull();
340 boolean removed = device != null && availableDevices.remove(serialize(deviceId));
tom8cc5aa72014-09-19 15:14:43 -0700341 return !removed ? null :
342 new DeviceEvent(DEVICE_AVAILABILITY_CHANGED, device, null);
343 }
344 }
345
346 @Override
347 public List<DeviceEvent> updatePorts(DeviceId deviceId,
348 List<PortDescription> portDescriptions) {
349 List<DeviceEvent> events = new ArrayList<>();
350 synchronized (this) {
Yuta HIGUCHIb3b2ac42014-09-21 23:37:11 -0700351 Device device = devices.getUnchecked(deviceId).orNull();
tom8cc5aa72014-09-19 15:14:43 -0700352 checkArgument(device != null, DEVICE_NOT_FOUND, deviceId);
353 Map<PortNumber, Port> ports = getPortMap(deviceId);
354
355 // Add new ports
356 Set<PortNumber> processed = new HashSet<>();
357 for (PortDescription portDescription : portDescriptions) {
358 Port port = ports.get(portDescription.portNumber());
359 events.add(port == null ?
360 createPort(device, portDescription, ports) :
361 updatePort(device, port, portDescription, ports));
362 processed.add(portDescription.portNumber());
363 }
364
Yuta HIGUCHIb3b2ac42014-09-21 23:37:11 -0700365 updatePortMap(deviceId, ports);
366
tom8cc5aa72014-09-19 15:14:43 -0700367 events.addAll(pruneOldPorts(device, ports, processed));
368 }
369 return events;
370 }
371
372 // Creates a new port based on the port description adds it to the map and
373 // Returns corresponding event.
Yuta HIGUCHIb3b2ac42014-09-21 23:37:11 -0700374 //@GuardedBy("this")
tom8cc5aa72014-09-19 15:14:43 -0700375 private DeviceEvent createPort(Device device, PortDescription portDescription,
376 Map<PortNumber, Port> ports) {
377 DefaultPort port = new DefaultPort(device, portDescription.portNumber(),
378 portDescription.isEnabled());
379 ports.put(port.number(), port);
Yuta HIGUCHIb3b2ac42014-09-21 23:37:11 -0700380 updatePortMap(device.id(), ports);
tom8cc5aa72014-09-19 15:14:43 -0700381 return new DeviceEvent(PORT_ADDED, device, port);
382 }
383
Yuta HIGUCHIb3b2ac42014-09-21 23:37:11 -0700384 // Checks if the specified port requires update and if so, it replaces the
tom8cc5aa72014-09-19 15:14:43 -0700385 // existing entry in the map and returns corresponding event.
Yuta HIGUCHIb3b2ac42014-09-21 23:37:11 -0700386 //@GuardedBy("this")
tom8cc5aa72014-09-19 15:14:43 -0700387 private DeviceEvent updatePort(Device device, Port port,
388 PortDescription portDescription,
389 Map<PortNumber, Port> ports) {
390 if (port.isEnabled() != portDescription.isEnabled()) {
391 DefaultPort updatedPort =
392 new DefaultPort(device, portDescription.portNumber(),
393 portDescription.isEnabled());
394 ports.put(port.number(), updatedPort);
Yuta HIGUCHIb3b2ac42014-09-21 23:37:11 -0700395 updatePortMap(device.id(), ports);
tom8cc5aa72014-09-19 15:14:43 -0700396 return new DeviceEvent(PORT_UPDATED, device, port);
397 }
398 return null;
399 }
400
401 // Prunes the specified list of ports based on which ports are in the
402 // processed list and returns list of corresponding events.
Yuta HIGUCHIb3b2ac42014-09-21 23:37:11 -0700403 //@GuardedBy("this")
tom8cc5aa72014-09-19 15:14:43 -0700404 private List<DeviceEvent> pruneOldPorts(Device device,
405 Map<PortNumber, Port> ports,
406 Set<PortNumber> processed) {
407 List<DeviceEvent> events = new ArrayList<>();
408 Iterator<PortNumber> iterator = ports.keySet().iterator();
409 while (iterator.hasNext()) {
410 PortNumber portNumber = iterator.next();
411 if (!processed.contains(portNumber)) {
412 events.add(new DeviceEvent(PORT_REMOVED, device,
413 ports.get(portNumber)));
414 iterator.remove();
415 }
416 }
Yuta HIGUCHIb3b2ac42014-09-21 23:37:11 -0700417 if (!events.isEmpty()) {
418 updatePortMap(device.id(), ports);
419 }
tom8cc5aa72014-09-19 15:14:43 -0700420 return events;
421 }
422
423 // Gets the map of ports for the specified device; if one does not already
424 // exist, it creates and registers a new one.
Yuta HIGUCHIb3b2ac42014-09-21 23:37:11 -0700425 // WARN: returned value is a copy, changes made to the Map
426 // needs to be written back using updatePortMap
427 //@GuardedBy("this")
tom8cc5aa72014-09-19 15:14:43 -0700428 private Map<PortNumber, Port> getPortMap(DeviceId deviceId) {
Yuta HIGUCHIb3b2ac42014-09-21 23:37:11 -0700429 Map<PortNumber, Port> ports = devicePorts.getUnchecked(deviceId).orNull();
tom8cc5aa72014-09-19 15:14:43 -0700430 if (ports == null) {
431 ports = new HashMap<>();
Yuta HIGUCHIb3b2ac42014-09-21 23:37:11 -0700432 // this probably is waste of time in most cases.
433 updatePortMap(deviceId, ports);
tom8cc5aa72014-09-19 15:14:43 -0700434 }
435 return ports;
436 }
437
Yuta HIGUCHIb3b2ac42014-09-21 23:37:11 -0700438 //@GuardedBy("this")
439 private void updatePortMap(DeviceId deviceId, Map<PortNumber, Port> ports) {
440 rawDevicePorts.put(serialize(deviceId), serialize(ports));
441 devicePorts.put(deviceId, Optional.of(ports));
442 }
443
tom8cc5aa72014-09-19 15:14:43 -0700444 @Override
445 public DeviceEvent updatePortStatus(DeviceId deviceId,
446 PortDescription portDescription) {
447 synchronized (this) {
Yuta HIGUCHIb3b2ac42014-09-21 23:37:11 -0700448 Device device = devices.getUnchecked(deviceId).orNull();
tom8cc5aa72014-09-19 15:14:43 -0700449 checkArgument(device != null, DEVICE_NOT_FOUND, deviceId);
450 Map<PortNumber, Port> ports = getPortMap(deviceId);
451 Port port = ports.get(portDescription.portNumber());
452 return updatePort(device, port, portDescription, ports);
453 }
454 }
455
456 @Override
457 public List<Port> getPorts(DeviceId deviceId) {
Yuta HIGUCHIb3b2ac42014-09-21 23:37:11 -0700458 Map<PortNumber, Port> ports = devicePorts.getUnchecked(deviceId).orNull();
459 return ports == null ? Collections.<Port>emptyList() : ImmutableList.copyOf(ports.values());
tom8cc5aa72014-09-19 15:14:43 -0700460 }
461
462 @Override
463 public Port getPort(DeviceId deviceId, PortNumber portNumber) {
Yuta HIGUCHIb3b2ac42014-09-21 23:37:11 -0700464 Map<PortNumber, Port> ports = devicePorts.getUnchecked(deviceId).orNull();
tom8cc5aa72014-09-19 15:14:43 -0700465 return ports == null ? null : ports.get(portNumber);
466 }
467
468 @Override
469 public boolean isAvailable(DeviceId deviceId) {
Yuta HIGUCHIb3b2ac42014-09-21 23:37:11 -0700470 return availableDevices.contains(serialize(deviceId));
tom8cc5aa72014-09-19 15:14:43 -0700471 }
472
473 @Override
474 public MastershipRole getRole(DeviceId deviceId) {
Yuta HIGUCHIb3b2ac42014-09-21 23:37:11 -0700475 MastershipRole role = roles.getUnchecked(deviceId).orNull();
tom8cc5aa72014-09-19 15:14:43 -0700476 return role != null ? role : MastershipRole.NONE;
477 }
478
479 @Override
480 public DeviceEvent setRole(DeviceId deviceId, MastershipRole role) {
481 synchronized (this) {
482 Device device = getDevice(deviceId);
483 checkArgument(device != null, DEVICE_NOT_FOUND, deviceId);
Yuta HIGUCHIb3b2ac42014-09-21 23:37:11 -0700484 MastershipRole oldRole = deserialize(
485 rawRoles.put(serialize(deviceId), serialize(role)));
486 roles.put(deviceId, Optional.of(role));
tom8cc5aa72014-09-19 15:14:43 -0700487 return oldRole == role ? null :
488 new DeviceEvent(DEVICE_MASTERSHIP_CHANGED, device, null);
489 }
490 }
491
492 @Override
493 public DeviceEvent removeDevice(DeviceId deviceId) {
494 synchronized (this) {
Yuta HIGUCHIb3b2ac42014-09-21 23:37:11 -0700495 byte[] deviceIdBytes = serialize(deviceId);
496 rawRoles.remove(deviceIdBytes);
497 roles.invalidate(deviceId);
498
499 // TODO conditional remove?
500 Device device = deserialize(rawDevices.remove(deviceIdBytes));
501 devices.invalidate(deviceId);
tom8cc5aa72014-09-19 15:14:43 -0700502 return device == null ? null :
503 new DeviceEvent(DEVICE_REMOVED, device, null);
504 }
505 }
Yuta HIGUCHIb3b2ac42014-09-21 23:37:11 -0700506
507 // TODO cache serialized DeviceID if we suffer from serialization cost
508
509 private static byte[] serialize(final Object obj) {
510 return POOL.serialize(obj);
511 }
512
513 private static <T> T deserialize(final byte[] bytes) {
514 if (bytes == null) {
515 return null;
516 }
517 return POOL.deserialize(bytes);
518 }
519
tom8cc5aa72014-09-19 15:14:43 -0700520}