/******************************************************************************* * Copyright (c) 2015 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 * Achim Kraus (Bosch Software Innovations GmbH) - use ObserveRelationFilter * Achim Kraus (Bosch Software Innovations GmbH) - use ServerIdentity * Achim Kraus (Bosch Software Innovations GmbH) - implement POST "/oid/iid" * as UPDATE instance *******************************************************************************/ package org.eclipse.leshan.client.californium.impl; import static org.eclipse.leshan.client.californium.impl.ResourceUtil.extractServerIdentity; import static org.eclipse.leshan.core.californium.ResponseCodeUtil.fromLwM2mCode; import java.util.List; import org.eclipse.californium.core.CoapResource; import org.eclipse.californium.core.coap.CoAP.ResponseCode; import org.eclipse.californium.core.coap.MediaTypeRegistry; import org.eclipse.californium.core.coap.Response; import org.eclipse.californium.core.network.Exchange; import org.eclipse.californium.core.server.resources.CoapExchange; import org.eclipse.californium.core.server.resources.Resource; import org.eclipse.leshan.Link; import org.eclipse.leshan.ObserveSpec; import org.eclipse.leshan.client.request.ServerIdentity; import org.eclipse.leshan.client.resource.LwM2mObjectEnabler; import org.eclipse.leshan.client.resource.NotifySender; import org.eclipse.leshan.client.servers.BootstrapHandler; import org.eclipse.leshan.core.model.LwM2mModel; import org.eclipse.leshan.core.node.LwM2mNode; import org.eclipse.leshan.core.node.LwM2mObjectInstance; import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.node.codec.CodecException; import org.eclipse.leshan.core.node.codec.LwM2mNodeDecoder; import org.eclipse.leshan.core.node.codec.LwM2mNodeEncoder; import org.eclipse.leshan.core.request.BootstrapWriteRequest; import org.eclipse.leshan.core.request.ContentFormat; import org.eclipse.leshan.core.request.CreateRequest; import org.eclipse.leshan.core.request.DeleteRequest; import org.eclipse.leshan.core.request.DiscoverRequest; import org.eclipse.leshan.core.request.ExecuteRequest; import org.eclipse.leshan.core.request.ObserveRequest; import org.eclipse.leshan.core.request.ReadRequest; import org.eclipse.leshan.core.request.WriteAttributesRequest; import org.eclipse.leshan.core.request.WriteRequest; import org.eclipse.leshan.core.request.WriteRequest.Mode; import org.eclipse.leshan.core.request.exception.InvalidRequestException; import org.eclipse.leshan.core.response.BootstrapWriteResponse; import org.eclipse.leshan.core.response.CreateResponse; import org.eclipse.leshan.core.response.DeleteResponse; import org.eclipse.leshan.core.response.DiscoverResponse; import org.eclipse.leshan.core.response.ExecuteResponse; import org.eclipse.leshan.core.response.ObserveResponse; import org.eclipse.leshan.core.response.ReadResponse; import org.eclipse.leshan.core.response.WriteAttributesResponse; import org.eclipse.leshan.core.response.WriteResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * A CoAP {@link Resource} in charge of handling requests for of a lwM2M Object. */ public class ObjectResource extends CoapResource implements NotifySender { private static final Logger LOG = LoggerFactory.getLogger(ObjectResource.class); private final LwM2mObjectEnabler nodeEnabler; private final BootstrapHandler bootstrapHandler; private final LwM2mNodeEncoder encoder; private final LwM2mNodeDecoder decoder; public ObjectResource(LwM2mObjectEnabler nodeEnabler, BootstrapHandler bootstrapHandler, LwM2mNodeEncoder encoder, LwM2mNodeDecoder decoder) { super(Integer.toString(nodeEnabler.getId())); this.nodeEnabler = nodeEnabler; this.nodeEnabler.setNotifySender(this); this.bootstrapHandler = bootstrapHandler; this.encoder = encoder; this.decoder = decoder; setObservable(true); } @Override public void handleRequest(Exchange exchange) { try { super.handleRequest(exchange); } catch (InvalidRequestException e) { if (LOG.isDebugEnabled()) { LOG.debug(String.format("InvalidRequestException while handling request(%s) on the %s resource", exchange.getRequest(), getURI()), e); } Response response = new Response(ResponseCode.BAD_REQUEST); response.setPayload(e.getMessage()); exchange.sendResponse(response); } catch (Exception e) { LOG.error(String.format("Exception while handling request(%s) on the %s resource", exchange.getRequest(), getURI()), e); exchange.sendResponse(new Response(ResponseCode.INTERNAL_SERVER_ERROR)); } } @Override public void handleGET(CoapExchange exchange) { ServerIdentity identity = extractServerIdentity(exchange, bootstrapHandler); String URI = exchange.getRequestOptions().getUriPathString(); // Manage Discover Request if (exchange.getRequestOptions().getAccept() == MediaTypeRegistry.APPLICATION_LINK_FORMAT) { DiscoverResponse response = nodeEnabler.discover(identity, new DiscoverRequest(URI)); if (response.getCode().isError()) { exchange.respond(fromLwM2mCode(response.getCode()), response.getErrorMessage()); } else { exchange.respond(fromLwM2mCode(response.getCode()), Link.serialize(response.getObjectLinks()), MediaTypeRegistry.APPLICATION_LINK_FORMAT); } } else { // handle content format for Read and Observe Request ContentFormat format = ContentFormat.TLV; // use TLV as default format if (exchange.getRequestOptions().hasAccept()) { format = ContentFormat.fromCode(exchange.getRequestOptions().getAccept()); if (!encoder.isSupported(format)) { exchange.respond(ResponseCode.NOT_ACCEPTABLE); return; } } // Manage Observe Request if (exchange.getRequestOptions().hasObserve()) { ObserveResponse response = nodeEnabler.observe(identity, new ObserveRequest(URI)); if (response.getCode() == org.eclipse.leshan.ResponseCode.CONTENT) { LwM2mPath path = new LwM2mPath(URI); LwM2mNode content = response.getContent(); LwM2mModel model = new LwM2mModel(nodeEnabler.getObjectModel()); exchange.respond(ResponseCode.CONTENT, encoder.encode(content, format, path, model), format.getCode()); return; } else { exchange.respond(fromLwM2mCode(response.getCode()), response.getErrorMessage()); return; } } // Manage Read Request else { ReadResponse response = nodeEnabler.read(identity, new ReadRequest(URI)); if (response.getCode() == org.eclipse.leshan.ResponseCode.CONTENT) { LwM2mPath path = new LwM2mPath(URI); LwM2mNode content = response.getContent(); LwM2mModel model = new LwM2mModel(nodeEnabler.getObjectModel()); exchange.respond(ResponseCode.CONTENT, encoder.encode(content, format, path, model), format.getCode()); return; } else { exchange.respond(fromLwM2mCode(response.getCode()), response.getErrorMessage()); return; } } } } @Override public void handlePUT(CoapExchange coapExchange) { ServerIdentity identity = extractServerIdentity(coapExchange, bootstrapHandler); String URI = coapExchange.getRequestOptions().getUriPathString(); // get Observe Spec ObserveSpec spec = null; if (coapExchange.advanced().getRequest().getOptions().getURIQueryCount() != 0) { List<String> uriQueries = coapExchange.advanced().getRequest().getOptions().getUriQuery(); spec = ObserveSpec.parse(uriQueries); } // Manage Write Attributes Request if (spec != null) { WriteAttributesResponse response = nodeEnabler.writeAttributes(identity, new WriteAttributesRequest(URI, spec)); coapExchange.respond(fromLwM2mCode(response.getCode()), response.getErrorMessage()); return; } // Manage Write and Bootstrap Write Request (replace) else { LwM2mPath path = new LwM2mPath(URI); if (!coapExchange.getRequestOptions().hasContentFormat()) { coapExchange.respond(ResponseCode.BAD_REQUEST, "Content Format is mandatory"); return; } ContentFormat contentFormat = ContentFormat.fromCode(coapExchange.getRequestOptions().getContentFormat()); if (!decoder.isSupported(contentFormat)) { coapExchange.respond(ResponseCode.UNSUPPORTED_CONTENT_FORMAT); return; } LwM2mNode lwM2mNode; try { LwM2mModel model = new LwM2mModel(nodeEnabler.getObjectModel()); lwM2mNode = decoder.decode(coapExchange.getRequestPayload(), contentFormat, path, model); if (identity.isLwm2mBootstrapServer()) { BootstrapWriteResponse response = nodeEnabler.write(identity, new BootstrapWriteRequest(path, lwM2mNode, contentFormat)); coapExchange.respond(fromLwM2mCode(response.getCode()), response.getErrorMessage()); } else { WriteResponse response = nodeEnabler.write(identity, new WriteRequest(Mode.REPLACE, contentFormat, URI, lwM2mNode)); coapExchange.respond(fromLwM2mCode(response.getCode()), response.getErrorMessage()); } return; } catch (CodecException e) { LOG.warn("Unable to decode payload to write", e); coapExchange.respond(ResponseCode.BAD_REQUEST); return; } } } @Override public void handlePOST(CoapExchange exchange) { ServerIdentity identity = extractServerIdentity(exchange, bootstrapHandler); String URI = exchange.getRequestOptions().getUriPathString(); LwM2mPath path = new LwM2mPath(URI); // Manage Execute Request if (path.isResource()) { byte[] payload = exchange.getRequestPayload(); ExecuteResponse response = nodeEnabler.execute(identity, new ExecuteRequest(URI, payload != null ? new String(payload) : null)); exchange.respond(fromLwM2mCode(response.getCode()), response.getErrorMessage()); return; } // handle content format for Write (Update) and Create request if (!exchange.getRequestOptions().hasContentFormat()) { exchange.respond(ResponseCode.BAD_REQUEST, "Content Format is mandatory"); return; } ContentFormat contentFormat = ContentFormat.fromCode(exchange.getRequestOptions().getContentFormat()); if (!decoder.isSupported(contentFormat)) { exchange.respond(ResponseCode.UNSUPPORTED_CONTENT_FORMAT); return; } LwM2mModel model = new LwM2mModel(nodeEnabler.getObjectModel()); // Manage Update Instance if (path.isObjectInstance()) { try { LwM2mNode lwM2mNode = decoder.decode(exchange.getRequestPayload(), contentFormat, path, model); WriteResponse response = nodeEnabler.write(identity, new WriteRequest(Mode.UPDATE, contentFormat, URI, lwM2mNode)); exchange.respond(fromLwM2mCode(response.getCode()), response.getErrorMessage()); } catch (CodecException e) { LOG.warn("Unable to decode payload to write", e); exchange.respond(ResponseCode.BAD_REQUEST); } return; } // Manage Create Request try { // decode the payload as an instance LwM2mObjectInstance newInstance = decoder.decode(exchange.getRequestPayload(), contentFormat, new LwM2mPath(path.getObjectId()), model, LwM2mObjectInstance.class); CreateRequest createRequest; if (newInstance.getId() != LwM2mObjectInstance.UNDEFINED) { createRequest = new CreateRequest(contentFormat, path.getObjectId(), newInstance); } else { // the instance Id was not part of the create request payload. // will be assigned by the client. createRequest = new CreateRequest(contentFormat, path.getObjectId(), newInstance.getResources().values()); } CreateResponse response = nodeEnabler.create(identity, createRequest); if (response.getCode() == org.eclipse.leshan.ResponseCode.CREATED) { exchange.setLocationPath(response.getLocation()); exchange.respond(fromLwM2mCode(response.getCode())); return; } else { exchange.respond(fromLwM2mCode(response.getCode()), response.getErrorMessage()); return; } } catch (CodecException e) { LOG.warn("Unable to decode payload to create", e); exchange.respond(ResponseCode.BAD_REQUEST); return; } } @Override public void handleDELETE(CoapExchange coapExchange) { // Manage Delete Request String URI = coapExchange.getRequestOptions().getUriPathString(); ServerIdentity identity = extractServerIdentity(coapExchange, bootstrapHandler); DeleteResponse response = nodeEnabler.delete(identity, new DeleteRequest(URI)); coapExchange.respond(fromLwM2mCode(response.getCode()), response.getErrorMessage()); } @Override public void sendNotify(String URI) { changed(new ResourceObserveFilter(URI)); } /* * Override the default behavior so that requests to sub resources (typically /ObjectId/*) are handled by this * resource. */ @Override public Resource getChild(String name) { return this; } }