Skip to content

Commit

Permalink
Allow default resource metadata to be set in the model
Browse files Browse the repository at this point in the history
Provider instances have the ability to independently set metadata values, however in most cases metadata is common to all instances of a model and it is relatively static. It therefore makes sense to be able to define default metadata at the resource model level which is applied to the instance when it is first created.

Note that only the Extra metadata is used to populate the defaults. This prevents other data about the resource (e.g. the resource type and value type) from being added and bloating the metadata. To differentiate default metadata values from metadata values which are set later the default metadata values will have no associated timestamp. This is in line with the behaviour for default resource values.

Signed-off-by: Tim Ward <[email protected]>
  • Loading branch information
timothyjward committed Jul 24, 2024
1 parent 6fad5f8 commit fe402d7
Show file tree
Hide file tree
Showing 10 changed files with 258 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,11 @@ public interface Resource extends Modelled, CommandScoped {
List<Map.Entry<String, Class<?>>> getArguments();

Service getService();

/**
* The Map of default metadata which is set for all resource instances
* @return
*/
Map<String, Object> getDefaultMetadata();

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import java.time.Duration;
import java.time.Instant;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.Consumer;
import java.util.function.Function;
Expand Down Expand Up @@ -54,6 +55,13 @@ public interface ResourceBuilder<B, T> {
*/
<U extends T> ResourceBuilder<B, U> withInitialValue(U initialValue, Instant timestamp);

/**
* Default metadata that should be provided by the model for this resource
* @param defaultMetadata
* @return
*/
ResourceBuilder<B, T> withDefaultMetadata(Map<String, Object> defaultMetadata);

/**
* The type of the value - must be consistent with the built model, e.g. if
* {@link #withSetter(Consumer)} is called then the valueType must be
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.eclipse.sensinact.core.snapshot.ProviderSnapshot;
import org.eclipse.sensinact.core.snapshot.ResourceSnapshot;
import org.eclipse.sensinact.core.twin.TimedValue;
import org.eclipse.sensinact.model.core.metadata.MetadataFactory;
import org.eclipse.sensinact.model.core.provider.Metadata;
import org.eclipse.sensinact.model.core.provider.Service;
import org.eclipse.sensinact.core.model.impl.ResourceImpl;
Expand Down Expand Up @@ -77,15 +78,17 @@ public ResourceSnapshotImpl(final ServiceSnapshotImpl parent, final ETypedElemen
this.valueType = ValueType.UPDATABLE;

Service modelService = parent.getModelService();
final Metadata rcMetadata = modelService == null ? null : modelService.getMetadata().get(rcFeature);
Metadata rcMetadata = modelService == null ? null : modelService.getMetadata().get(rcFeature);
if (rcMetadata == null) {
this.metadata = Map.of();
} else {
final Map<String, Object> rcMeta = new HashMap<>();
rcMeta.putAll(EMFUtil.toMetadataAttributesToMap(rcMetadata, rcFeature));
this.metadata = rcMeta;
rcMetadata = MetadataFactory.eINSTANCE.createResourceMetadata();
if(rcFeature instanceof Metadata) {
rcMetadata.getExtra().addAll(((Metadata)rcFeature).getExtra());
}
}
}
final Map<String, Object> rcMeta = new HashMap<>();
rcMeta.putAll(EMFUtil.toMetadataAttributesToMap(rcMetadata, rcFeature));
this.metadata = rcMeta;
}

@Override
public String toString() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import java.time.Duration;
import java.time.Instant;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicBoolean;

Expand All @@ -33,6 +34,7 @@ public class ResourceBuilderImpl<R, T> extends NestableBuilderImpl<R, ServiceImp
private Class<?> type;
private Object initialValue;
private Instant timestamp;
private Map<String, Object> defaultMetadata;
private ResourceType resourceType = null;
private List<Entry<String, Class<?>>> namedParameterTypes;
private boolean hasGetter;
Expand Down Expand Up @@ -83,6 +85,13 @@ public <U extends T> ResourceBuilder<R, U> withInitialValue(U initialValue, Inst
return (ResourceBuilder<R, U>) this;
}

@Override
public ResourceBuilder<R, T> withDefaultMetadata(Map<String, Object> defaultMetadata) {
checkValid();
this.defaultMetadata = Map.copyOf(defaultMetadata);
return this;
}

@Override
public ResourceBuilder<R, T> withValueType(ValueType valueType) {
checkValid();
Expand Down Expand Up @@ -171,11 +180,11 @@ protected Resource doBuild(ServiceImpl builtParent) {
switch (resourceType) {
case ACTION:
createResource = nexusImpl.createActionResource(builtParent.getServiceEClass(), name, type,
namedParameterTypes);
namedParameterTypes, defaultMetadata);
break;
case SENSOR:
createResource = nexusImpl.createResource(builtParent.getServiceEClass(), name, type, timestamp,
initialValue, hasGetter, getterCacheMs, hasSetter);
initialValue, defaultMetadata, hasGetter, getterCacheMs, hasSetter);
break;
case PROPERTY:
case STATE_VARIABLE:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@
import org.eclipse.sensinact.core.model.ResourceType;
import org.eclipse.sensinact.core.model.Service;
import org.eclipse.sensinact.core.model.ValueType;
import org.eclipse.sensinact.core.model.nexus.emf.EMFUtil;
import org.eclipse.sensinact.model.core.metadata.NexusMetadata;
import org.eclipse.sensinact.model.core.metadata.ResourceAttribute;
import org.eclipse.sensinact.model.core.provider.Metadata;

public class ResourceImpl extends CommandScopedImpl implements Resource {

Expand Down Expand Up @@ -121,4 +124,12 @@ public static ValueType findValueType(ETypedElement feature) {
throw new UnsupportedOperationException("Handling of none Sensinact Atributes not implemented yet");
}

@Override
public Map<String, Object> getDefaultMetadata() {
if(feature instanceof Metadata) {
return EMFUtil.toMetadataAttributesToMap((Metadata) feature, feature);
}
throw new UnsupportedOperationException("Handling of none Sensinact Atributes not implemented yet");
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
import org.eclipse.sensinact.core.twin.TimedValue;
import org.eclipse.sensinact.core.twin.impl.TimedValueImpl;
import org.eclipse.sensinact.core.whiteboard.impl.SensinactWhiteboard;
import org.eclipse.sensinact.model.core.metadata.Action;
import org.eclipse.sensinact.model.core.metadata.ActionParameter;
import org.eclipse.sensinact.model.core.metadata.AnnotationMetadata;
import org.eclipse.sensinact.model.core.metadata.MetadataFactory;
Expand All @@ -71,6 +72,7 @@
import org.eclipse.sensinact.model.core.provider.Admin;
import org.eclipse.sensinact.model.core.provider.DynamicProvider;
import org.eclipse.sensinact.model.core.provider.FeatureCustomMetadata;
import org.eclipse.sensinact.model.core.provider.Metadata;
import org.eclipse.sensinact.model.core.provider.Provider;
import org.eclipse.sensinact.model.core.provider.ProviderFactory;
import org.eclipse.sensinact.model.core.provider.ProviderPackage;
Expand Down Expand Up @@ -415,6 +417,9 @@ private void handleDataUpdate(Provider provider, String serviceName, Service ser

if (metadata == null) {
metadata = MetadataFactory.eINSTANCE.createResourceMetadata();
if(resourceFeature instanceof Metadata) {
metadata.getExtra().addAll(((Metadata)resourceFeature).getExtra());
}
service.getMetadata().put(resourceFeature, metadata);
}
metadata.setTimestamp(metaTimestamp);
Expand Down Expand Up @@ -480,6 +485,7 @@ private void createAdminServiceForProvider(Provider original, Instant timestamp)
|| resourceFeature == ProviderPackage.Literals.ADMIN__MODEL_PACKAGE_URI
|| resourceFeature == ProviderPackage.Literals.ADMIN__MODEL) {
ResourceMetadata metadata = MetadataFactory.eINSTANCE.createResourceMetadata();
// N.B. We currently don't copy default metadata for the admin resources
metadata.setOriginalName(resourceFeature.getName());
metadata.setTimestamp(timestamp);
adminSvc.getMetadata().put(resourceFeature, metadata);
Expand Down Expand Up @@ -571,35 +577,46 @@ private List<Provider> getProviders(EClass model) {

public EAttribute createResource(EClass service, String resource, Class<?> type, Instant timestamp,
Object defaultValue) {
return createResource(service, resource, type, timestamp, defaultValue, false, 0, false);
return createResource(service, resource, type, timestamp, defaultValue, Map.of(), false, 0, false);
}

public EAttribute createResource(EClass service, String resource, Class<?> type, Instant timestamp,
Object defaultValue, boolean hasGetter, long getterCacheMs, boolean hasSetter) {
Object defaultValue, Map<String, Object> defaultMetadata, boolean hasGetter,
long getterCacheMs, boolean hasSetter) {
// FIXME: WIP
FeatureCustomMetadata resourceType = ProviderFactory.eINSTANCE.createFeatureCustomMetadata();
resourceType.setName("resourceType");
resourceType.setValue(ResourceType.SENSOR);
resourceType.setTimestamp(Instant.EPOCH);

return doCreateResource(service, resource, type, timestamp, defaultValue, List.of(resourceType), hasGetter,
getterCacheMs, hasSetter);
return doCreateResource(service, resource, type, timestamp, defaultValue, defaultMetadata,
List.of(resourceType), hasGetter, getterCacheMs, hasSetter);
}

private EAttribute doCreateResource(EClass service, String resource, Class<?> type, Instant timestamp,
Object defaultValue, List<FeatureCustomMetadata> metadata, boolean hasGetter, long getterCacheMs,
boolean hasSetter) {
Object defaultValue, Map<String, Object> defaultMetadata, List<FeatureCustomMetadata> metadata,
boolean hasGetter, long getterCacheMs, boolean hasSetter) {
assertResourceNotExist(service, resource);
ResourceAttribute feature = EMFUtil.createResourceAttribute(service, resource, type, defaultValue);
feature.setExternalGet(hasGetter);
feature.setExternalSet(hasSetter);
if (getterCacheMs > 0) {
feature.setExternalGetCacheMs(getterCacheMs);
}
EMFUtil.fillMetadata(feature, timestamp, false, resource, List.of());
List<FeatureCustomMetadata> defaultFeatureMetadata = defaultMetadata == null ?
List.of() : toDefaultFeatureCustomMetadata(defaultMetadata);
EMFUtil.fillMetadata(feature, timestamp, false, resource, defaultFeatureMetadata);
return feature;
}

private List<FeatureCustomMetadata> toDefaultFeatureCustomMetadata(Map<String, Object> defaultMetadata) {
List<FeatureCustomMetadata> defaultFeatureMetadata = defaultMetadata.entrySet().stream()
.map(e -> handleFeatureCustomMetadata(
ProviderFactory.eINSTANCE.createFeatureCustomMetadata(), e.getKey(), null, e.getValue()))
.collect(Collectors.toList());
return defaultFeatureMetadata;
}

private void assertResourceNotExist(EClass service, String resource) {
ETypedElement element = service.getEOperations().stream().filter(o -> o.getName().equals(resource))
.map(ETypedElement.class::cast).findFirst().orElseGet(() -> service.getEStructuralFeature(resource));
Expand Down Expand Up @@ -655,23 +672,32 @@ private List<EAnnotation> createEClassAnnotations(String model, Instant timestam
EMFUtil.createEAnnotation("model", Map.of("name", model)));
}

public Map<String, Object> getResourceMetadata(Service svc, final ETypedElement rcFeature) {
private Map<String, Object> getResourceMetadata(Provider provider, Service svc,
String serviceName, final ETypedElement rcFeature) {
if (svc == null) {
return Map.of();
svc = createServiceInstance(provider, serviceName, null, null);
}

final ResourceMetadata metadata = (ResourceMetadata) svc.getMetadata().get(rcFeature);
ResourceMetadata metadata = getOrInitializeResourceMetadata(svc, rcFeature);
return toMetadataMap(rcFeature, metadata);
}

private ResourceMetadata getOrInitializeResourceMetadata(Service svc, final ETypedElement rcFeature) {
ResourceMetadata metadata = (ResourceMetadata) svc.getMetadata().get(rcFeature);
if (metadata == null) {
return Map.of();
} else {
return toMetadataMap(rcFeature, metadata);
metadata = MetadataFactory.eINSTANCE.createResourceMetadata();
if(rcFeature instanceof Metadata) {
metadata.getExtra().addAll(((Metadata)rcFeature).getExtra());
}
svc.getMetadata().put(rcFeature, metadata);
}
return metadata;
}

public Map<String, Object> getResourceMetadata(Provider provider, String serviceName,
final ETypedElement rcFeature) {
Service svc = provider.getService(serviceName);
return getResourceMetadata(svc, rcFeature);
return getResourceMetadata(provider, svc, serviceName, rcFeature);
}

private Map<String, Object> toMetadataMap(final ETypedElement rcFeature, final ResourceMetadata metadata) {
Expand All @@ -687,7 +713,7 @@ public TimedValue<Object> getResourceMetadataValue(Provider provider, String ser
return null;
}

final ResourceMetadata metadata = (ResourceMetadata) svc.getMetadata().get(rcFeature);
final ResourceMetadata metadata = getOrInitializeResourceMetadata(svc, rcFeature);
if (metadata != null) {
for (FeatureCustomMetadata entry : metadata.getExtra()) {
if (entry.getName().equals(key)) {
Expand Down Expand Up @@ -734,6 +760,9 @@ private void setResourceMetadata(Provider provider, String serviceName, Service
ResourceMetadata metadata = (ResourceMetadata) svc.getMetadata().get(resource);
if (metadata == null) {
metadata = MetadataFactory.eINSTANCE.createResourceMetadata();
if(resource instanceof Metadata) {
metadata.getExtra().addAll(((Metadata)resource).getExtra());
}
svc.getMetadata().put(resource, metadata);
}

Expand Down Expand Up @@ -883,14 +912,20 @@ public Stream<ETypedElement> getResourcesForService(EClass svcClass) {
}

public EOperation createActionResource(EClass serviceEClass, String name, Class<?> type,
List<Entry<String, Class<?>>> namedParameterTypes) {
List<Entry<String, Class<?>>> namedParameterTypes, Map<String, Object> defaultMetadata) {

assertResourceNotExist(serviceEClass, name);

List<ActionParameter> params = namedParameterTypes.stream().map(EMFUtil::createActionParameter)
.collect(Collectors.toList());

return EMFUtil.createAction(serviceEClass, name, type, params);
List<FeatureCustomMetadata> defaultFeatureMetadata = defaultMetadata == null ?
List.of() : toDefaultFeatureCustomMetadata(defaultMetadata);

Action action = EMFUtil.createAction(serviceEClass, name, type, params);
EMFUtil.fillMetadata(action, null, false, name, defaultFeatureMetadata);

return action;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

import java.util.AbstractMap.SimpleEntry;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.eclipse.emf.ecore.resource.ResourceSet;
Expand Down Expand Up @@ -166,5 +167,45 @@ void testDeleteModel() {

assertThrows(IllegalStateException.class, () -> model.getPackageUri());
}

@Test
void resourceWithDefaultMetadata() {
Model model = manager.createModel(TEST_MODEL).withService(TEST_SERVICE).withResource(TEST_RESOURCE)
.withType(Integer.class).withDefaultMetadata(Map.of("foo", "bar", "foobar", 42)).build().build().build();

Resource resource = model.getServices().get(TEST_SERVICE).getResources().get(TEST_RESOURCE);

assertEquals(TEST_RESOURCE, resource.getName());
assertEquals(Integer.class, resource.getType());
assertEquals(ResourceType.SENSOR, resource.getResourceType());

Map<String, Object> metadata = resource.getDefaultMetadata();

assertNotNull(metadata);
assertEquals("bar", metadata.get("foo"));
assertEquals(42, metadata.get("foobar"));
}

@Test
void actionWithDefaultMetadata() {
List<Entry<String, Class<?>>> parameters = List.of(new SimpleEntry<>("foo", Double.class),
new SimpleEntry<>("bar", Long.class));
Model model = manager.createModel(TEST_MODEL).withService(TEST_SERVICE).withResource(TEST_RESOURCE)
.withType(Integer.class).withAction(parameters)
.withDefaultMetadata(Map.of("foo", "bar", "foobar", 42)).build().build().build();

Resource resource = model.getServices().get(TEST_SERVICE).getResources().get(TEST_RESOURCE);

assertEquals(TEST_RESOURCE, resource.getName());
assertEquals(Integer.class, resource.getType());
assertEquals(ResourceType.ACTION, resource.getResourceType());
assertEquals(parameters, resource.getArguments());

Map<String, Object> metadata = resource.getDefaultMetadata();

assertNotNull(metadata);
assertEquals("bar", metadata.get("foo"));
assertEquals(42, metadata.get("foobar"));
}
}
}
Loading

0 comments on commit fe402d7

Please sign in to comment.