Skip to content

Commit

Permalink
Add way to add or remove object dynamically at client side
Browse files Browse the repository at this point in the history
  • Loading branch information
sbernard31 committed Jan 20, 2020
1 parent dd02d25 commit da2a39e
Show file tree
Hide file tree
Showing 9 changed files with 279 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,8 @@
package org.eclipse.leshan.client.californium;

import java.net.InetSocketAddress;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.eclipse.californium.core.CoapServer;
import org.eclipse.californium.core.network.config.NetworkConfig;
Expand All @@ -33,6 +31,8 @@
import org.eclipse.leshan.client.observer.LwM2mClientObserver;
import org.eclipse.leshan.client.observer.LwM2mClientObserverDispatcher;
import org.eclipse.leshan.client.resource.LwM2mObjectEnabler;
import org.eclipse.leshan.client.resource.LwM2mObjectTree;
import org.eclipse.leshan.client.resource.listener.ObjectsListenerAdapter;
import org.eclipse.leshan.core.californium.EndpointFactory;
import org.eclipse.leshan.core.node.codec.LwM2mNodeDecoder;
import org.eclipse.leshan.core.node.codec.LwM2mNodeEncoder;
Expand All @@ -47,39 +47,31 @@ public class LeshanClient {

private static final Logger LOG = LoggerFactory.getLogger(LeshanClient.class);

private final ConcurrentHashMap<Integer, LwM2mObjectEnabler> objectEnablers;

private final CoapServer clientSideServer;
private final CaliforniumLwM2mRequestSender requestSender;
private final RegistrationEngine engine;
private final BootstrapHandler bootstrapHandler;
private final LwM2mClientObserverDispatcher observers;
private LwM2mObjectTree objectTree;

private final CaliforniumEndpointsManager endpointsManager;

public LeshanClient(String endpoint, InetSocketAddress localAddress,
List<? extends LwM2mObjectEnabler> objectEnablers, NetworkConfig coapConfig, Builder dtlsConfigBuilder,
EndpointFactory endpointFactory, Map<String, String> additionalAttributes, LwM2mNodeEncoder encoder,
LwM2mNodeDecoder decoder) {
EndpointFactory endpointFactory, Map<String, String> additionalAttributes, final LwM2mNodeEncoder encoder,
final LwM2mNodeDecoder decoder) {

Validate.notNull(endpoint);
Validate.notEmpty(objectEnablers);
Validate.notNull(coapConfig);

// Create Object enablers
this.objectEnablers = new ConcurrentHashMap<>();
for (LwM2mObjectEnabler enabler : objectEnablers) {
if (this.objectEnablers.containsKey(enabler.getId())) {
throw new IllegalArgumentException(
String.format("There is several objectEnablers with the same id %d.", enabler.getId()));
}
this.objectEnablers.put(enabler.getId(), enabler);
}
// create ObjectTree
objectTree = new LwM2mObjectTree(objectEnablers);

// Create Client Observers
observers = new LwM2mClientObserverDispatcher();

bootstrapHandler = new BootstrapHandler(this.objectEnablers);
bootstrapHandler = new BootstrapHandler(objectTree.getObjectEnablers());

// Create CoAP Server
clientSideServer = new CoapServer(coapConfig) {
Expand All @@ -95,6 +87,19 @@ protected Resource createRoot() {
ObjectResource clientObject = new ObjectResource(enabler, bootstrapHandler, encoder, decoder);
clientSideServer.add(clientObject);
}
objectTree.addListener(new ObjectsListenerAdapter() {
@Override
public void objectAdded(LwM2mObjectEnabler object) {
ObjectResource clientObject = new ObjectResource(object, bootstrapHandler, encoder, decoder);
clientSideServer.add(clientObject);
}

@Override
public void objectRemoved(LwM2mObjectEnabler object) {
Resource resource = clientSideServer.getRoot().getChild(Integer.toString(object.getId()));
clientSideServer.remove(resource);
}
});

// Create CoAP resources needed for the bootstrap sequence
clientSideServer.add(new BootstrapResource(bootstrapHandler));
Expand All @@ -107,7 +112,7 @@ protected Resource createRoot() {
requestSender = new CaliforniumLwM2mRequestSender(endpointsManager);

// Create registration engine
engine = new RegistrationEngine(endpoint, this.objectEnablers, endpointsManager, requestSender,
engine = new RegistrationEngine(endpoint, objectTree.getObjectEnablers(), endpointsManager, requestSender,
bootstrapHandler, observers, additionalAttributes);

}
Expand Down Expand Up @@ -139,8 +144,8 @@ public void triggerRegistrationUpdate() {
engine.triggerRegistrationUpdate();
}

public Map<Integer, LwM2mObjectEnabler> getObjectEnablers() {
return Collections.unmodifiableMap(objectEnablers);
public LwM2mObjectTree getObjectTree() {
return objectTree;
}

public CoapServer getCoapServer() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import org.eclipse.leshan.Link;
import org.eclipse.leshan.LwM2mId;
import org.eclipse.leshan.client.request.ServerIdentity;
import org.eclipse.leshan.client.resource.listener.ObjectListener;
import org.eclipse.leshan.client.util.LinkFormatHelper;
import org.eclipse.leshan.core.model.ObjectModel;
import org.eclipse.leshan.core.model.ResourceModel;
Expand Down Expand Up @@ -61,6 +62,7 @@ public abstract class BaseObjectEnabler implements LwM2mObjectEnabler {

final int id;
private NotifySender notifySender;
private ObjectListener listener;
private ObjectModel objectModel;

public BaseObjectEnabler(int id, ObjectModel objectModel) {
Expand Down Expand Up @@ -382,6 +384,27 @@ public NotifySender getNotifySender() {
return notifySender;
}

@Override
public void setListener(ObjectListener listener) {
this.listener = listener;
}

protected ObjectListener getListener() {
return listener;
}

protected void fireInstancesAdded(int... instanceIds) {
if (listener != null) {
listener.objectInstancesAdded(this, instanceIds);
}
}

protected void fireInstancesRemoved(int... instanceIds) {
if (listener != null) {
listener.objectInstancesRemoved(this, instanceIds);
}
}

@Override
public ContentFormat getDefaultEncodingFormat(DownlinkRequest<?> request) {
return ContentFormat.DEFAULT;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.util.List;

import org.eclipse.leshan.client.request.ServerIdentity;
import org.eclipse.leshan.client.resource.listener.ObjectListener;
import org.eclipse.leshan.core.model.ObjectModel;
import org.eclipse.leshan.core.request.BootstrapDeleteRequest;
import org.eclipse.leshan.core.request.BootstrapWriteRequest;
Expand Down Expand Up @@ -75,5 +76,7 @@ public interface LwM2mObjectEnabler {

void setNotifySender(NotifySender sender);

void setListener(ObjectListener listener);

ContentFormat getDefaultEncodingFormat(DownlinkRequest<?> request);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/*******************************************************************************
* Copyright (c) 2019 Sierra Wireless and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.html.
*
* Contributors:
* Sierra Wireless - initial API and implementation
*******************************************************************************/
package org.eclipse.leshan.client.resource;

import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;

import org.eclipse.leshan.client.resource.listener.ObjectListener;
import org.eclipse.leshan.client.resource.listener.ObjectsListener;

public class LwM2mObjectTree {

private ObjectListener dispatcher = new ObjectListenerDispatcher();
private CopyOnWriteArrayList<ObjectsListener> listeners = new CopyOnWriteArrayList<>();
private ConcurrentHashMap<Integer, LwM2mObjectEnabler> objectEnablers = new ConcurrentHashMap<>();

public LwM2mObjectTree(LwM2mObjectEnabler... enablers) {
this(Arrays.asList(enablers));
}

public LwM2mObjectTree(Collection<? extends LwM2mObjectEnabler> enablers) {
for (LwM2mObjectEnabler enabler : enablers) {
LwM2mObjectEnabler previousEnabler = objectEnablers.putIfAbsent(enabler.getId(), enabler);
if (previousEnabler != null) {
throw new IllegalArgumentException(
String.format("Can not add 2 enablers for the same id %d", enabler.getId()));
}
}
for (LwM2mObjectEnabler enabler : enablers) {
enabler.setListener(dispatcher);
}
}

public void addListener(ObjectsListener listener) {
listeners.add(listener);
}

public void removedListener(ObjectsListener listener) {
listeners.remove(listener);
}

public Map<Integer, LwM2mObjectEnabler> getObjectEnablers() {
return Collections.unmodifiableMap(objectEnablers);
}

public LwM2mObjectEnabler getObjectEnabler(int id) {
return objectEnablers.get(id);
}

public void addObjectEnabler(LwM2mObjectEnabler enabler) {
LwM2mObjectEnabler previousEnabler = objectEnablers.putIfAbsent(enabler.getId(), enabler);
enabler.setListener(dispatcher);
if (previousEnabler != null) {
throw new IllegalArgumentException(
String.format("Can not add 2 enablers for the same id %d", enabler.getId()));
}
for (ObjectsListener listener : listeners) {
listener.objectAdded(enabler);
}
}

public void removeObjectEnabler(int objectId) {
LwM2mObjectEnabler removedEnabler = objectEnablers.remove(objectId);
if (removedEnabler != null) {
removedEnabler.setListener(null);
for (ObjectsListener listener : listeners) {
listener.objectRemoved(removedEnabler);
}
}
}

private class ObjectListenerDispatcher implements ObjectListener {
@Override
public void objectInstancesAdded(LwM2mObjectEnabler object, int... instanceIds) {
for (ObjectsListener listener : listeners) {
listener.objectInstancesAdded(object, instanceIds);
}
}

@Override
public void objectInstancesRemoved(LwM2mObjectEnabler object, int... instanceIds) {
for (ObjectsListener listener : listeners) {
listener.objectInstancesRemoved(object, instanceIds);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ public ObjectEnabler(int id, ObjectModel objectModel, Map<Integer, LwM2mInstance
this.instanceFactory = instanceFactory;
this.defaultContentFormat = defaultContentFormat;
for (Entry<Integer, LwM2mInstanceEnabler> entry : this.instances.entrySet()) {
addInstance(entry.getKey(), entry.getValue());
instances.put(entry.getKey(), entry.getValue());
listenInstance(entry.getValue(), entry.getKey());
}
}

Expand All @@ -90,14 +91,19 @@ public synchronized List<Integer> getAvailableResourceIds(int instanceId) {
public synchronized void addInstance(int instanceId, LwM2mInstanceEnabler newInstance) {
instances.put(instanceId, newInstance);
listenInstance(newInstance, instanceId);
fireInstancesAdded(instanceId);
}

public synchronized LwM2mInstanceEnabler getInstance(int instanceId) {
return instances.get(instanceId);
}

public synchronized LwM2mInstanceEnabler removeInstance(int instanceId) {
return instances.remove(instanceId);
LwM2mInstanceEnabler removedInstance = instances.remove(instanceId);
if (removedInstance != null) {
fireInstancesRemoved(removedInstance.getId());
}
return removedInstance;
}

@Override
Expand All @@ -114,6 +120,7 @@ protected CreateResponse doCreate(ServerIdentity identity, CreateRequest request
// add new instance to this object
instances.put(newInstance.getId(), newInstance);
listenInstance(newInstance, newInstance.getId());
fireInstancesAdded(newInstance.getId());

return CreateResponse
.success(new LwM2mPath(request.getPath().getObjectId(), newInstance.getId()).toString());
Expand All @@ -137,6 +144,8 @@ protected CreateResponse doCreate(ServerIdentity identity, CreateRequest request
}

// create the new instances
int[] instanceIds = new int[request.getObjectInstances().size()];
int i = 0;
for (LwM2mObjectInstance instance : request.getObjectInstances()) {
// create instance
LwM2mInstanceEnabler newInstance = createInstance(identity, instance.getId(),
Expand All @@ -145,7 +154,12 @@ protected CreateResponse doCreate(ServerIdentity identity, CreateRequest request
// add new instance to this object
instances.put(newInstance.getId(), newInstance);
listenInstance(newInstance, newInstance.getId());

// store instance ids
instanceIds[i] = newInstance.getId();
i++;
}
fireInstancesAdded(instanceIds);
return CreateResponse.success();
}
}
Expand Down Expand Up @@ -296,6 +310,7 @@ protected DeleteResponse doDelete(ServerIdentity identity, DeleteRequest request
LwM2mInstanceEnabler deletedInstance = instances.remove(request.getPath().getObjectInstanceId());
if (deletedInstance != null) {
deletedInstance.onDelete(identity);
fireInstancesRemoved(deletedInstance.getId());
return DeleteResponse.success();
}
return DeleteResponse.notFound();
Expand All @@ -307,18 +322,34 @@ public BootstrapDeleteResponse doDelete(ServerIdentity identity, BootstrapDelete
if (id == LwM2mId.SECURITY) {
// For security object, we clean everything except bootstrap Server account.
Entry<Integer, LwM2mInstanceEnabler> bootstrapServerAccount = null;
int[] instanceIds = new int[instances.size()];
int i = 0;
for (Entry<Integer, LwM2mInstanceEnabler> instance : instances.entrySet()) {
if (ServersInfoExtractor.isBootstrapServer(instance.getValue())) {
bootstrapServerAccount = instance;
} else {
// store instance ids
instanceIds[i] = instance.getKey();
i++;
}
}
instances.clear();
if (bootstrapServerAccount != null) {
instances.put(bootstrapServerAccount.getKey(), bootstrapServerAccount.getValue());
}
fireInstancesRemoved(instanceIds);
return BootstrapDeleteResponse.success();
} else {
instances.clear();
// fired instances removed
int[] instanceIds = new int[instances.size()];
int i = 0;
for (Entry<Integer, LwM2mInstanceEnabler> instance : instances.entrySet()) {
instanceIds[i] = instance.getKey();
i++;
}
fireInstancesRemoved(instanceIds);

return BootstrapDeleteResponse.success();
}
} else if (request.getPath().isObjectInstance()) {
Expand All @@ -330,6 +361,7 @@ public BootstrapDeleteResponse doDelete(ServerIdentity identity, BootstrapDelete
}
}
if (null != instances.remove(request.getPath().getObjectInstanceId())) {
fireInstancesRemoved(request.getPath().getObjectInstanceId());
return BootstrapDeleteResponse.success();
} else {
return BootstrapDeleteResponse.badRequest(String.format("Instance %s not found", request.getPath()));
Expand Down
Loading

0 comments on commit da2a39e

Please sign in to comment.