Skip to content

Commit

Permalink
Introduce transactional listener to regroup notifications.
Browse files Browse the repository at this point in the history
  • Loading branch information
sbernard31 committed Feb 7, 2020
1 parent 6ef4932 commit b687e5c
Show file tree
Hide file tree
Showing 2 changed files with 197 additions and 52 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public abstract class BaseObjectEnabler implements LwM2mObjectEnabler {

final int id;
private NotifySender notifySender;
private ObjectListener listener;
private TransactionalObjectListener listener;
private ObjectModel objectModel;
private LwM2mClient lwm2mClient;

Expand Down Expand Up @@ -92,12 +92,19 @@ public List<Integer> getAvailableResourceIds(int instanceId) {

@Override
public synchronized CreateResponse create(ServerIdentity identity, CreateRequest request) {
if (!identity.isSystem()) {
if (id == LwM2mId.SECURITY) {
return CreateResponse.notFound();
try {
beginTransaction();

if (!identity.isSystem()) {
if (id == LwM2mId.SECURITY) {
return CreateResponse.notFound();
}
}
return doCreate(identity, request);

} finally {
endTransaction();
}
return doCreate(identity, request);
}

protected CreateResponse doCreate(ServerIdentity identity, CreateRequest request) {
Expand Down Expand Up @@ -141,62 +148,69 @@ protected ReadResponse doRead(ServerIdentity identity, ReadRequest request) {

@Override
public synchronized WriteResponse write(ServerIdentity identity, WriteRequest request) {
LwM2mPath path = request.getPath();
try {
beginTransaction();

// write is not supported for bootstrap, use bootstrap write
if (identity.isLwm2mBootstrapServer()) {
return WriteResponse.methodNotAllowed();
}
LwM2mPath path = request.getPath();

// write the security object is forbidden
if (LwM2mId.SECURITY == id && !identity.isSystem()) {
return WriteResponse.notFound();
}
// write is not supported for bootstrap, use bootstrap write
if (identity.isLwm2mBootstrapServer()) {
return WriteResponse.methodNotAllowed();
}

if (path.isResource()) {
// resource write:
// check if the resource is writable
if (LwM2mId.SECURITY != id) { // security resources are writable by SYSTEM
ResourceModel resourceModel = objectModel.resources.get(path.getResourceId());
if (resourceModel != null && !resourceModel.operations.isWritable()) {
return WriteResponse.methodNotAllowed();
}
// write the security object is forbidden
if (LwM2mId.SECURITY == id && !identity.isSystem()) {
return WriteResponse.notFound();
}
} else if (path.isObjectInstance()) {
// instance write:
// check if all resources are writable
if (LwM2mId.SECURITY != id) { // security resources are writable by SYSTEM
ObjectModel model = getObjectModel();
for (Integer writeResourceId : ((LwM2mObjectInstance) request.getNode()).getResources().keySet()) {
ResourceModel resourceModel = model.resources.get(writeResourceId);
if (null != resourceModel && !resourceModel.operations.isWritable()) {

if (path.isResource()) {
// resource write:
// check if the resource is writable
if (LwM2mId.SECURITY != id) { // security resources are writable by SYSTEM
ResourceModel resourceModel = objectModel.resources.get(path.getResourceId());
if (resourceModel != null && !resourceModel.operations.isWritable()) {
return WriteResponse.methodNotAllowed();
}
}
}

if (request.isReplaceRequest()) {
// REPLACE
// check, if all mandatory writable resources are provided
// Collect all mandatory writable resource IDs from the model
Set<Integer> mandatoryResources = new HashSet<>();
for (ResourceModel resourceModel : getObjectModel().resources.values()) {
if (resourceModel.mandatory && (LwM2mId.SECURITY == id || resourceModel.operations.isWritable()))
mandatoryResources.add(resourceModel.id);
}
// Afterwards remove the provided resource IDs from that set
for (Integer writeResourceId : ((LwM2mObjectInstance) request.getNode()).getResources().keySet()) {
mandatoryResources.remove(writeResourceId);
} else if (path.isObjectInstance()) {
// instance write:
// check if all resources are writable
if (LwM2mId.SECURITY != id) { // security resources are writable by SYSTEM
ObjectModel model = getObjectModel();
for (Integer writeResourceId : ((LwM2mObjectInstance) request.getNode()).getResources().keySet()) {
ResourceModel resourceModel = model.resources.get(writeResourceId);
if (null != resourceModel && !resourceModel.operations.isWritable()) {
return WriteResponse.methodNotAllowed();
}
}
}
if (!mandatoryResources.isEmpty()) {
return WriteResponse.badRequest("mandatory writable resources missing!");

if (request.isReplaceRequest()) {
// REPLACE
// check, if all mandatory writable resources are provided
// Collect all mandatory writable resource IDs from the model
Set<Integer> mandatoryResources = new HashSet<>();
for (ResourceModel resourceModel : getObjectModel().resources.values()) {
if (resourceModel.mandatory
&& (LwM2mId.SECURITY == id || resourceModel.operations.isWritable()))
mandatoryResources.add(resourceModel.id);
}
// Afterwards remove the provided resource IDs from that set
for (Integer writeResourceId : ((LwM2mObjectInstance) request.getNode()).getResources().keySet()) {
mandatoryResources.remove(writeResourceId);
}
if (!mandatoryResources.isEmpty()) {
return WriteResponse.badRequest("mandatory writable resources missing!");
}
}
}
}

// TODO we could do a validation of request.getNode() by comparing with resourceSpec information
// TODO we could do a validation of request.getNode() by comparing with resourceSpec information

return doWrite(identity, request);
return doWrite(identity, request);
} finally {
endTransaction();
}
}

protected WriteResponse doWrite(ServerIdentity identity, WriteRequest request) {
Expand Down Expand Up @@ -388,11 +402,19 @@ public NotifySender getNotifySender() {

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

protected void beginTransaction() {
if (listener != null) {
listener.beginTransaction();
}
}

protected ObjectListener getListener() {
return listener;
protected void endTransaction() {
if (listener != null) {
listener.endTransaction();
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/*******************************************************************************
* Copyright (c) 2020 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.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

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

/**
* An {@link ObjectListener} which is able to store notification during transaction and raise all grouped event at the
* end of the transaction.
* <p>
* This class is not threadsafe.
*/
public class TransactionalObjectListener implements ObjectListener {
private boolean inTransaction = false;
private List<Integer> instancesAdded = new ArrayList<>();
private List<Integer> instancesRemoved = new ArrayList<>();
private Map<Integer, List<Integer>> resourcesChangedByInstance = new HashMap<>();

private LwM2mObjectEnabler objectEnabler;
private ObjectListener innerListener;

public TransactionalObjectListener(LwM2mObjectEnabler objectEnabler, ObjectListener listener) {
this.objectEnabler = objectEnabler;
this.innerListener = listener;
}

public void beginTransaction() {
inTransaction = true;
}

public void endTransaction() {
fireStoredEvents();
instancesAdded.clear();
instancesRemoved.clear();
resourcesChangedByInstance.clear();
inTransaction = false;
}

private void fireStoredEvents() {
if (!instancesAdded.isEmpty())
innerListener.objectInstancesAdded(objectEnabler, toIntArray(instancesAdded));
if (!instancesRemoved.isEmpty())
innerListener.objectInstancesRemoved(objectEnabler, toIntArray(instancesRemoved));

for (Map.Entry<Integer, List<Integer>> entry : resourcesChangedByInstance.entrySet()) {
innerListener.resourceChanged(objectEnabler, entry.getKey(), toIntArray(entry.getValue()));
}
}

@Override
public void objectInstancesAdded(LwM2mObjectEnabler object, int... instanceIds) {
if (!inTransaction) {
innerListener.objectInstancesAdded(object, instanceIds);
} else {
// store additions
for (int instanceId : instanceIds) {
if (instancesRemoved.contains(instanceId)) {
instancesRemoved.remove(instanceId);
} else if (!instancesAdded.contains(instanceId)) {
instancesAdded.add(instanceId);
}
}
}
}

@Override
public void objectInstancesRemoved(LwM2mObjectEnabler object, int... instanceIds) {
if (!inTransaction) {
innerListener.objectInstancesRemoved(object, instanceIds);
} else {
// store deletion
for (int instanceId : instanceIds) {
if (instancesAdded.contains(instanceId)) {
instancesAdded.remove(instanceId);
} else if (!instancesRemoved.contains(instanceId)) {
instancesRemoved.add(instanceId);
}
}
}
}

@Override
public void resourceChanged(LwM2mObjectEnabler object, int instanceId, int... resourcesIds) {
if (!inTransaction) {
innerListener.resourceChanged(object, instanceId, resourcesIds);
} else {
List<Integer> resourcesChanged = resourcesChangedByInstance.get(instanceId);
if (resourcesChanged == null) {
resourcesChanged = new ArrayList<Integer>();
resourcesChangedByInstance.put(instanceId, resourcesChanged);
}
for (int resourceId : resourcesIds) {
resourcesChanged.add(resourceId);
}
}
}

private int[] toIntArray(List<Integer> list) {
int[] ret = new int[list.size()];
int i = 0;
for (Integer e : list)
ret[i++] = e;
return ret;
}
}

0 comments on commit b687e5c

Please sign in to comment.