Californium – A CoAP framework in Java
This HTML documentation is based on the lab thesis
by Daniel Pauli and Dominique Im Obersteg. While continuing the development of
Californium, I will update this page over time.
Ideas behind Californium
In the following few paragraphs, we would like to point out the fundamental
ideas behind Californium that heavily influenced our design decisions.
Abstraction
Californium aims to shield users from the details of CoAP in order to
provide an intuitive, easy-to-use framework to interact with CoAP endpoints or
provide specific services. Following that, users do not need to deal with
internals like message retransmissions, block-wise transfers and observation
handling.
From a client's view, a RESTful operation can be performed by just creating
a GET, POST, PUT, or DELETE request object, specifying the URI of the target
endpoint, followed by payload and other options if required. After execution of
the request, the resulting responses can be processed either synchronously or
asynchronously.
Setting up a CoAP server using Californium just requires to define its
resources by providing subclasses that implement the GET, POST, PUT, and/or
DELETE request handlers. Thus, no custom message dispatching is required.
Nevertheless, protocol components and properties can still be configured to
suit extended requirements.
Modularity
First, Californium is based on a layered architecture, extending the two-layer
approach described in the CoAP draft.
This allows an isolated implementation of different aspects such as message
retransmission, transactions, and block-wise transfers specified by CoAP over
several levels.
Second, message communication is decoupled from the state representation of
CoAP endpoints. Although currently not implemented, this design allows the
modeling of endpoints similar to remote objects of Java's RMI system.
Hence, a client could access resources on a remote endpoint over a local stub,
as if it were an object on the local machine.
Maintainability
As described in Section 2.2, our
implementation is highly modular and therefore also easy to maintain. Future
adaptions and changes can be implemented by only changing the corresponding
part of the code without going through the hassle of reading thousands of lines
of code and changing the architecture. Protocol constants that may be subject
to change in later CoAP versions as well as Californium specific constants, are
managed over an easy-accessible object containing library specific parameters
that can be synchronized with a configuration file to allow convenient
adjustments by users.
Extensibility
A direct consequence of the modularity is the augmented extensibility of our
CoAP library. Communication-related features can be added by introducing new
layers in the communication stack, which is convenient due to the uniform
interfaces of the layers. Adding a new layer provides the possibility to
include and exclude certain components for testing purposes. Client- and
server-related features can be implemented by extending the Endpoint classes,
e.g., to provide proxying support.
Backwards Compatibility
Californium implements CoAP draft 08.
To ensure that our implementation of CoAP still works with servers and clients
based on previous drafts, we also provide a support for reasonable properties
of previous drafts, such as option numbers and return codes.
Architectural Design
Figure 3.1 provides an overview over the whole system
architecture. The following sections will provide more details about the
packages and the classes therein contained.
This package contains all classes that provide the functionality of the core
Californium CoAP library. It includes entity classes defining CoAP messages and
the derived request and response subclasses as well as static classes
containing enumerations and constants for the different registries specified by
the IETF CoAP draft.
Furthermore, it defines common interfaces that are used to communicate between
packages.
Interfaces
MessageHandler
The interface MessageHandler describes the method signatures to handle
a request or response. It is mainly used by the communication layer classes for
the double dispatch to process messages according to their subtype.
MessageReceiver
The interface MessageReceiver describes the method signature to
receive a message. It is mainly used by communication layer and endpoint
classes to implement the propagation of incoming messages.
RequestHandler
The interface RequestHandler describes the method signatures to handle
requests (GET, POST, PUT, and DELETE). This interface is implemented by
Resource classes and users will implement its methods in order to
define custom resource behavior of Californium servers.
ResponseHandler
The interface ResponseHandler describes the method signature to handle
a response. It is used to propagate responses to requests over the affected
endpoints back to the communication layer classes.
Classes
CodeRegistry
The class CodeRegistry provides the CoAP code registry as defined in
the draft. It
contains constants for request and response codes as well as the corresponding
string representations. Users will primarily need this registry to specify or
check response codes of CoAP messages.
OptionNumberRegistry
The class OptionNumberRegistry provides the CoAP option number
registry as defined in the draft.
It contains constants for CoAP option numbers as well as the corresponding
string representations. Users will primarily need this registry to specify or
check additional options of CoAP messages where no convenience accessor/mutator
methods (such as Message.setContentType()) are provided.
MediaTypeRegistry
The class MediaTypeRegistry provides the CoAP media type registry as
defined in the draft.
It contains constants for content-type codes as well as the corresponding
string representations. Users will primarily need this registry to specify the
content-type for custom CoAP messages.
Communicator
The class Communicator provides the functionality to build the
communication layer stack and to send and receive messages. As a subclass of
UpperLayer (see Section 3.2.3)
it is actually a composite layer that contains the subsequent layers in the
top-down order as explained in Table 3.1.
Hence, the Communicator class is used to encapsulate the various communication
layers of the CoAP protocol by providing an appropriate unified interface.
Internally, it instantiates the required communication layer classes and
connects them accordingly. The Communicator also acts as Mediator between
endpoint classes and communication layer classes, allowing to specify and query
parameters like the UDP port.
Transaction timeouts, e.g., to limit wait time for separate responses and responses to non-confirmable requests
Message
Reliable transport of Confirmable messages over underlying layers by making use of retransmissions and exponential backoff
Matching of Confirmables to their corresponding Acknowledgment/Reset
Detection and cancellation of duplicate messages
Retransmission of Acknowledgments/Reset messages upon receiving duplicate Confirmable messages
UDP
UDP datagram exchange
Table 3.1: Californium communication layers
Message
The class Message represents the core entities for the message
exchange between CoAP endpoints. All communication layers process objects of
this class. This class provides accessor and mutator methods to the CoAP
message header and payload. Internally, it implements the conversion from the
Java object to the serialized datagram representation. Users will primarily use
the following methods of this class:
public URI getURI()
public void setURI(URI uri)
public boolean setURI(String uri)
Accesses or modifies the URI property of a message. For outgoing messages, this
is the URI of the recipient. For incoming messages, it is the URI of the sender.
public byte[] getPayload()
public String getPayloadString()
public void setPayload(byte[] payload)
public void setPayload(String payload)
public void setPayload(String payload, int mediaType)
Accesses or modifies the payload of a message, either as UTF-8 string or raw
binary data. The content-type option will be specified accordingly.
public void addOption(Option opt)
public void setOption(Option opt)
public void removeOption(int optionNumber)
Adds or removes CoAP options from a message. Depending on the option, multiple
options with the same option number can be added to a message. addOption()
will append an option to the list of options with the same number,
setOption() will replace any options with the same number and
removeOption() will remove all options with the specified number.
public List
Accesses or modifies the list of CoAP options with the same option number.
getFirstOption() is a convenience method to access the option in case
where only one option is allowed, e.g., for the Token option. Note that users
will instantiate request and response subclasses rather than directly create a
new message object.
Option
The class Option provides the functionality of the CoAP options. A
message can have several Options with different or same option numbers, as
required by the draft. Every option is associated with a value of implicit type
and hence provides accessor and mutator methods of different types to the
option value. This allows clients to e.g., generate tokens as consecutive
integers, while servers will treat token as opaque objects and echo them
without further assumptions about their format. Users will primarily use the
following methods of this class:
public Option(int val, int nr)
public Option(String str, int nr)
public Option(byte[] raw, int nr)
Constructor for a new option with the given option number. The option value can
be specified either as UTF-8 string, as integer number or as raw binary value,
depending on the option number. New options can be added to messages using the
Message.addOption() method.
public int getIntValue()
public void setIntValue(int val)
public String getStringValue()
public void setStringValue(String str)
public byte[] getRawValue()
public void setRawValue(byte[] value)
Accesses or modifies the value of the option. The option value can be specified
either as UTF-8 string, as integer number or as raw binary value, depending on
the option number.
BlockOption
The class BlockOption provides the CoAP block option as a subclass of
Option. It is used to encode/decode the NUM, SZX and M fields as well
as derived values thereof.
Request
The class Request provides the functionality of a CoAP request as a
subclass of Message. It provides different ways to answer with a
matching response at the server side and to handle incoming responses at the
client. Users can register an asynchronous handler using registerResponseHandler(),
create a sub-class (e.g., anonymous inner class) that provides asynchronous
handling by overriding the protected method handleResponse(), or call
the synchronous (blocking) method receiveResponse().
In order to perform a request, users will instantiate a subclass corresponding
to the desired operation:
GETRequest,
PUTRequest,
POSTRequest, and
DELETERequest.
The subclasses are required by the double dispatch mechanism for the message
handling upon receival.
Generally, the se the following Request methods will be required
(in addition to the methods provided by the Message class):
public void execute() throws IOException
Sends the request for processing to the remote endpoint specified by the URI
property on instantiation or by setURI(). This method will return
immediately rather than block.
public void respond(Response response)
public void respond(int code)
public void respond(int code, String message)
Sends a response to the endpoint where the request originated from. This method
is generally used in server-side request handlers in order to answer to a
processed request. Note that a request can be answered by multiple responses
(please refer to the 'Response' class description for more information).
public void accept()
public void reject()
Accepts or rejects a request by sending an ACK or RST to the endpoint where the
request originated from. These methods are generally used in more
time-consuming request handlers to acknowledge the receipt of a message
immediately, avoiding retransmissions of the request. The final response is
then issued using the respond() method, which will be delivered to the sender
using a separate message. Note that it is not required nor suggested to always
perform accept() before respond().
public void enableResponseQueue(boolean enable)
This method is used to define how incoming responses to this request are
handled. If the argument is set to true, incoming responses will be queued and
can be retrieved using the synchronous receiveResponse() method. If the
argument is set to false, incoming responses are not stored and need to be
processed asynchronously using response handlers.
public Response receiveResponse() throws InterruptedException
This method blocks until a response to this request was received. It returns
null if no response can be received (e.g., when the DEFAULT_TRANSACTION_TIMEOUT
occurred). In order to use this method, the response queue must be
enabled (see enableResponseQueue()). This mechanism is usually used
by single-threaded clients that only perform one request at a time.
public void registerResponseHandler(ResponseHandler handler)
public void unregisterResponseHandler(ResponseHandler handler)
Registers a handler object that is notified about incoming responses to this
request. This mechanism is generally used by clients that implement a similar
response handling regardless of specific requests, e.g., log the arrival of a
response in a file.
protected void handleResponse(Response response)
This protected method is called upon receiving a response to this request.
Subclasses can override this method to provide custom response handling. This
mechanism is generally used by clients that implement response handling
specific to requests, e.g., display the results of a resource discovery. The
handler method can be implemented conveniently using anonymous inner classes.
Response
The class Response provides the functionality of a CoAP response as a
subclass of Message. Requests and responses are in a
one-to-many-relationship, i.e., a request may receive an arbitrary number of
responses (as for separate responses, observing, and broadcasts). A response is
related with exactly one request. Users will primarily use the following
methods of this class (in addition to the methods provided by the
Message class):
public Request getRequest()
Returns the request that is related with this response.
public int getRTT()
Returns the round trip time of this response in milliseconds. The corresponding
time stamps are set in the UDP layer.
This package contains all classes which provide the functionality for CoAP
endpoints, both local and remote. It includes structural classes defining CoAP
resources as well as endpoints as generalization of the client-server model.
The general idea is to provide a common interface for both local endpoints
(corresponding to own server or client implementations using Californium) and
remote endpoints (which are only accessible in terms of CoAP message
exchanges). Similar as in the Java RMI System, RemoteEndpoint/RemoteResource
objects could be implemented as local stubs to LocalEndpoint/LocalResource
objects on remote machines, respectively.
Abstract Classes
Endpoint
The abstract class Endpoint describes the functionality of a CoAP
endpoint as an implementation of the interfaces MessageReceiver and
MessageHandler. It provides an interface to execute requests on a CoAP
endpoint as well as access to its resources. An Endpoint class
provides a Communicator object that is used for message exchange.
Additionally, it contains a single resource that represents the root of the
resource tree.
Resource
The abstract class Resource describes the functionality of a CoAP
resource as an implementation of the interface RequestHandler. Resources are
the core entities of a CoAP endpoint, providing services that can be accessed
and manipulated over RESTful operations. Hence, the main purpose of a resource
is to process a request and return adequate responses. Resources can contain
sub-resources, allowing for content directories or similar, and offer
operations to query or iterate over a resource subtree. Finally, as resource
can contain several attributes as specified by the CoRE Link Format,
attributes are represented using a TreeMap in order to support generic
attributes, while the resource class also provides convenient accessor and
mutator methods for well-known attributes.
Classes
LocalEndpoint
The class LocalEndpoint provides the functionality of a local CoAP
endpoint as a subclass of Endpoint. A client of the Californium
framework will override this class in order to provide custom resources.
Internally, the main purpose of this class is to forward received requests to
the according resource specified by the Uri-Path option. Furthermore, it
implements the root resource to return a brief server description to GET
requests with empty Uri-Path.
LocalResource
The class LocalResource provides the functionality of a local CoAP
resource as a subclass of Resource. Users will inherit this class in
order to provide custom resources by overriding some the following methods:
public void performGET(GETRequest request)
public void performPOST(POSTRequest request)
public void performPUT(PUTRequest request)
public void performDELETE(DELETERequest request)
These methods are defined by the RequestHandler interface and have a
default implementation in this class that respond with "4.05 Method Not Allowed."
DiscoveryResource
The class DiscoveryResource provides the functionality of a CoAP
discovery entry point as a subclass of LocalResource. It basically
implements the "/.well-known/core" resource and returns the list of resources
provided by a CoAP endpoint in link format upon a GET request.
This package contains all classes which provide the functionality required in order to represent and implement the
layered architecture of the Californium CoAP design.
Abstract Classes
Layer
The abstract class Layer describes a layer in the Californium
architecture as an implementation of the interface MessageReceiver. It
provides features to send and receive CoAP Messages and allows clients
to subscribe for incoming messages, following the observer pattern.
Additionally, it provides an interface for implementing communication
statistics.
UpperLayer
The abstract class UpperLayer describes a higher communication layer
in the Californium architecture as a subclass of Layer. Every
UpperLayer is associated with a underlaying layer instance that is
used to send and receive CoAP Messages. All layer classes except
UDPLayer (as the base layer) inherit from UpperLayer and
override the protected methods doSendMessage() and
doReceiveMessage() in order to implement additional features to the
communication stack. Finally, messages are propagated using methods
sendMessageOverLowerLayer() and deliverMessage(), respectively.
Classes
AdverseLayer
The class AdverseLayer provides the functionality of an adverse layer
as a subclass of UpperLayer. It drops messages with a given
probability in order to test retransmissions between MessageLayer and
UDPLayer. Used for testing and evaluation purposes only, it is not
present in the final communication stack.
MessageLayer
The class MessageLayer provides the functionality of a CoAP message
layer as a subclass of UpperLayer. It introduces reliable transport of
confirmable messages over underlying layers by making use of retransmissions
and exponential backoff, matching of confirmables to their corresponding
ACK/RST, detection and cancellation of duplicate messages,
retransmission of ACK/RST messages upon receiving duplicate confirmable messages.
TransactionLayer
The class TransactionLayer provides the functionality of a CoAP
transaction layer as a subclass of UpperLayer. It implements the
matching of responses to the corresponding requests and introduces a custom
timeout for requests to complete.
TransferLayer
The class TransactionLayer provides the functionality of a CoAP
transfer layer as a subclass of UpperLayer. It provides support for
blockwise transfers
using Block1 and Block2 options and custom block sizes and limits.
UDPLayer
The class UDPLayer provides the functionality of a UDP layer that is
able to exchange CoAP messages with remote endpoints. According to the UDP
protocol, messages are exchanged over an unreliable channel and thus may arrive
out of order, appear duplicated or are being lost without any notice.
This package contains all classes which provide the functionality required for
logging and managing preferences.
Classes
DatagramReader
The class DatagramReader provides the functionality to read raw
network-ordered datagrams on bit-level. It is used to parse CoAP messages with
respect to the integer fields of varying bit lengths.
DatagramWriter
The class DatagramWriter provides the functionality to write raw
network-ordered datagrams on bit-level. It is used to serialize CoAP messages
with respect to the integer fields of varying bit lengths.
Log
The class Log provides the functionality to log events in the CoAP
library. It is used to redirect console output and provide uniform error
messages.
Properties
The class Properties implements the functionality of an extended
properties registry. It is used to manage CoAP- and Californium-specific
constants in a central place.
Sequences
The following two sequence diagrams show the interaction of the different
objects (and layers) when sending (Figure 3.6)
or receiving (Figure 3.7) a message.
The implementation details of the operations are split over the different
communication layers.
Send
Whenever an endpoint calls execute on a request, the send order is propagated
down the different layers until it reaches the UDPLayer which then sends the
datagram over UDP.
Figure 3.6: Send operation
Receive
Whenever the UDPLayer receives a message (response to a request), the receive
order is propagated up the different layer until it reaches the Endpoint which
then handles the response.
Figure 3.7: Receive operation
Response Timeout
Whenever the UDPLayer does not receives a response to a request, i.e., a
receive time out has occurred, it repeatedly sends the request again until a
response is received.
Figure 3.8: Response timeout
Examples
In the following few paragraphs, we would like to point out the fundamental
ideas behind Californium that heavily influenced our design decisions.
Synchronous Client
This example demonstrates the receiveResponse() method. This approach
is generally used by single-threaded clients that only perform one single
request at a time, such as console applications.
public class GETClient {
public static void main(String args[]) {
URI uri = null; // URI parameter of the request
if (args.length > 0) {
// input URI from command line arguments
try {
uri = new URI(args[0]);
} catch (URISyntaxException e) {
System.err.println("Invalid URI: " + e.getMessage());
System.exit(-1);
}
// create new request
Request request = new GETRequest();
// specify URI of target endpoint
request.setURI(uri);
// enable response queue for synchronous I/O
request.enableResponseQueue(true);
// execute the request
try {
request.execute();
} catch (IOException e) {
System.err.println("Failed to execute request: " + e.getMessage());
System.exit(-1);
}
// receive response
try {
Response response = request.receiveResponse();
if (response != null) {
// response received, output a pretty-print of it
response.log();
} else {
// transaction timeout occurred
System.out.println("No response received.");
}
} catch (InterruptedException e) {
System.err.println("Receiving of response interrupted: " + e.getMessage());
System.exit(-1);
}
} else {
// display help
System.out.println("Usage: GETClient URI");
}
}
}
Asynchronous Client with Listener
This example demonstrates how to register a client class to handle responses.
This approach is generally used by clients that implement a similar response
handling regardless of specific requests, e.g., log the arrival of a response
in a file.
static class MyClient implements ResponseHandler {
public void performSampleRequest() {
// create new request
Request request = new GETRequest();
// specify URI of target endpoint
request.setURI("coap://vs0.inf.ethz.ch:5683/timeResource");
// register MyClient as response handler
request.registerResponseHandler(this);
// execute the request
try {
request.execute();
} catch (IOException e) {
System.err.println("Failed to execute request: " + e.getMessage());
System.exit(-1);
}
/*
* Do something or return to the message loop of the GUI
*/
}
@Override
public void handleResponse(Response response) {
// specific handling for this request
// here: response received, output a pretty-print
response.log();
}
}
Asynchronous Client with Subclassing (here: anonymous inner class)
This example demonstrates how to use an anonymous inner class to handle
responses. This approach is generally used by clients that implement response
handling specific to requests, e.g. display the results of a resource discovery.
public void performSampleRequest() {
// create new request using an anonymous inner class
Request request = new GETRequest() {
@Override
protected void handleResponse(Response response) {
// specific handling for this request
// here: response received, output a pretty-print
response.log();
}
};
// specify URI of target endpoint
request.setURI("coap://vs0.inf.ethz.ch:5683/timeResource");
// execute the request
try {
request.execute();
} catch (IOException e) {
System.err.println("Failed to execute request: " + e.getMessage());
System.exit(-1);
}
}
Hello World Server
The code below demonstrates how to implement a simple CoAP server that provides
a resource which returns "Hello World" upon a GET-Request.
public class HelloWorldServer extends LocalEndpoint {
class HelloWorldResource extends ReadOnlyResource {
/**
* Constructor for a new Hello-World resource. Here,
* resource-specific properties are set.
*/
public HelloWorldResource() {
// call constructor of superclass and
// provide resource identifier
super("helloWorld");
// set display name
setResourceTitle("Hello-World Resource");
}
/**
* Implementation of the actual functionality by overriding
* the default GET request handler
*/
@Override
public void performGET(GETRequest request) {
// respond to the request with the according response code and payload
request.respond(CodeRegistry.RESP_CONTENT, "Hello World!");
}
}
/**
* Constructor for a new Hello-World server. Here,
* the resources of the server are initialized.
*/
public HelloWorldServer() throws SocketException {
// provide an instance of the Hello-World resource
addResource(new HelloWorldResource());
}
/*
* Application entry point.
*/
public static void main(String[] args) {
try {
// create server
HelloWorldServer server = new HelloWorldServer();
System.out.println("Server listening on port " + server.port());
} catch (SocketException e) {
System.err.println("Failed to initialize server: " + e.getMessage());
}
}
}
Storage Resource
The code below demonstrates a more complex resource for a server. It can be
used to store hierarchical data like a file system.
Content of the individual resources can be accessed using GET requests. New
sub-resources can be created using POST, updated using PUT, and
removed again using DELETE requests. The resources can also be observed.
public class StorageResource extends LocalResource {
private byte[] data;
/**
* Default constructor.
*/
public StorageResource() {
this("storage");
}
/**
* Constructs a new storage resource with the given resourceIdentifier.
*/
public StorageResource(String resourceIdentifier) {
super(resourceIdentifier);
setResourceTitle("PUT your data here or POST new resources!");
setResourceType("Storage");
setObservable(true);
}
// REST Operations /////////////////////////////////////////////////////////
/**
* GETs the content of this storage resource.
* If the content-type of the request is set to application/link-format
* or if the resource does not store any data, the contained sub-resources
* are returned in link format.
*/
@Override
public void performGET(GETRequest request) {
// create response
Response response = new Response(CodeRegistry.RESP_CONTENT);
// check if link format requested
if (request.hasFormat(MediaTypeRegistry.LINK_FORMAT) || data == null) {
// respond with list of sub-resources in link format
response.setPayload(toLinkFormat(), MediaTypeRegistry.LINK_FORMAT);
} else {
// load data into payload
response.setPayload(data);
// set content type
response.setContentType(getContentTypeCode());
}
// complete the request
request.respond(response);
}
/**
* PUTs content to this resource.
*/
@Override
public void performPUT(PUTRequest request) {
// store payload
storeData(request);
// complete the request
request.respond(CodeRegistry.RESP_CHANGED);
}
/**
* POSTs a new sub-resource to this resource.
* The name of the new sub-resource is retrieved from the request
* payload.
*/
@Override
public void performPOST(POSTRequest request) {
// get request payload as a string
String payload = request.getPayloadString();
// check if valid Uri-Path specified
if (payload != null && !payload.isEmpty()) {
createNew(request, payload);
} else {
// complete the request by reporting error
request.respond(CodeRegistry.RESP_BAD_REQUEST,
"Payload must contain Uri-Path for new sub-resource.");
}
}
/**
* Creates a new sub-resource with the given identifier in this
* resource, recursively creating sub-resources along the Uri-Path
* if necessary.
*/
@Override
public void createNew(Request request, String newIdentifier) {
// omit leading and trailing slashes
if (newIdentifier.startsWith("/")) {
newIdentifier = newIdentifier.substring(1);
}
if (newIdentifier.endsWith("/")) {
newIdentifier = newIdentifier.substring(0, newIdentifier.length()-1);
}
// check if resource should be created as a sub-resource
// of the current (this) resource
int delim = newIdentifier.indexOf('/');
if (delim < 0) {
// create new sub-resource if it does not yet exist
StorageResource resource = (StorageResource)subResource(newIdentifier);
if (resource == null) {
// create new resource and add it to the current resource
resource = new StorageResource(newIdentifier);
addSubResource(resource);
// store payload
resource.storeData(request);
// create new response
Response response = new Response(CodeRegistry.RESP_CREATED);
// inform client about the location of the new resource
response.setLocationPath(resource.getResourcePath());
// complete the request
request.respond(response);
} else {
// resource already exists; update data
storeData(request);
// complete the request
request.respond(CodeRegistry.RESP_CHANGED);
}
} else {
// split path in parent and sub identifier
String parentIdentifier = newIdentifier.substring(0, delim);
newIdentifier = newIdentifier.substring(delim+1);
// retrieve corresponding sub-resource, create if necessary
StorageResource sub = (StorageResource) subResource(parentIdentifier);
if (sub == null) {
sub = new StorageResource(parentIdentifier);
addSubResource(sub);
}
// delegate creation to the sub-resource
sub.createNew(request, newIdentifier);
}
}
/**
* DELETEs this storage resource, if it is not root.
*/
@Override
public void performDELETE(DELETERequest request) {
// disallow to remove the root "storage" resource
if (parent instanceof StorageResource) {
// remove this resource
remove();
request.respond(CodeRegistry.RESP_DELETED);
} else {
request.respond(CodeRegistry.RESP_FORBIDDEN,
"Root storage resource cannot be deleted");
}
}
// Internal ////////////////////////////////////////////////////////////////
/*
* Convenience function to store data contained in a
* PUT/POST-Request. Notifies observing endpoints about
* the change of its contents.
*/
private void storeData(Request request) {
// set payload and content type
data = request.getPayload();
setContentTypeCode(request.getContentType());
// signal that resource state changed
changed();
}
}