blob: 5c2d8f9e5297ef9862fffe4c9ad4b684aced49bf [file] [log] [blame]
/**
* Copyright (c) 2019 CEA.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* CEA - initial API and implementation and/or initial documentation
*/
package org.eclipse.sensinact.studio.model.manager.modelupdater;
import java.io.IOException;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import org.apache.log4j.Logger;
import org.eclipse.sensinact.studio.http.messages.snamessage.MsgSensinact;
import org.eclipse.sensinact.studio.http.messages.snamessage.attributevalueupdated.MsgAttributeValueUpdated;
import org.eclipse.sensinact.studio.http.messages.snamessage.completelist.MsgCompleteList;
import org.eclipse.sensinact.studio.http.messages.snamessage.completelist.ObjectProvider;
import org.eclipse.sensinact.studio.http.messages.snamessage.completelist.ObjectResource;
import org.eclipse.sensinact.studio.http.messages.snamessage.completelist.ObjectService;
import org.eclipse.sensinact.studio.http.messages.snamessage.lifecycle.MsgProviderAppearing;
import org.eclipse.sensinact.studio.http.messages.snamessage.lifecycle.MsgProviderDisappearing;
import org.eclipse.sensinact.studio.http.messages.snamessage.lifecycle.MsgResourceAppearing;
import org.eclipse.sensinact.studio.http.messages.snamessage.lifecycle.MsgResourceDisappearing;
import org.eclipse.sensinact.studio.http.messages.snamessage.lifecycle.MsgServiceAppearing;
import org.eclipse.sensinact.studio.http.messages.snamessage.lifecycle.MsgServiceDisappearing;
import org.eclipse.sensinact.studio.http.messages.snamessage.resourceslist.MsgResourcesList;
import org.eclipse.sensinact.studio.http.services.client.GatewayHttpClient;
import org.eclipse.sensinact.studio.http.services.client.GatewayHttpClient.RequestParameter;
import org.eclipse.sensinact.studio.http.services.client.connectionmanager.NotifDispatcher;
import org.eclipse.sensinact.studio.http.services.client.connectionmanager.NotifSubscriptionListener;
import org.eclipse.sensinact.studio.model.manager.listener.devicelocation.DeviceLocationManager;
import org.eclipse.sensinact.studio.model.resource.utils.DeviceDescriptor;
import org.eclipse.sensinact.studio.model.resource.utils.GPScoordinates;
import org.eclipse.sensinact.studio.model.resource.utils.GPSparsingException;
import org.eclipse.sensinact.studio.model.resource.utils.ResourceDescriptor;
import org.eclipse.sensinact.studio.model.resource.utils.Segments;
import org.eclipse.sensinact.studio.resource.AccessMethodType;
/**
* @author Nicolas Hili, Etienne Gandrille, Jander and others
*/
public class ModelUpdater implements NotifSubscriptionListener {
private static final Logger logger = Logger.getLogger(ModelUpdater.class);
private static ModelUpdater INSTANCE;
public static ModelUpdater getInstance() {
if (INSTANCE == null)
INSTANCE = new ModelUpdater();
return INSTANCE;
}
private ModelUpdater() {
NotifDispatcher.getInstance().subscribe(this);
}
/**
* Get all devices from the gateway and updates the model.
*
* @throws IOException
*/
public void updateDevices(final String gatewayName) throws IOException {
logger.info("Full model update triggered");
Segments segments = new Segments.Builder().gateway(gatewayName).root().build();
MsgSensinact snaMsg = GatewayHttpClient.sendGetRequest(segments);
if (!(snaMsg instanceof MsgCompleteList))
throwExceptionOnError(snaMsg, "devices");
MsgCompleteList list = (MsgCompleteList) snaMsg;
// update devices list
new Thread() {
public void run() {
try {
// add devices
for (ObjectProvider provider : list.getProviders())
ModelEditor.getInstance().addDeviceIfNotExist(gatewayName, provider.getName());
// remove device
for (String localDevicesId : ModelEditor.getInstance().getDevicesId(gatewayName)) {
Optional<String> elem = list.getProviders().stream().map(p -> p.getName()).filter(name -> name.equals(localDevicesId)).findFirst();
if( ! elem.isPresent()) {
ModelEditor.getInstance().removeDevice(gatewayName, localDevicesId);
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
// update locations
for (ObjectProvider provider : list.getProviders())
updateLocation(new DeviceDescriptor(gatewayName, provider.getName()), provider.getLocation());
}
/**
* Get all services on a device, from the gateway, and updates the model.
*
* @param device
* the device from which services need to be retrieved
* @throws IOException
*/
public void updateServices(final String gatewayName, final String deviceId, boolean recursive) throws IOException {
String expr = "jsonpath=$..*[?(@.name=='" + deviceId + "')]";
Segments segments = new Segments.Builder().gateway(gatewayName).jsonPath(expr).build();
MsgSensinact snaMsg = GatewayHttpClient.sendGetRequest(segments);
if (!(snaMsg instanceof MsgCompleteList))
throwExceptionOnError(snaMsg, "services");
MsgCompleteList list = (MsgCompleteList) snaMsg;
try {
ObjectProvider provider = list.getProvider(deviceId);
// location update
updateLocation(new DeviceDescriptor(gatewayName, deviceId), provider.getLocation());
// add service
for (ObjectService service : provider.getServices()) {
ModelEditor.getInstance().addServiceIfNotExist(gatewayName, deviceId, service.getName());
if(recursive) {
List<ObjectResource> resources = service.getResources();
for (ObjectResource resource : resources) {
ModelEditor.getInstance().addResourceIfNotExist(new ResourceDescriptor(gatewayName, deviceId, service.getName(), resource.getName()));
}
}
}
// remove service
for (String localServicesId : ModelEditor.getInstance().getServicesId(gatewayName, deviceId)) {
List<String> servicesIds = list.getProvider(deviceId).getServices().stream().map(s->s.getName()).collect(Collectors.toList());
if(!servicesIds.contains(localServicesId)){
ModelEditor.getInstance().removeService(gatewayName, deviceId, localServicesId);
}
}
} catch (Exception e) {
throw new IOException("Error while parsing infos for " + gatewayName + "/" + deviceId, e);
}
}
/* ================ */
/* Update resources */
/* ================ */
/**
* Retrieve all resources from one service
*
* @param service
* the service from which resources need to be retrieved
* @throws IOException
*/
public void updateResources(final String gatewayName, final String deviceId, final String serviceId) throws IOException {
Segments segment = new Segments.Builder().gateway(gatewayName).device(deviceId).service(serviceId).resources().build();
MsgSensinact msg = GatewayHttpClient.sendGetRequest(segment);
if (msg instanceof MsgResourcesList) {
List<String> resources = ((MsgResourcesList) msg).getResources();
for (int i = 0; i < resources.size(); i++) {
final String resourceId = resources.get(i);
ModelEditor.getInstance().addResourceIfNotExist(new ResourceDescriptor(gatewayName, deviceId, serviceId, resourceId));
}
} else {
throwExceptionOnError(msg, "resources");
}
}
/* =============== */
/* Update location */
/* =============== */
private void updateLocation(DeviceDescriptor descriptor, String coordinates) {
if (validCoordinates(coordinates)) {
try {
GPScoordinates gps = new GPScoordinates(coordinates);
DeviceLocationManager.getInstance().updateLocationInStudio(descriptor, gps);
} catch (GPSparsingException e) {
logger.error("coordinates parsing error for " + descriptor + " (" + coordinates + ")");
}
}
}
private static boolean validCoordinates(String coordinate) {
if (coordinate == null)
return false;
if (coordinate.isEmpty())
return false;
if (coordinate.equals("null"))
return false;
if (coordinate.equals("null:null"))
return false;
return true;
}
public boolean updateLocationOnServer(DeviceDescriptor deviceDescriptor, GPScoordinates coordinates) {
Segments segments = new Segments.Builder().device(deviceDescriptor).service("admin").resource("location").method(AccessMethodType.SET).build();
RequestParameter param = new RequestParameter("location", "java.lang.String", coordinates.getLat() + ":" + coordinates.getLng());
try {
MsgSensinact msg = GatewayHttpClient.sendPostRequest(segments, null,param);
return msg.isValid();
} catch (IOException e) {
logger.error("Update location on server failed", e);
return false;
}
}
/* ========= */
/* Listeners */
/* ========= */
@Override
public void onLifecycleEvent(String gateway, List<MsgSensinact> messages) {
for (MsgSensinact message : messages)
onLifecycleEvent(gateway, message);
}
public void onLifecycleEvent(String gateway, MsgSensinact message) {
if (message instanceof MsgProviderAppearing) {
MsgProviderAppearing msg = (MsgProviderAppearing) message;
String providerName = getProviderName(msg.getUri());
// updateLocation(new DeviceDescriptor(gateway, providerName), provider.getLocation());
executeInThread( () -> ModelEditor.getInstance().addDeviceIfNotExist(gateway, providerName));
} else if (message instanceof MsgProviderDisappearing) {
MsgProviderDisappearing msg = (MsgProviderDisappearing) message;
String providerName = getProviderName(msg.getUri());
DeviceLocationManager.getInstance().deleteDeviceInStudio(new DeviceDescriptor(gateway, providerName));
executeInThread( () -> ModelEditor.getInstance().removeDevice(gateway, providerName));
} else if (message instanceof MsgServiceAppearing) {
MsgServiceAppearing msg = (MsgServiceAppearing) message;
String providerName = getProviderName(msg.getUri());
String serviceName = getServiceName(msg.getUri());
executeInThread( () -> ModelEditor.getInstance().addServiceIfNotExist(gateway, providerName, serviceName));
} else if (message instanceof MsgServiceDisappearing) {
MsgServiceDisappearing msg = (MsgServiceDisappearing) message;
String providerName = getProviderName(msg.getUri());
String serviceName = getServiceName(msg.getUri());
executeInThread( () -> ModelEditor.getInstance().removeService(gateway, providerName, serviceName));
} else if (message instanceof MsgResourceAppearing) {
MsgResourceAppearing msg = (MsgResourceAppearing) message;
String providerName = getProviderName(msg.getUri());
String serviceName = getServiceName(msg.getUri());
String resourceName = getResourceName(msg.getUri());
executeInThread( () -> ModelEditor.getInstance().addResourceIfNotExist(new ResourceDescriptor(gateway, providerName, serviceName, resourceName)));
if ("location".equals(resourceName)) {
String coordinates = msg.getInitial().getValueAsString();
updateLocation(new DeviceDescriptor(gateway, providerName), coordinates);
}
} else if (message instanceof MsgResourceDisappearing) {
MsgResourceDisappearing msg = (MsgResourceDisappearing) message;
String providerName = getProviderName(msg.getUri());
String serviceName = getServiceName(msg.getUri());
String resourceName = getResourceName(msg.getUri());
executeInThread( () -> ModelEditor.getInstance().removeResource(new ResourceDescriptor(gateway, providerName, serviceName, resourceName)));
} else {
throw new RuntimeException("Unhandled lifecycle event of class " + message.getClass() + " " + message.toString());
}
}
@Override
public void onLocationEvent(String gateway, List<MsgSensinact> messages) {
for (MsgSensinact message : messages)
onLocationEvent(gateway, message);
}
public void onLocationEvent(String gatewayName, MsgSensinact message) {
if (message instanceof MsgAttributeValueUpdated) {
MsgAttributeValueUpdated msg = (MsgAttributeValueUpdated) message;
String deviceId = msg.getUri().split("/")[1];
String location = msg.getNotification().getValueAsString();
updateLocation(new DeviceDescriptor(gatewayName, deviceId), location);
}
}
@Override
public void onValueEvent(String gateway, List<MsgSensinact> messages) {
// do nothing
}
private static void executeInThread(Runnable r){
Runnable runnableWithTry = () -> {
try {
r.run();
} catch (Exception e) {
logger.error(e);
}
};
new Thread(runnableWithTry).start();
}
private String getProviderName(String uri) {
String[] tokens = uri.split("/");
return tokens[1];
}
private String getServiceName(String uri) {
String[] tokens = uri.split("/");
return tokens[2];
}
private String getResourceName(String uri) {
String[] tokens = uri.split("/");
return tokens[3];
}
/* ========= */
/* Exception */
/* ========= */
private void throwExceptionOnError(MsgSensinact response, String elementName) {
String fullMsg = String.format("Error sent by gateway while getting %s (%s)", elementName, response.getType());
throw new RuntimeException(fullMsg);
}
}