| /* |
| * Copyright (c) 2020 Kentyou. |
| * 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: |
| * Kentyou - initial API and implementation |
| */ |
| package org.eclipse.sensinact.gateway.core; |
| |
| import java.security.AccessController; |
| import java.security.PrivilegedAction; |
| import java.util.ArrayList; |
| import java.util.Dictionary; |
| import java.util.Enumeration; |
| import java.util.HashMap; |
| import java.util.Hashtable; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.eclipse.sensinact.gateway.core.ServiceProvider.LifecycleStatus; |
| import org.eclipse.sensinact.gateway.core.message.AbstractMidCallback; |
| import org.eclipse.sensinact.gateway.core.message.SnaLifecycleMessage; |
| import org.eclipse.sensinact.gateway.core.message.SnaLifecycleMessage.Lifecycle; |
| import org.eclipse.sensinact.gateway.core.message.SnaLifecycleMessageImpl; |
| import org.eclipse.sensinact.gateway.core.message.SnaMessage; |
| import org.eclipse.sensinact.gateway.core.message.SnaMessageSubType; |
| import org.eclipse.sensinact.gateway.core.message.SnaUpdateMessage; |
| import org.eclipse.sensinact.gateway.core.method.AccessMethod; |
| import org.eclipse.sensinact.gateway.core.security.AccessLevelOption; |
| import org.eclipse.sensinact.gateway.core.security.MutableAccessNode; |
| import org.eclipse.sensinact.gateway.util.UriUtils; |
| import org.json.JSONObject; |
| import org.osgi.framework.ServiceReference; |
| import org.osgi.framework.ServiceRegistration; |
| |
| /** |
| * Wraps the {@link ServiceRegistration} of a {@link SensiNactResourceModel} |
| * instance and updates the properties of its associated |
| * {@link ServiceReference}. |
| * |
| * @author <a href="mailto:christophe.munilla@cea.fr">Christophe Munilla</a> |
| */ |
| public class ModelInstanceRegistration extends AbstractMidCallback { |
| public static final String LOCATION_PROPERTY = "admin.".concat(LocationResource.LOCATION); |
| |
| private boolean registered; |
| private ServiceRegistration<?> instanceRegistration; |
| private ModelConfiguration configuration; |
| private Map<String, List<String>> observed; |
| |
| /** |
| * Constructor |
| * |
| * @param path |
| * the String uri of the {@link SensiNactResourceModel} registered |
| * @param observed |
| * the list of observed String paths |
| * @param registration |
| * the {@link ServiceRegistration} of the |
| * {@link SensiNactResourceModel} |
| * @param configuration |
| * the {@link ModelConfiguration} of the {@link ModelInstance} whose |
| * registration will be wrapped by the ModelInstanceRegistration to |
| * be instantiated |
| */ |
| public ModelInstanceRegistration(String path, List<String> observed, ServiceRegistration<?> registration, |
| ModelConfiguration configuration) { |
| super(false); |
| this.observed = new HashMap<String, List<String>>(); |
| |
| if (observed != null && !observed.isEmpty()) { |
| Iterator<String> it = observed.iterator(); |
| while (it.hasNext()) { |
| String obs = it.next(); |
| String[] obsEls = UriUtils.getUriElements(obs); |
| int length = obsEls == null ? 0 : obsEls.length; |
| |
| String attribute = null; |
| switch (length) { |
| case 0: |
| case 1: |
| continue; |
| case 2: |
| attribute = DataResource.VALUE; |
| break; |
| case 3: |
| attribute = obsEls[2]; |
| break; |
| default: |
| continue; |
| } |
| String key = new StringBuilder().append(obsEls[0]).append(".").append(obsEls[1]).toString(); |
| List<String> list = this.observed.get(key); |
| if (list == null) { |
| list = new ArrayList<String>(); |
| this.observed.put(key, list); |
| } |
| if (!list.contains(attribute)) { |
| list.add(attribute); |
| } |
| } |
| } |
| List<String> list = this.observed.get(LOCATION_PROPERTY); |
| if (list == null) { |
| list = new ArrayList<String>(); |
| this.observed.put(LOCATION_PROPERTY, list); |
| } |
| if (!list.contains(DataResource.VALUE)) { |
| list.add(DataResource.VALUE); |
| } |
| this.instanceRegistration = registration; |
| this.configuration = configuration; |
| this.registered = true; |
| } |
| |
| /** |
| * Unregisters the {@link ModelInstance} service registration of this |
| * ModelInstanceRegistration |
| */ |
| public void unregister() { |
| this.registered = false; |
| if (this.instanceRegistration != null) { |
| this.instanceRegistration.unregister(); |
| } |
| } |
| |
| void update(final Dictionary<String, Object> properties) { |
| if (!registered || properties == null || properties.size() == 0 || this.instanceRegistration == null) { |
| return; |
| } |
| synchronized (this.instanceRegistration) { |
| AccessController.<Void>doPrivileged(new PrivilegedAction<Void>() { |
| @Override |
| public Void run() { |
| try { |
| ModelInstanceRegistration.this.instanceRegistration.setProperties(properties); |
| |
| } catch (IllegalArgumentException e) { |
| // if it is a duplicate service property |
| // try to retrieve it and to remove it |
| String message = e.getMessage(); |
| String duplicateMessage = "Duplicate service property: "; |
| String duplicateProperty = null; |
| if (message.startsWith(duplicateMessage)) { |
| duplicateProperty = message.substring(duplicateMessage.length()); |
| } |
| if (duplicateProperty != null && properties.remove(duplicateProperty) != null) { |
| update(properties); |
| } |
| } catch (Exception e) { |
| e.printStackTrace(); |
| } |
| return null; |
| } |
| }); |
| } |
| } |
| |
| private Dictionary<String, Object> properties() { |
| final Hashtable<String, Object> properties = new Hashtable<String, Object>(); |
| |
| if (this.instanceRegistration == null) { |
| return properties; |
| } |
| synchronized (this.instanceRegistration) { |
| AccessController.<Void>doPrivileged(new PrivilegedAction<Void>() { |
| @Override |
| public Void run() { |
| ServiceReference<?> ref = null; |
| if ((ref = ModelInstanceRegistration.this.instanceRegistration.getReference()) != null) { |
| String[] keys = ref.getPropertyKeys(); |
| for (String key : keys) { |
| properties.put(key, ref.getProperty(key)); |
| } |
| } |
| return null; |
| } |
| }); |
| } |
| return properties; |
| } |
| |
| protected void updateLifecycle(LifecycleStatus status) { |
| if (!registered) { |
| return; |
| } |
| Dictionary<String, Object> properties = properties(); |
| properties.put("lifecycle.status", status.name()); |
| this.update(properties); |
| } |
| |
| private final void updateObserved(String observed, Object value) { |
| if (!registered || observed == null) { |
| return; |
| } |
| Dictionary<String, Object> properties = properties(); |
| properties.remove(observed); |
| |
| if (observed.startsWith(LOCATION_PROPERTY)) { |
| properties.remove("latitude"); |
| properties.remove("longitude"); |
| |
| double latitude = 0d; |
| double longitude = 0d; |
| |
| String[] latlon = value == null ? new String[] {} : String.valueOf(value).split(":"); |
| |
| if (latlon.length == 2) { |
| try { |
| latitude = Double.parseDouble(latlon[0]); |
| longitude = Double.parseDouble(latlon[1]); |
| |
| properties.put("latitude", latitude); |
| properties.put("longitude", longitude); |
| |
| } catch (NumberFormatException e) { |
| this.configuration.mediator.debug(e.getMessage()); |
| } |
| } |
| } |
| if (value == null) { |
| return; |
| } |
| properties.put(observed, value); |
| this.update(properties); |
| } |
| |
| /** |
| */ |
| public void updateContent(SnaLifecycleMessage.Lifecycle lifecycle, String uri, JSONObject initial, String type) { |
| if (!registered) { |
| return; |
| } |
| String[] uriElements = UriUtils.getUriElements(uri); |
| int length = uriElements == null ? 0 : uriElements.length; |
| String service = (length > 1) ? uriElements[1] : null; |
| if (service == null) { |
| return; |
| } |
| MutableAccessNode node = null; |
| MutableAccessNode root = this.configuration.getAccessTree().getRoot(); |
| if ((node = (MutableAccessNode) root.get(uri)) == null) { |
| node = root; |
| } |
| String resource = (length > 2) ? uriElements[2] : null; |
| boolean added = !lifecycle.equals(Lifecycle.RESOURCE_DISAPPEARING) |
| && !lifecycle.equals(Lifecycle.SERVICE_DISAPPEARING); |
| |
| Dictionary<String, Object> properties = properties(); |
| |
| if (resource != null) { |
| if (added) { |
| updateResourceAppearing(service, resource, type, initial, node, properties); |
| } else { |
| updateResourceDisappearing(service, resource, node, properties); |
| } |
| } else { |
| updateService(service, node, added, properties); |
| } |
| this.update(properties); |
| } |
| |
| private final void updateResourceAppearing(String service, String resource, String type, JSONObject initial, |
| MutableAccessNode node, Dictionary<String, Object> properties) { |
| if (!registered || service == null || resource == null) { |
| return; |
| } |
| AccessMethod.Type[] accessMethodTypes = AccessMethod.Type.values(); |
| int typesLength = accessMethodTypes == null ? 0 : accessMethodTypes.length; |
| |
| String serviceKey = service.concat(".resources"); |
| String resourceKey = new StringBuilder().append(service).append(".").append(resource).toString(); |
| |
| List<String> resources = (List<String>) properties.get(serviceKey); |
| if (resources == null) { |
| resources = new ArrayList<String>(); |
| properties.put(serviceKey, resources); |
| } |
| List<String> attributes = this.observed.get(resourceKey); |
| if (attributes != null && !attributes.isEmpty()) { |
| Iterator<String> it = attributes.iterator(); |
| String name = initial == null ? null : initial.optString(Resource.NAME); |
| |
| while (it.hasNext()) { |
| Object value = null; |
| String attribute = it.next(); |
| |
| if (attribute.equals(name) || (attribute.equals(DataResource.VALUE) && resource.equals(name))) |
| value = initial.opt(DataResource.VALUE); |
| |
| if (LOCATION_PROPERTY.equals(resourceKey)) { |
| double latitude = 0d; |
| double longitude = 0d; |
| |
| String[] latlon = value == null ? new String[] {} : String.valueOf(value).split(":"); |
| |
| if (latlon.length == 2) { |
| try { |
| latitude = Double.parseDouble(latlon[0]); |
| longitude = Double.parseDouble(latlon[1]); |
| |
| properties.put("latitude", latitude); |
| properties.put("longitude", longitude); |
| |
| } catch (NumberFormatException e) { |
| configuration.mediator.debug(e.getMessage()); |
| } |
| } |
| } |
| if (value != null) { |
| properties.put(new StringBuilder().append(resourceKey).append(".").append(attribute).toString(), |
| value); |
| } |
| } |
| } |
| resources.add(resource); |
| properties.put(resourceKey.concat(".type"), type); |
| |
| int index = 0; |
| for (; index < typesLength; index++) { |
| AccessLevelOption accessLevelOption = node.getAccessLevelOption(accessMethodTypes[index]); |
| |
| properties.put(new StringBuilder().append(resourceKey).append(".").append(accessMethodTypes[index].name()) |
| .toString(), accessLevelOption.getAccessLevel().getLevel()); |
| } |
| } |
| |
| private final void updateResourceDisappearing(String service, String resource, MutableAccessNode node, |
| Dictionary<String, Object> properties) { |
| if (!registered || service == null || resource == null) { |
| return; |
| } |
| AccessMethod.Type[] accessMethodTypes = AccessMethod.Type.values(); |
| int typesLength = accessMethodTypes == null ? 0 : accessMethodTypes.length; |
| |
| String serviceKey = service.concat(".resources"); |
| String resourceKey = new StringBuilder().append(service).append(".").append(resource).toString(); |
| |
| List<String> resources = (List<String>) properties.get(serviceKey); |
| if (resources != null) |
| resources.remove(resource); |
| |
| properties.remove(resourceKey.concat(".type")); |
| int index = 0; |
| for (; index < typesLength; index++) { |
| properties.remove(new StringBuilder().append(resourceKey).append(".") |
| .append(accessMethodTypes[index].name()).toString()); |
| } |
| } |
| |
| private final void updateService(String service, MutableAccessNode node, boolean added, |
| Dictionary<String, Object> properties) { |
| if (!registered || service == null) { |
| return; |
| } |
| List<String> services = (List<String>) properties.get("services"); |
| if (services == null) { |
| services = new ArrayList<String>(); |
| } |
| if (added) { |
| AccessMethod.Type[] accessMethodTypes = AccessMethod.Type.values(); |
| int typesLength = accessMethodTypes == null ? 0 : accessMethodTypes.length; |
| |
| int index = 0; |
| for (; index < typesLength; index++) { |
| AccessLevelOption accessLevelOption = node.getAccessLevelOption(accessMethodTypes[index]); |
| |
| properties.put(new StringBuilder().append(service).append(".").append(accessMethodTypes[index].name()) |
| .toString(), accessLevelOption.getAccessLevel().getLevel()); |
| } |
| services.add(service); |
| } else { |
| services.remove(service); |
| List<String> tobeRemoved = new ArrayList<>(); |
| Enumeration<String> enumeration = properties.keys(); |
| while (enumeration.hasMoreElements()) { |
| String key = enumeration.nextElement(); |
| if (key != null && key.startsWith(service.concat("."))) { |
| tobeRemoved.add(key); |
| } |
| } |
| Iterator<String> iterator = tobeRemoved.iterator(); |
| while (iterator.hasNext()) { |
| properties.remove(iterator.next()); |
| } |
| } |
| properties.put("services", services); |
| } |
| |
| @Override |
| public synchronized void doCallback(SnaMessage<?> message) { |
| |
| String uri = message.getPath(); |
| String[] uriElements = UriUtils.getUriElements(uri); |
| switch (((SnaMessageSubType) message.getType()).getSnaMessageType()) { |
| case UPDATE: |
| SnaUpdateMessage m = (SnaUpdateMessage) message; |
| JSONObject notification = m.getNotification(); |
| String key = new StringBuilder().append(uriElements[1]).append(".").append(uriElements[2]).toString(); |
| switch(m.getType()) { |
| case ATTRIBUTE_VALUE_UPDATED: |
| List<String> obs = this.observed.get(key); |
| if (obs != null && !obs.isEmpty() && obs.contains(uriElements[3])) { |
| Object value = notification.opt(DataResource.VALUE); |
| this.updateObserved(new StringBuilder().append(key).append("." |
| ).append(uriElements[3]).toString(), value); |
| } |
| break; |
| case METADATA_VALUE_UPDATED: |
| Object value = notification.opt(DataResource.VALUE); |
| this.updateObserved(new StringBuilder().append(key).append("." |
| ).append(uriElements[3]).append(".").append(uriElements[4]).toString(), value); |
| break; |
| case ACTUATED: |
| break; |
| } |
| break; |
| case LIFECYCLE: |
| SnaLifecycleMessage l = (SnaLifecycleMessage) message; |
| String type = null; |
| JSONObject initial = null; |
| switch (l.getType()) { |
| case RESOURCE_APPEARING: |
| initial = (JSONObject) ((SnaLifecycleMessageImpl) l).get("initial"); |
| type = ((SnaLifecycleMessageImpl) l).getNotification().optString("type"); |
| ResourceConfig config = configuration.getResourceConfig(new ResourceDescriptor( |
| ).withResourceName(uriElements[2] |
| ).withServiceName(uriElements[1])); |
| List<String> observeds = null; |
| if(config!=null) |
| observeds = config.getObserveds(uriElements[1]); |
| if (observeds != null && !observeds.isEmpty()) { |
| Iterator<String> it = observeds.iterator(); |
| while (it.hasNext()) { |
| String attr = null; |
| String s = it.next(); |
| String[] obsEls = UriUtils.getUriElements(s); |
| int length = obsEls == null ? 0 : obsEls.length; |
| switch (length) { |
| case 0: |
| case 1: |
| continue; |
| case 2: |
| attr = DataResource.VALUE; |
| break; |
| case 3: |
| attr = obsEls[2]; |
| break; |
| default: |
| continue; |
| } |
| String observedKey = new StringBuilder().append(obsEls[0]).append(".").append(obsEls[1]).toString(); |
| List<String> list = this.observed.get(observedKey); |
| if (list == null) { |
| list = new ArrayList<>(); |
| this.observed.put(observedKey, list); |
| } |
| if (!list.contains(attr)) |
| list.add(attr); |
| } |
| } |
| case SERVICE_APPEARING: |
| case PROVIDER_DISAPPEARING: |
| case RESOURCE_DISAPPEARING: |
| case SERVICE_DISAPPEARING: |
| this.updateContent(l.getType(), uri, initial, type); |
| case PROVIDER_APPEARING: |
| default: |
| break; |
| } |
| break; |
| case ERROR: |
| case REMOTE: |
| case RESPONSE: |
| default: |
| break; |
| } |
| } |
| } |