Commit 93e1d23f authored by Björn Butzin's avatar Björn Butzin
Browse files

CoapChannelManager & BasicCoapChannelManager - Added method to remove resource...

CoapChannelManager & BasicCoapChannelManager - Added method to remove resource server channel from the channel manager (used to stop resource server)
BasicCoapRequest - Changed getURIPath to return non-URL-encoded string
CoapResource & BasicCoapResource - added new Constructors and setValue methods for string values instead of byte[]; Removed writable and replaced by postable & putable
Resource Server & CoapResourceServer - implemented start & stop; reviewed onRequest; updated create permissions & process
CoreResource - updated access permissions, Fixed filter query bug
BasicResourceTest & InterfaceTest - updated test setUp
Encoder - added as helper class to encode and decode UTF-8 string <-> byte[]
parent 5ce8f292
......@@ -119,6 +119,16 @@ public class BasicCoapChannelManager implements CoapChannelManager {
throw new IllegalStateException("address already in use");
}
}
public void removeServerListener(CoapServer listener, int localPort) {
if (this.socketMap.containsKey(localPort)) {
SocketInformation socketInfo = this.socketMap.get(localPort);
if(socketInfo.serverListener.equals(listener)){
socketInfo.socketHandler.close();
this.socketMap.remove(localPort);
}
}
}
@Override
public CoapClientChannel connect(CoapClient client, InetAddress addr,
......
......@@ -48,6 +48,13 @@ public interface CoapChannelManager {
* @param localPort
*/
public void createServerListener(CoapServer serverListener, int localPort);
/**
* removes a server socket listener for incoming connections
* @param serverListener
* @param localPort
*/
public void removeServerListener(CoapServer coapResourceServer, int port);
/**
* called by a client to create a connection
......
......@@ -16,6 +16,8 @@
package org.ws4d.coap.messages;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.Vector;
import org.ws4d.coap.interfaces.CoapRequest;
......@@ -180,26 +182,23 @@ public class BasicCoapRequest extends AbstractCoapMessage implements CoapRequest
return (int)coapUint2Long(options.getOption(CoapHeaderOptionType.Uri_Port).getOptionData());
}
@Override
public String getUriPath() {
if (options.getOption(CoapHeaderOptionType.Uri_Path) == null){
return null;
}
@Override
public String getUriPath() throws IllegalArgumentException {
if (this.options.getOption(CoapHeaderOptionType.Uri_Path) == null) {
return null;
}
StringBuilder uriPathBuilder = new StringBuilder();
for (CoapHeaderOption option : options) {
if (option.getOptionType() == CoapHeaderOptionType.Uri_Path) {
String uriPathElement;
try {
uriPathElement = new String(option.getOptionData(), "UTF-8");
try {
for (CoapHeaderOption option : this.options) {
if (option.getOptionType() == CoapHeaderOptionType.Uri_Path) {
uriPathBuilder.append("/");
uriPathBuilder.append(uriPathElement);
} catch (UnsupportedEncodingException e) {
throw new IllegalArgumentException("Invalid Encoding");
uriPathBuilder.append(new String(option.getOptionData(), "UTF-8"));
}
}
return URLDecoder.decode(uriPathBuilder.toString(), "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new IllegalArgumentException("Invalid Encoding");
}
return uriPathBuilder.toString();
}
@Override
......
......@@ -26,6 +26,7 @@ import org.ws4d.coap.interfaces.CoapResponse;
import org.ws4d.coap.interfaces.CoapServerChannel;
import org.ws4d.coap.messages.CoapMediaType;
import org.ws4d.coap.messages.CoapResponseCode;
import org.ws4d.coap.tools.Encoder;
/**
* @author Christian Lerche <christian.lerche@uni-rostock.de>
......@@ -42,11 +43,12 @@ public class BasicCoapResource implements CoapResource {
private CoapMediaType mediaType;
private String path;
private byte[] value;
private long expires = -1; // never expires
private long expires = -1; // -1 = never expires
// permissions
private boolean readable = true;
private boolean writable = true;
private boolean postable = true;
private boolean putable = true;
private boolean observable = true;
private boolean deletable = true;
......@@ -57,18 +59,26 @@ public class BasicCoapResource implements CoapResource {
/** DEFAULT NULL: let the client decide **/
private Boolean reliableNotification = null;
public BasicCoapResource(String path, byte[] value, CoapMediaType mediaType) {
String[] segments = path.trim().split("/");
public BasicCoapResource(String path, String value, CoapMediaType mediaType) {
init(path, Encoder.StringToByte(value), mediaType);
}
public BasicCoapResource(String path, byte[] value, CoapMediaType mediaType){
init(path, value, mediaType);
}
private void init(String initPath, byte[] initValue, CoapMediaType initMediaType)throws IllegalArgumentException{
String[] segments = initPath.trim().split("/");
for (String segment : segments) {
if (segment.getBytes().length > 255) {
IllegalArgumentException e = new IllegalArgumentException("Uri-Path too long");
logger.warn("BasicCoapResource(" + path + "," + value + "," + mediaType + "): Uri-Path too long", e);
logger.info("Uri-Path too long: "+initPath, e);
throw e;
}
}
this.path = path;
this.value = value;
this.mediaType = mediaType;
this.path = initPath;
this.value = initValue;
this.mediaType = initMediaType;
}
public BasicCoapResource setCoapMediaType(CoapMediaType mediaType) {
......@@ -92,6 +102,20 @@ public class BasicCoapResource implements CoapResource {
return getPath();
}
/**
* Sets the value of the resource. Be aware to take care about the right
* encoding!
*
* @param value
* the value to be set
* @return true if and only if the value was changed
*/
public boolean setValue(String value) {
this.value = Encoder.StringToByte(value);
this.changed();
return true;
}
public boolean setValue(byte[] value) {
this.value = value;
this.changed();
......@@ -103,12 +127,22 @@ public class BasicCoapResource implements CoapResource {
}
public byte[] getValue(List<String> query) {
// query string is ignored by intention
return this.value;
}
public String getStringValue() {
return Encoder.ByteToString(this.value);
}
public String getStringValue(@SuppressWarnings("unused") List<String> query) {
// query string is ignored by intention
return Encoder.ByteToString(this.value);
}
/**
* @param reliableNotification
* NULL = let the client decide
* NULL = let the client decide
*/
public BasicCoapResource setReliableNotification(Boolean reliableNotification) {
this.reliableNotification = reliableNotification;
......@@ -121,7 +155,7 @@ public class BasicCoapResource implements CoapResource {
@Override
public String toString() {
return getPath()+"\n"+getValue().toString();
return getPath()+"\n"+Encoder.ByteToString(getValue());
}
public boolean post(byte[] data) {
......@@ -203,13 +237,14 @@ public class BasicCoapResource implements CoapResource {
return this.readable;
}
public BasicCoapResource setWritable(boolean writeable) {
this.writable = writeable;
public BasicCoapResource setPostable(boolean postable) {
this.postable = postable;
return this;
}
public boolean isWriteable() {
return this.writable;
public BasicCoapResource setPutable(boolean putable) {
this.putable = putable;
return this;
}
public BasicCoapResource setObservable(boolean observable) {
......@@ -251,4 +286,12 @@ public class BasicCoapResource implements CoapResource {
public int getSizeEstimate() {
return getValue().length;
}
public boolean isPostable() {
return this.postable;
}
public boolean isPutable() {
return this.putable;
}
}
\ No newline at end of file
/* Copyright 2015 University of Rostock
/* Copyright 2016 University of Rostock
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
......@@ -24,8 +24,9 @@ import org.ws4d.coap.messages.CoapMediaType;
/**
* @author Nico Laum <nico.laum@uni-rostock.de>
* @author Christian Lerche <christian.lerche@uni-rostock.de>
* @author Björn Butzin <bjoern.butzin@uni-rostock.de>
*/
public interface CoapResource{
public interface CoapResource {
/**
* Can be called to inform the resource about changed content.
......@@ -47,6 +48,9 @@ public interface CoapResource{
* Removes an observer from this resource
*
* @param channel
* - the channel that should be removed from the observers list.
* In most cases this will be the CoapRequest.getChannel() from
* the clients request
*/
public void removeObserver(CoapChannel channel);
......@@ -58,7 +62,7 @@ public interface CoapResource{
/**
* @return The Unix time (in milliseconds), when the resource expires. -1,
* when the resource never expires.
* if the resource never expires.
*/
public long expires();
......@@ -67,42 +71,98 @@ public interface CoapResource{
*/
public boolean isExpired();
/* ------------------------------------------------------------------*/
/**
* Get the MIME Type of the resource (e.g., "application/xml")
* @return The MIME Type of this resource as String.
*/
public String getMimeType();
/**
* Get the unique name of this resource
* @return The unique name of the resource.
*/
public String getPath();
public String getShortName();
public boolean setValue(byte[] value);
public byte[] getValue();
/**
* Get the MIME Type of the resource (e.g., "application/xml")
*
* @return The MIME Type of this resource as String.
*/
public String getMimeType();
/**
* Get the unique name of this resource
*
* @return The unique name of the resource.
*/
public String getPath();
/**
* Get the name of this resource. Might not be unique
*
* @return The unique name of the resource.
*/
public String getShortName();
/**
* Sets the value of the resource. Be aware to take care about the right
* encoding!
*
* @param value
* the value to be set
* @return true if and only if the value was changed
*/
public boolean setValue(byte[] value);
/**
* Get the current value of the resource as byte[].
*
* @return the current value
* @see {@link #getMimeType()} to get the encoding of the data
* @see {@link #getValue(List)} If you want to pass a query string public
*/
byte[] getValue();
/**
* If can use this method to get the current value of the resource as byte[]
* with respect to query parameters.
*
* @return the current value
* @see {@link #getMimeType()} to get the encoding of the data
*/
public byte[] getValue(List<String> query);
//TODO: bad api: no return value
public boolean post(byte[] data);
/**
* Use this method to hand posted data to the resource. The behavior
* strongly depends on the resource itself.
*
* @param data
* - The data posted
* @return true if and only if the resource accepted the post and did the
* respective changes
*/
public boolean post(byte[] data);
/**
* Give the resource a callback to inform the resource server about changes.
*
* @param server
* - The resource server that handles this resource
*/
public void registerServerListener(ResourceServer server);
/**
* Remove the callback handle to inform the resource server about changes.
* Changes of the resource will not be propagated to this resource server
* anymore.
*
* @param server
* - The resource server handle to be removed
*/
public void unregisterServerListener(ResourceServer server);
/**
* @return True, if and only if the resource is readable.
*/
public boolean isReadable();
/**
* @return True, if and only if the resource is writable.
* @return True, if and only if the resource accepts post requests.
*/
public boolean isPostable();
/**
* @return True, if and only if the resource accepts put requests.
*/
public boolean isWriteable();
public boolean isPutable();
/**
* @return True, if and only if the resource is observable.
......@@ -113,16 +173,28 @@ public interface CoapResource{
* @return True, if and only if the resource is delete-able.
*/
public boolean isDeletable();
/**
* @return the CoAP Media Type of this resource
*/
public CoapMediaType getCoapMediaType();
public String getResourceType();
public String getInterfaceDescription();
public int getSizeEstimate();
/**
* This method is used to get the resource type of this resource.
*
* @return The string representing the resource type or null.
*/
public String getResourceType();
/**
* This method is used to get the interface description of this resource.
*
* @return The string representing the interface description or null.
*/
public String getInterfaceDescription();
/**
* @return an integer value representing the size of the resources value
*/
public int getSizeEstimate();
}
/* Copyright 2015 University of Rostock
/* Copyright 2016 University of Rostock
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
......@@ -22,7 +22,6 @@ import java.net.URI;
import java.net.URISyntaxException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Vector;
......@@ -32,7 +31,6 @@ import org.apache.log4j.Logger;
import org.apache.log4j.SimpleLayout;
import org.ws4d.coap.Constants;
import org.ws4d.coap.connection.BasicCoapChannelManager;
import org.ws4d.coap.interfaces.CoapChannelManager;
import org.ws4d.coap.interfaces.CoapRequest;
import org.ws4d.coap.interfaces.CoapResponse;
import org.ws4d.coap.interfaces.CoapServer;
......@@ -44,6 +42,7 @@ import org.ws4d.coap.messages.CoapResponseCode;
/**
* @author Christian Lerche <christian.lerche@uni-rostock.de>
* @author Bjrn Konieczek <bjoern.konieczek@uni-rostock.de>
* @author Bjrn Butzin <bjoern.butzin@uni-rostock.de>
*/
public class CoapResourceServer implements CoapServer, ResourceServer {
private final static Logger logger = Logger.getLogger(CoapResourceServer.class);
......@@ -51,9 +50,8 @@ public class CoapResourceServer implements CoapServer, ResourceServer {
private Map<String, byte[]> etags = new HashMap<String, byte[]>();
private Map<String, CoapResource> resources = new HashMap<String, CoapResource>();
private CoreResource coreResource = new CoreResource(this);
/** toggle if the creation of resources is allowed on this server **/
private boolean allowCreate = true;
private boolean allowDelete = true;
public CoapResourceServer() {
logger.addAppender(new ConsoleAppender(new SimpleLayout()));
......@@ -64,6 +62,12 @@ public class CoapResourceServer implements CoapServer, ResourceServer {
return this.resources;
}
/**
* Adds a resource to the resources list and set up a resource listener.
*
* @param resource
* - The resource to add
*/
private void addResource(CoapResource resource) {
resource.registerServerListener(this);
this.resources.put(resource.getPath(), resource);
......@@ -85,7 +89,7 @@ public class CoapResourceServer implements CoapServer, ResourceServer {
@Override
public boolean updateResource(CoapResource resource, CoapRequest request) {
if (null != resource && this.resources.containsKey(resource.getPath())) {
((BasicCoapResource) resource).setValue(request.getPayload());
resource.setValue(request.getPayload());
generateEtag(resource);
logger.info("updated ressource: " + resource.getPath());
return true;
......@@ -105,45 +109,44 @@ public class CoapResourceServer implements CoapServer, ResourceServer {
}
@Override
public final CoapResource readResource(String path) {
public final CoapResource getResource(String path) {
logger.info("read ressource: " + path);
return this.resources.get(path);
}
/*
* corresponding to the coap spec the put is an update or create (or error)
*/
// public CoapResponseCode CoapResponseCode(Resource resource) {
// Resource res = readResource(resource.getPath());
// //TODO: check results
// if (res == null){
// createResource(resource);
// return CoapResponseCode.Created_201;
// } else if( res == coreResource ){
// return CoapResponseCode.Forbidden_403;
// } else {
// updateResource(resource );
// return CoapResponseCode.Changed_204;
// }
// }
@Override
public void start() throws Exception {
start(Constants.COAP_DEFAULT_PORT);
}
public void start(int port) throws Exception {
/**
* Start the ResourceServer. This usually opens network ports and makes the
* resources available through a certain network protocol.
*
* @param serverport
* - The port to be used.
* @throws Exception
* if the connection can not be established
* @see {@link #start()} To start the server on the standard port
*/
public void start(int serverport) throws Exception {
this.coreResource = new CoreResource(this);
this.resources.put(this.coreResource.getPath(), this.coreResource);
CoapChannelManager channelManager = BasicCoapChannelManager.getInstance();
this.port = port;
channelManager.createServerListener(this, this.port);
this.port = serverport;
BasicCoapChannelManager.getInstance().createServerListener(this, this.port);
}
@Override
public void stop() {
// TODO implement stop method of the CoAP resource server
this.resources.clear();
this.etags.clear();
this.coreResource = null;
BasicCoapChannelManager.getInstance().removeServerListener(this, this.port);
}
/**
* @return The port this server runs on.
*/
public int getPort() {
return this.port;
}
......@@ -155,7 +158,6 @@ public class CoapResourceServer implements CoapServer, ResourceServer {
hostUri = new URI("coap://" + getLocalIpAddress() + ":" + getPort());
} catch (URISyntaxException e) {
logger.warn("getHostUri() could not create valid URI from local IP address", e);
e.printStackTrace();
}
return hostUri;
}
......@@ -176,90 +178,91 @@ public class CoapResourceServer implements CoapServer, ResourceServer {
CoapRequestCode requestCode = request.getRequestCode();
String targetPath = request.getUriPath();
int eTagMatch = -1;
CoapResource resource = readResource(targetPath);
// TODO: check return values of create, read, update and delete --> no changes!
// TODO: implement forbidden --> maybe also done!
CoapResource resource = getResource(targetPath);
switch (requestCode) {
case GET:
if (resource != null) {
eTagMatch = checkEtagMatch(request.getETag(), this.etags.get(targetPath));
if (eTagMatch != -1) {
response = channel.createResponse(request, CoapResponseCode.Valid_203, CoapMediaType.text_plain);
response.setETag(request.getETag().get(eTagMatch));
} else if (!resource.isReadable()){
response = channel.createResponse(request, CoapResponseCode.Forbidden_403);
if (null != request.getETag()) {
eTagMatch = request.getETag().indexOf(this.etags.get(targetPath));
}
if (null == resource) {
response = channel.createResponse(request, CoapResponseCode.Not_Found_404);
} else if (eTagMatch != -1) {
response = channel.createResponse(request, CoapResponseCode.Valid_203, CoapMediaType.text_plain);
response.setETag(request.getETag().get(eTagMatch));
} else if (!resource.isReadable()) {
response = channel.createResponse(request, CoapResponseCode.Forbidden_403);
} else {
// URI queries
Vector<String> uriQueries = request.getUriQuery();
final byte[] responseValue = (null == uriQueries ? resource.getValue() : resource.getValue(uriQueries));
// BLOCKWISE transfer?
if (null != request.getBlock2() || null != channel.getMaxSendBlocksize()) {
response = channel.addBlockContext(request, responseValue);
} else {
// URI queries
Vector<String> uriQueries = request.getUriQuery();
final byte[] responseValue;
if (uriQueries != null) {
responseValue = resource.getValue(uriQueries);
} else {
responseValue = resource.getValue();
}
if (request.getBlock2() != null || channel.getMaxSendBlocksize() != null) {
response = channel.addBlockContext(request, responseValue);
} else {
response = channel.createResponse(request, CoapResponseCode.Content_205, resource.getCoapMediaType());