Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.eclipse.leshan.server.cluster.RedisRequestResponseHandler Maven / Gradle / Ivy
/*******************************************************************************
* Copyright (c) 2016 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.server.cluster;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.eclipse.californium.core.Utils;
import org.eclipse.leshan.core.node.LwM2mNode;
import org.eclipse.leshan.core.observation.Observation;
import org.eclipse.leshan.core.request.DownlinkRequest;
import org.eclipse.leshan.core.response.ErrorCallback;
import org.eclipse.leshan.core.response.LwM2mResponse;
import org.eclipse.leshan.core.response.ObserveResponse;
import org.eclipse.leshan.core.response.ResponseCallback;
import org.eclipse.leshan.server.LwM2mServer;
import org.eclipse.leshan.server.cluster.serialization.DownlinkRequestSerDes;
import org.eclipse.leshan.server.cluster.serialization.ResponseSerDes;
import org.eclipse.leshan.server.observation.ObservationListener;
import org.eclipse.leshan.server.observation.ObservationService;
import org.eclipse.leshan.server.registration.Registration;
import org.eclipse.leshan.server.registration.RegistrationService;
import org.eclipse.leshan.util.NamedThreadFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.eclipsesource.json.Json;
import com.eclipsesource.json.JsonObject;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPubSub;
import redis.clients.util.Pool;
/**
* Handle Request/Response Redis API.
* Send LWM2M Request to a registered LWM2M client when JSON Request Message is received on redis {@code LESHAN_REQ}
* channel.
* Send JSON Response Message on redis {@code LESHAN_RESP} channel when LWM2M Response is received from LWM2M Client.
*/
public class RedisRequestResponseHandler {
private static final Logger LOG = LoggerFactory.getLogger(RedisRequestResponseHandler.class);
private static final String REQUEST_CHANNEL = "LESHAN_REQ";
private static final String RESPONSE_CHANNEL = "LESHAN_RESP";
private final LwM2mServer server;
private final Pool pool;
private final RegistrationService registrationService;
private final ExecutorService executorService;
private final RedisTokenHandler tokenHandler;
private final ObservationService observationService;
private final Map observatioIdToTicket = new ConcurrentHashMap<>();
public RedisRequestResponseHandler(Pool p, LwM2mServer server, RegistrationService registrationService,
RedisTokenHandler tokenHandler, ObservationService observationService) {
// Listen LWM2M response
this.server = server;
this.registrationService = registrationService;
this.observationService = observationService;
this.tokenHandler = tokenHandler;
this.executorService = Executors.newCachedThreadPool(
new NamedThreadFactory(String.format("Redis %s channel writer", RESPONSE_CHANNEL)));
// Listen LWM2M notification from client
this.observationService.addListener(new ObservationListener() {
@Override
public void onResponse(Observation observation, Registration registration, ObserveResponse response) {
handleNotification(observation, response.getContent());
}
@Override
public void onError(Observation observation, Registration registration, Exception error) {
if (LOG.isWarnEnabled()) {
LOG.warn(String.format("Unable to handle notification of [%s:%s]", observation.getRegistrationId(),
observation.getPath()), error);
}
}
@Override
public void newObservation(Observation observation, Registration registration) {
}
@Override
public void cancelled(Observation observation) {
observatioIdToTicket.remove(new KeyId(observation.getId()));
}
});
// Listen redis "send request" channel
this.pool = p;
new Thread(new Runnable() {
@Override
public void run() {
do {
try (Jedis j = pool.getResource()) {
j.subscribe(new JedisPubSub() {
@Override
public void onMessage(String channel, String message) {
handleSendRequestMessage(message);
}
}, REQUEST_CHANNEL);
} catch (RuntimeException e) {
LOG.warn("Redis SUBSCRIBE interrupted.", e);
}
// wait & re-launch
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
LOG.warn("Relaunch Redis SUBSCRIBE.");
} while (true);
}
}, String.format("Redis %s channel reader", REQUEST_CHANNEL)).start();
}
private void handleResponse(String clientEndpoint, final String ticket, final LwM2mResponse response) {
executorService.submit(new Runnable() {
@Override
public void run() {
try {
sendResponse(ticket, response);
} catch (RuntimeException t) {
LOG.error("Unable to send response.", t);
sendError(ticket,
String.format("Expected error while sending LWM2M response.(%s)", t.getMessage()));
}
}
});
}
private void handleNotification(final Observation observation, final LwM2mNode value) {
executorService.submit(new Runnable() {
@Override
public void run() {
String ticket = observatioIdToTicket.get(new KeyId(observation.getId()));
try {
sendNotification(ticket, value);
} catch (RuntimeException t) {
LOG.error("Unable to send Notification.", t);
sendError(ticket,
String.format("Expected error while sending LWM2M Notification.(%s)", t.getMessage()));
}
}
});
}
private void handlerError(String clientEndpoint, final String ticket, final Exception exception) {
executorService.submit(new Runnable() {
@Override
public void run() {
try {
sendError(ticket, exception.getMessage());
} catch (RuntimeException t) {
LOG.error("Unable to send error message.", t);
}
}
});
}
private void handleSendRequestMessage(final String message) {
executorService.submit(new Runnable() {
@Override
public void run() {
sendRequest(message);
}
});
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private void sendRequest(final String message) {
// Parse JSON and extract ticket
final String ticket;
JsonObject jMessage;
try {
jMessage = (JsonObject) Json.parse(message);
ticket = jMessage.getString("ticket", null);
} catch (RuntimeException t) {
LOG.error(String.format("Unexpected exception during request message handling. (%s)", message), t);
return;
}
// Now if an error occurred we can prevent message sender
try {
// Check if we must handle this request
String endpoint = jMessage.getString("ep", null);
if (!isResponsibleFor(endpoint))
return;
// Get the registration for this endpoint
final Registration destination = registrationService.getByEndpoint(endpoint);
if (destination == null) {
sendError(ticket, String.format("No registration for this endpoint %s.", endpoint));
}
// Deserialize Request
DownlinkRequest> request = DownlinkRequestSerDes.deserialize((JsonObject) jMessage.get("req"));
// Ack we will handle this request
sendAck(ticket);
// Send it
server.send(destination, request, new ResponseCallback() {
@Override
public void onResponse(LwM2mResponse response) {
handleResponse(destination.getEndpoint(), ticket, response);
}
}, new ErrorCallback() {
@Override
public void onError(Exception e) {
handlerError(destination.getEndpoint(), ticket, e);
}
});
} catch (RuntimeException t) {
String errorMessage = String.format("Unexpected exception during request message handling.(%s:%s)",
t.toString(), t.getMessage());
LOG.error(errorMessage, t);
sendError(ticket, errorMessage);
}
}
private boolean isResponsibleFor(String endpoint) {
return tokenHandler.isResponsible(endpoint);
}
private void sendAck(String ticket) {
try (Jedis j = pool.getResource()) {
JsonObject m = Json.object();
m.add("ticket", ticket);
m.add("ack", true);
j.publish(RESPONSE_CHANNEL, m.toString());
}
}
private void sendError(String ticket, String message) {
try (Jedis j = pool.getResource()) {
JsonObject m = Json.object();
m.add("ticket", ticket);
JsonObject err = Json.object();
err.add("errorMessage", message);
m.add("err", err);
j.publish(RESPONSE_CHANNEL, m.toString());
}
}
private void sendNotification(String ticket, LwM2mNode value) {
try (Jedis j = pool.getResource()) {
JsonObject m = Json.object();
m.add("ticket", ticket);
m.add("rep", ResponseSerDes.jSerialize(ObserveResponse.success(value)));
j.publish(RESPONSE_CHANNEL, m.toString());
}
}
private void sendResponse(String ticket, LwM2mResponse response) {
if (response instanceof ObserveResponse) {
Observation observation = ((ObserveResponse) response).getObservation();
observatioIdToTicket.put(new KeyId(observation.getId()), ticket);
}
try (Jedis j = pool.getResource()) {
JsonObject m = Json.object();
m.add("ticket", ticket);
m.add("rep", ResponseSerDes.jSerialize(response));
j.publish(RESPONSE_CHANNEL, m.toString());
}
}
public static final class KeyId {
protected final byte[] id;
private final int hash;
public KeyId(byte[] token) {
if (token == null)
throw new NullPointerException();
this.id = token;
this.hash = Arrays.hashCode(token);
}
@Override
public int hashCode() {
return hash;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof KeyId))
return false;
KeyId key = (KeyId) o;
return Arrays.equals(id, key.id);
}
@Override
public String toString() {
return new StringBuilder("KeyId[").append(Utils.toHexString(id)).append("]").toString();
}
}
}