blob: d0f79e582401ea185986aaf96d070dd44d4859d9 [file] [log] [blame]
/*
* Copyright (c) 2017 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
*/
package org.eclipse.sensinact.gateway.core;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Collection;
import java.util.Dictionary;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import org.eclipse.sensinact.gateway.api.core.AttributeBuilder;
import org.eclipse.sensinact.gateway.api.core.Core;
import org.eclipse.sensinact.gateway.api.core.DataResource;
import org.eclipse.sensinact.gateway.api.core.ServiceProvider;
import org.eclipse.sensinact.gateway.api.core.ServiceProvider.LifecycleStatus;
import org.eclipse.sensinact.gateway.api.message.AbstractSnaMessage;
import org.eclipse.sensinact.gateway.api.message.MessageCallback;
import org.eclipse.sensinact.gateway.api.message.MessagePropagator;
import org.eclipse.sensinact.gateway.api.message.SnaMessage;
import org.eclipse.sensinact.gateway.common.bundle.Mediator;
import org.eclipse.sensinact.gateway.common.execution.Executable;
import org.eclipse.sensinact.gateway.common.primitive.Nameable;
import org.eclipse.sensinact.gateway.common.primitive.ProcessableData;
import org.eclipse.sensinact.gateway.core.message.MessageFilter;
import org.eclipse.sensinact.gateway.core.message.SnaConstants;
import org.eclipse.sensinact.gateway.core.method.AccessMethod;
import org.eclipse.sensinact.gateway.core.security.AccessLevelOption;
import org.eclipse.sensinact.gateway.core.security.AccessNode;
import org.eclipse.sensinact.gateway.core.security.AccessNodeImpl;
import org.eclipse.sensinact.gateway.core.security.MethodAccessibility;
import org.eclipse.sensinact.gateway.security.signature.api.BundleValidation;
import org.eclipse.sensinact.gateway.util.ReflectUtils;
import org.eclipse.sensinact.gateway.util.UriUtils;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration;
/**
* A sensiNact Resource Model instance
*
* @author <a href="mailto:cmunilla@cmssi.fr">Christophe Munilla</a>
*/
public class ModelInstance<C extends ModelConfiguration> implements SensiNactResourceModel<C>, LifecycleStatusListener {
/**
* Returns the initial location of the sensiNact gateway and so of the service
* providers for which it is not specified. This method should be called once at
* initialization time
*
* @return the initial string location value ( latitude:longitude)
*/
public static String defaultLocation(Mediator mediator) {
double systemLatitude = 0d;
double systemLongitude = 0d;
try {
systemLatitude = Double.parseDouble(mediator.getContext().getProperty(ServiceProvider.LATITUDE_PROPERTY));
systemLongitude = Double.parseDouble(mediator.getContext().getProperty(ServiceProvider.LONGITUDE_PROPERTY));
} catch (Exception e) {
systemLatitude = ServiceProvider.DEFAULT_CEA_LOCATION_LATITUDE;
systemLongitude = ServiceProvider.DEFAULT_CEA_LOCATION_LONGITUDE;
}
String locationStr = new StringBuilder().append(systemLatitude).append(":").append(systemLongitude).toString();
return locationStr;
}
/**
* the {@link Mediator} allowing to interact with the OSGi host environment
*/
protected Mediator mediator;
/**
* the {@link SensiNactResourceModelConfiguration} of this ModelInstance
*/
protected final C configuration;
/**
* the root {@link ServiceProviderImpl} of this instance of the sensiNact
* resource model
*/
protected ServiceProviderImpl provider;
/**
* <ul>
* <li>true if this SensiNactResourceModel has been registered in the OSGi host
* environment</li>
* <li>false otherwise</li>
* </ul>
*/
protected boolean registered;
/**
* the String unique identifier of this SensiNactResourceModel in the OSGi host
* environment
*/
private final String identifier;
/**
* Remote ID of the sensiNact instance
*/
private String namespace;
/**
* the {@link MesssageHandler} handling messages coming from this
* SensiNactResourceModel
*/
protected MessagePropagator messageHandler;
/**
* the String identifier of the profile of this SnaServiceProvider
*/
protected final String profileId;
/**
* The {@link ServiceRegistration} in the OSGi host environment for this
* {@link SensiNactResourceModel} instance
*/
private ModelInstanceRegistration registration;
/**
* Constructor
*
* @param mediator
* the {@link Mediator} allowing to interact with the OSGi host
* environment
* @param configuration
* the extended {@link ModelConfiguration} gathering the
* configuration properties applying on the ModelInstance to be
* created
*
* @throws InvalidServiceProviderException
*/
public ModelInstance(final Mediator mediator, C modelConfiguration, String name, String profileId)
throws InvalidServiceProviderException {
this.mediator = mediator;
this.profileId = profileId;
this.configuration = modelConfiguration;
List<String> initialSetOfServices = this.configuration.getFixedServices(name);
initialSetOfServices.addAll(this.configuration.getServices(profileId));
this.provider = ReflectUtils.getInstance(ServiceProviderImpl.class,
this.configuration.getProviderImplementationType(),
new Object[] { this, name, initialSetOfServices });
if (this.provider == null) {
throw new InvalidServiceProviderException("Unable to instantiate the root services provider");
}
// retrieve the unique identifier if it exits
this.identifier = this.mediator.callService(BundleValidation.class, new Executable<BundleValidation, String>() {
@Override
public String execute(BundleValidation service) throws Exception {
return service.check(mediator.getContext().getBundle());
}
});
this.namespace = this.mediator.callService(Core.class, new Executable<Core, String>() {
@Override
public String execute(Core core) throws Exception {
return core.namespace();
}
});
}
/**
* @inheritDoc
*
* @see SensiNactResourceModel#configuration()
*/
@Override
public C configuration() {
return this.configuration;
}
/**
* Returns a new {@link ServiceBuilder} instance
*
* @return a new {@link ServiceBuilder} instance
*/
public ServiceBuilder getServiceBuilder() {
ServiceBuilder builder = new ServiceBuilder(this.mediator, ServiceImpl.class);
builder.configureImplementationClass(this.configuration.getServiceImplementationType());
return builder;
}
/**
* Returns a new {@link ResourceBuilder} parameterized by the
* {@link ResourceConfig} passed as parameter
*
* @param resourceConfig
* the {@link ResourceConfig} parameterizing the
* {@link ResourceBuilder} to be created
*
* @return a new {@link ResourceBuilder} instance parameterized by the specified
* {@link ResourceConfig}
*/
public ResourceBuilder getResourceBuilder(ResourceConfig resourceConfig) {
ResourceBuilder builder = new ResourceBuilder(this.mediator, resourceConfig);
return builder;
}
/**
*
* @param descriptor
* @param buildPolicy
*
* @return the appropriate {@link ResourceBuiler} according to the specified
* {@link ResourceDescriptor} and the build policy
*/
protected ResourceBuilder getResourceBuilder(ResourceDescriptor descriptor, byte buildPolicy) {
ResourceBuilder builder = null;
if (SensiNactResourceModelConfiguration.BuildPolicy.isBuildPolicy(buildPolicy,
SensiNactResourceModelConfiguration.BuildPolicy.BUILD_ON_DESCRIPTION)) {
builder = getResourceBuilder(descriptor);
}
if (builder == null && SensiNactResourceModelConfiguration.BuildPolicy.isBuildPolicy(buildPolicy,
SensiNactResourceModelConfiguration.BuildPolicy.BUILD_NON_DESCRIBED)) {
builder = createResourceBuilder(descriptor);
}
return builder;
}
/**
* Returns a {@link ResourceBuilder} wrapping a previously registered
* {@link ResourceConfig} for the {@link ResourceDescriptor} passed as
* parameter. If no appropriate {@link ResourceConfig} can be retrieved, null is
* returned
*
* @param descriptor
* the {@link ResourceDescriptor} describing the
* {@link ResourceConfig} wrapped by the {@link ResourceBuilder} to
* be returned
*
* @return a new {@link ResourceBuilder} instance
*/
public ResourceBuilder getResourceBuilder(ResourceDescriptor descriptor) {
ResourceConfig resourceConfig = this.configuration().getResourceConfig(descriptor);
if (resourceConfig == null) {
return null;
}
return configureResourceBuilder(resourceConfig, descriptor);
}
/**
* Returns a {@link ResourceBuilder} wrapping a newly created
* {@link ResourceConfig} for the {@link ResourceDescriptor} passed as
* parameter. If no appropriate {@link ResourceConfig} can be created, null is
* returned
*
* @param descriptor
* the {@link ResourceDescriptor} describing the
* {@link ResourceConfig} wrapped by the {@link ResourceBuilder} to
* be returned
*
* @return a new {@link ResourceBuilder} instance
*/
public ResourceBuilder createResourceBuilder(ResourceDescriptor descriptor) {
ResourceConfig resourceConfig = this.configuration().createResourceConfig(descriptor);
if (resourceConfig == null) {
return null;
}
return configureResourceBuilder(resourceConfig, descriptor);
}
/**
* Returns a {@link ResourceBuilder} capable of creating a new resource based on
* both the {@link ResourceConfig} and the {@link ResourceDescriptor} passed as
* parameters
*
* @param resourceConfig
* the {@link ResourceConfig} that will be used by the
* {@link ResourceBuilder} to be returned
* @param descriptor
* the {@link ResourceDescriptor} describing the
* {@link ResourceConfig} wrapped by the {@link ResourceBuilder} to
* be returned
*
* @return a new {@link ResourceBuilder} instance
*/
private <G extends ResourceConfig> ResourceBuilder configureResourceBuilder(G resourceConfig,
ResourceDescriptor descriptor) {
ResourceBuilder builder = new ResourceBuilder(this.mediator, resourceConfig);
if (descriptor.resourceName() != null) {
builder.configureName(descriptor.resourceName());
}
if (descriptor.dataType() != null) {
builder.configureType(descriptor.dataType());
}
if (descriptor.dataValue() != null) {
builder.configureValue(descriptor.dataValue());
}
if (descriptor.modifiable() != null) {
builder.configureRequirement(DataResource.VALUE, AttributeBuilder.Requirement.MODIFIABLE,
descriptor.modifiable());
}
if (descriptor.hidden() != null) {
builder.configureRequirement(DataResource.VALUE, AttributeBuilder.Requirement.HIDDEN,
descriptor.hidden().booleanValue());
}
return builder;
}
/**
* Returns the {@link Mediator} of this SensiNactResourceModel allowing to
* interact with the OSGi host environment
*
* @return this SensiNactResourceModel's {@link Mediator}
*/
public Mediator mediator() {
return this.mediator;
}
/**
* Posts the {@link SnaMessage} past as parameter to the {@link MessagePropagator}
* of this SensiNactResourceModel
*
* @param message
* the {@link SnaMessage} to post
*/
public void postMessage(SnaMessage<?> message) {
if (this.messageHandler == null) {
return;
}
((AbstractSnaMessage<?>)message).put("namespace", this.namespace, true);
this.messageHandler.propagate(message);
}
/**
* Registers this sensiNact resource model instance in the OSGi host
* environment.
*
* @throws ModelAlreadyRegisteredException
* if this sensiNact resource model instance is already registered
*/
protected final void register() throws ModelAlreadyRegisteredException {
if (this.registered) {
throw new ModelAlreadyRegisteredException(this.registration.getName());
}
final String name = this.getName();
boolean exists = AccessController.<Boolean>doPrivileged(new PrivilegedAction<Boolean>() {
@Override
public Boolean run() {
Collection<ServiceReference<SensiNactResourceModel>> references = null;
try {
references = ModelInstance.this.mediator.getContext().getServiceReferences(
SensiNactResourceModel.class, new StringBuilder().append("(name="
).append(name).append(")").toString());
} catch (InvalidSyntaxException e) {
ModelInstance.this.mediator.error(e);
}
return (references != null && references.size() > 0);
}
});
if (exists) {
throw new ModelAlreadyRegisteredException(name);
}
final String uri = UriUtils.getUri(new String[] { name });
final Dictionary<String, Object> props = new Hashtable<String, Object>();
props.put("name", name);
String location = null;
try {
location = this.getRootElement().getLocation();
props.put(SnaConstants.LOCATION, location);
} catch (NullPointerException e) {
mediator.debug(String.format("No initial location defined for %s", name));
}
AccessNode node = null;
AccessNode root = this.configuration.getAccessTree().getRoot();
AccessMethod.Type[] accessMethodTypes = AccessMethod.Type.values();
int typesLength = accessMethodTypes == null ? 0 : accessMethodTypes.length;
if ((node = (AccessNodeImpl<?>) root.get(uri)) == null) {
node = root;
}
int index = 0;
for (; index < typesLength; index++) {
AccessLevelOption accessLevelOption = node.getAccessLevelOption(accessMethodTypes[index]);
props.put(new StringBuilder().append(name).append(".").append(accessMethodTypes[index].name()).toString(),
accessLevelOption.getAccessLevel().getLevel());
}
ServiceRegistration<SensiNactResourceModel> instanceRegistration =
AccessController.<ServiceRegistration<SensiNactResourceModel>>doPrivileged(
new PrivilegedAction<ServiceRegistration<SensiNactResourceModel>>() {
@Override
public ServiceRegistration<SensiNactResourceModel> run() {
return ModelInstance.this.mediator.getContext(
).registerService(SensiNactResourceModel.class, ModelInstance.this, props);
}
});
if (instanceRegistration != null) {
this.registered = true;
List<String> observed = this.configuration.getObserved();
this.registration = new ModelInstanceRegistration(uri, observed, instanceRegistration, this.configuration);
this.messageHandler = new ModelInstanceMessagePropagator(mediator, this.configuration());
boolean pattern = false;
StringBuilder observedBuilder = new StringBuilder().append(uri);
if (observed != null && !observed.isEmpty()) {
observedBuilder.append("(/admin/location/value");
Iterator<String> it = observed.iterator();
while (it.hasNext()) {
String obs = null;
String[] uriElements = UriUtils.getUriElements(it.next());
switch (uriElements.length) {
case 0:
case 1:
continue;
case 2:
obs = UriUtils.getUri(uriElements).concat("/value");
break;
case 3:
obs = UriUtils.getUri(uriElements);
break;
default:
continue;
}
observedBuilder.append("|");
observedBuilder.append(obs);
}
observedBuilder.append(")");
pattern = true;
} else {
observedBuilder.append("/admin/location/value");
}
MessageFilter filter = new MessageFilter(mediator, observedBuilder.toString(), pattern, false);
filter.addHandledType(SnaMessage.Type.UPDATE);
this.messageHandler.addCallback(filter, registration);
filter = new MessageFilter(mediator, "(\\/[^\\/]+)+", true, false);
filter.addHandledType(SnaMessage.Type.LIFECYCLE);
this.messageHandler.addCallback(filter, registration);
if (this.configuration().getStartAtInitializationTime()) {
this.provider.start();
}
}
}
/**
* Unregisters this sensiNact resource model instance from the OSGi host
* environment
*
* @throws IllegalStateException
* if this sensiNact resource model instance is not registered
*/
public final void unregister() throws IllegalStateException {
if (!this.isRegistered()) {
throw new IllegalStateException(this.registration.getName());
}
this.registered = false;
try {
this.getRootElement().stop();
} catch (Exception e) {
mediator.error(e);
}
this.messageHandler.close(true);
this.messageHandler = null;
AccessController.<Void>doPrivileged(new PrivilegedAction<Void>() {
@Override
public Void run() {
ModelInstance.this.registration.unregister();
return null;
}
});
}
/**
* Returns true if this SensiNactResourceModel has been registered; returns
* false otherwise
*
* @return
* <ul>
* <li>true if this SensiNactResourceModel has already been
* registered</li>
* <li>false otherwise</li>
* </ul>
*/
public boolean isRegistered() {
return this.registered;
}
/**
* Returns the root {@link ServiceProviderImpl} of this instance of the
* sensiNact resource model
*
* @return this resource model instance's root {@link ServiceProviderImpl}
*
* @see SensiNactResourceModel#getRootElement()
*/
@Override
public ServiceProviderImpl getRootElement() {
return this.provider;
}
/**
* @inheritedDoc
*
* @see Nameable#getName()
*/
@Override
public String getName() {
return this.getRootElement().getName();
}
/**
* @inheritedDoc
*
* @see SensiNactResourceModel#getIdentifier()
*/
@Override
public String getIdentifier() {
return this.identifier;
}
/**
* @param filter
* @param callback
*/
public void registerCallback(MessageFilter filter, MessageCallback callback) {
if (this.messageHandler == null) {
return;
}
this.messageHandler.addCallback(filter, callback);
}
/**
*
* @param callback
*/
public void unregisterCallback(String callback) {
if (this.messageHandler == null) {
return;
}
this.messageHandler.deleteCallback(callback);
}
/**
* Returns the string identifier of the profile to which this model instance
* belongs to
*
* @return this model instance profile identifier
*/
public String getProfile() {
return this.profileId;
}
/**
* @inheritDoc
*
* @see LifecycleStatusListener# update(ServiceProvider.LifecycleStatus)
*/
@Override
public void update(LifecycleStatus status) {
this.registration.updateLifecycle(status);
}
/**
* @inheritDoc
*
* @see SensiNactResourceModel#getProperties()
*/
@Override
public Dictionary<String, String> getProperties() {
Dictionary<String, String> props = new Hashtable<String, String>();
props.put("uri", UriUtils.getUri(new String[] { this.getName() }));
props.put("lifecycle.status", this.getRootElement().getStatus().name());
return props;
}
/**
* Returns the set of the specified {@link ModelElement} accessible
* {@link AccessMethod.Type}s for the {@link AccessLevelOption} passed as
* parameter and
*
* @param modelElement
* the {@link ModelElement} for which to retrieve the set of
* accessible {@link AccessMethod.Type}s
* @param accessLevelOption
* the requirer {@link AccessLevelOption}
*
* @return the set of accessible {@link AccessMethod.Type} of the specified
* {@link ModelElement} for the specified {@link AccessLevelOption}
*/
public <I extends ModelInstance<?>, M extends ModelElementProxy, P extends ProcessableData, E extends Nameable, R extends Nameable> List<MethodAccessibility> getAuthorizations(
ModelElement<I, M, P, E, R> modelElement, AccessLevelOption accessLevelOption) {
if (modelElement.getModelInstance() != this) {
throw new RuntimeException("the model element argument must belong to this model instance");
}
final String path = modelElement.getPath();
return this.configuration().getAccessibleMethods(path, accessLevelOption);
}
}