blob: 1a6bb0a52af126008cae4ab90cefc92631042f9f [file] [log] [blame]
/*
* 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.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.eclipse.sensinact.gateway.common.bundle.Mediator;
import org.eclipse.sensinact.gateway.common.constraint.Constraint;
import org.eclipse.sensinact.gateway.common.primitive.DescribablePrimitive;
import org.eclipse.sensinact.gateway.common.primitive.InvalidValueException;
import org.eclipse.sensinact.gateway.common.primitive.Modifiable;
import org.eclipse.sensinact.gateway.common.primitive.Name;
import org.eclipse.sensinact.gateway.common.primitive.PrimitiveDescription;
import org.eclipse.sensinact.gateway.util.CastUtils;
import org.json.JSONArray;
import org.json.JSONObject;
/**
* Extended {@link Primitive} defining an Attribute of a {@link Resource}
*
* @author <a href="mailto:christophe.munilla@cea.fr">Christophe Munilla</a>
*/
public class Attribute extends DescribablePrimitive {
public static final String NICKNAME = "nickname";
/**
* This Attribute's metadata.
*/
protected final List<Metadata> metadata;
private final ResourceImpl resource;
private int recipient;
/**
* Constructor
*
* @param mediator
* the {@link Mediator} allowing the Attribute to be instantiated to
* interact with the OSGi host environment
* @param resource
* the ResourceImpl holding the Attribute to be instantiated
* @param name
* the name of the Attribute to instantiate
* @param type
* the type of the Attribute to instantiate
*
* @throws InvalidValueException
*/
public Attribute(Mediator mediator, ResourceImpl resource, String name, Class<?> type)
throws InvalidValueException {
this(mediator, resource, name, type, null, Modifiable.UPDATABLE, false);
this.recipient = 0;
}
/**
* Constructor
*
* @param mediator
* the {@link Mediator} allowing the Attribute to be instantiated to
* interact with the OSGi host environment
* @param resource
* the ResourceImpl holding the Attribute to be instantiated
* @param name
* the name of the Attribute to instantiate
* @param type
* the type of the Attribute to instantiate
* @param value
* the initial value of the Attribute to instantiate
* @param modifiable
* is the value of Attribute to instantiate modifiable or not ?
* @param hidden
* does the Attribute to instantiate provide its own JSON formated
* description or not
*
* @throws InvalidValueException
*/
public Attribute(Mediator mediator, ResourceImpl resource, String name, Class<?> type, Object value,
Modifiable modifiable, boolean hidden) throws InvalidValueException {
super(mediator, name, type);
this.resource = resource;
this.metadata = new ArrayList<Metadata>();
if (value != null) {
super.setValue(value);
}
Metadata modifiableMeta = new Metadata(super.mediator, Metadata.MODIFIABLE, Modifiable.class, modifiable,
Modifiable.FIXED);
this.addMetadata(modifiableMeta);
Metadata hiddenMeta = new Metadata(super.mediator, Metadata.HIDDEN, boolean.class, hidden, Modifiable.FIXED);
this.addMetadata(hiddenMeta);
Metadata timestampMeta = new Metadata(super.mediator, Metadata.TIMESTAMP, long.class,
System.currentTimeMillis(), Modifiable.UPDATABLE);
this.addMetadata(timestampMeta);
Metadata lockedMeta = new Metadata(super.mediator, Metadata.LOCKED, boolean.class, false, Modifiable.UPDATABLE);
this.addMetadata(lockedMeta);
}
/**
* Constructs an attribute from the given {@link JSONObject} for the
* ResourceImpl passed as parameter
*
* @param mediator the {@link Mediator} allowing the Attribute to be instantiated to
* interact with the OSGi host environment
* @param attribute the {@link JSONObject} describing the attribute to instantiate
*/
protected Attribute(Mediator mediator, ResourceImpl resource, JSONObject attribute) throws InvalidValueException {
super(mediator, attribute == null ? null : attribute.optString(PrimitiveDescription.NAME_KEY),
attribute == null ? null : attribute.optString(PrimitiveDescription.TYPE_KEY));
this.resource = resource;
this.metadata = new ArrayList<Metadata>();
// set the value if defined in the JSONObject
Object ovalue = attribute.opt(PrimitiveDescription.VALUE_KEY);
if (ovalue != null) {
this.setValue(CastUtils.getObjectFromJSON(super.mediator.getClassLoader(), this.getType(), ovalue));
}
JSONArray metadataArray = attribute.optJSONArray("metadata");
// adds Metadata specified in the JSON object if not null
if (metadataArray != null) {
int index = 0;
for (; index < metadataArray.length(); index++) {
Metadata metadata = new Metadata(super.mediator, metadataArray.getJSONObject(index));
this.addMetadata(metadata);
}
}
// modifiable Metadata added only if it does not already exist
// it is not overridden if defined as modifiable
if (!this.metadata.contains(new Name<Metadata>(Metadata.MODIFIABLE))) {
Metadata modifiableMeta = new Metadata(super.mediator, Metadata.MODIFIABLE, Modifiable.class,
Modifiable.MODIFIABLE, Modifiable.FIXED);
this.addMetadata(modifiableMeta);
}
// hidden Metadata added only if it does not already exist
// it is not overridden if defined as modifiable
if (!this.metadata.contains(new Name<Metadata>(Metadata.HIDDEN))) {
Metadata hiddenMeta = new Metadata(super.mediator, Metadata.HIDDEN, boolean.class, false, Modifiable.FIXED);
this.addMetadata(hiddenMeta);
}
if (!this.metadata.contains(new Name<Metadata>(Metadata.TIMESTAMP))) {
Metadata timestampMeta = new Metadata(super.mediator, Metadata.TIMESTAMP, long.class,
System.currentTimeMillis(), Modifiable.UPDATABLE);
this.addMetadata(timestampMeta);
}
if (!this.metadata.contains(new Name<Metadata>(Metadata.LOCKED))) {
Metadata lockedMeta = new Metadata(super.mediator, Metadata.LOCKED, boolean.class, false,
Modifiable.UPDATABLE);
this.addMetadata(lockedMeta);
}
}
/**
* Defines the value of the {@link Metadata} whose whose name is passed as
* parameter
*
* @param name the name of the {@link Metadata} to set the value of
* @param value the value to set
* @return the extended {@link Description} describing the modified Metadata
* {@link Metadata}
*
* @throws InvalidValueException
*/
public MetadataDescription setMetadataValue(String name, Object value) throws InvalidValueException {
Metadata metadata = null;
if (name == null || (metadata = this.get(name)) == null)
return null;
metadata.setValue(value);
MetadataDescription description = (MetadataDescription) metadata.getDescription();
return description;
}
/**
* Adds the {@link Metadata} passed as parameter to the list of ones owned by
* this Attribute if it does not already exist or replaces the existing one with
* the same name if it is defined as modifiable
*
* @param metadata the {@link Metadata} to add or update
*/
public void addMetadata(Metadata metadata) {
Metadata meta = this.get(metadata.getName());
synchronized (this.metadata) {
if (meta == null) {
this.metadata.add(metadata);
super.weakDescription = null;
if(!metadata.getName().equals(Metadata.HIDDEN) && metadata.getModifiable().equals(Modifiable.FIXED))
this.resource.updated(this, metadata.getDescription());
} else {
try {
this.setMetadataValue(metadata.getName(), metadata.getValue());
} catch (InvalidValueException e) {
if (super.mediator.isErrorLoggable())
super.mediator.error(e);
}
return;
}
}
}
/**
* Removes the {@link Metadata} whose name is passed as parameter from the list
* of ones owned by this Attribute if it exists
*
* @param metadata
* the name of the {@link Metadata} to remove
* @return
* <ul>
* <li>true if the {@link Metadata} has been removed properly</li>
* <li>false if an error occurred or if the Metadata does not exist</li>
* </ul>
*/
public boolean removeMetadata(String metadata) {
if (metadata == null || Metadata.HIDDEN.intern() == metadata.intern()
|| Metadata.MODIFIABLE.intern() == metadata.intern() || Metadata.TIMESTAMP.intern() == metadata.intern()
|| Metadata.LOCKED.intern() == metadata.intern()) {
return false;
}
Metadata existingMeta = this.remove(metadata);
return (existingMeta != null);
}
/**
* Sets this Primitive's value and defines the timestamp of the value change
*
* @param value
* the value to set
* @param timestamp
* the timestamp of the value change
*
* @throws InvalidValueException
* if the value cannot be set
*/
@Override
public Object setValue(Object value) throws InvalidValueException {
return this.setValue(value, System.currentTimeMillis());
}
/**
* Sets this Primitive's value and defines the timestamp of the value change
*
* @param value
* the value to set
* @param timestamp
* the timestamp of the value change
*
* @throws InvalidValueException
* if the value cannot be set
*/
public Object setValue(Object value, long timestamp) throws InvalidValueException {
if (Modifiable.FIXED.equals(this.getModifiable())) {
throw new InvalidValueException("the value cannot be modified");
}
Object valueObject = cast(value);
Object copy = CastUtils.copy(super.type, valueObject);
synchronized (lock) {
boolean hasChanged = ((valueObject == null && super.value != null)
|| (valueObject != null && !valueObject.equals(super.value)));
this.beforeChange(copy);
this.setMetadataValue(Metadata.TIMESTAMP, timestamp);
super.value = valueObject;
this.afterChange(copy, hasChanged);
}
return copy;
}
/**
* @inheritDoc
*
* @see Primitive#beforeChange(java.lang.Object)
*/
@Override
protected void beforeChange(Object value) throws InvalidValueException {
Metadata metadata = this.get(Metadata.CONSTRAINTS);
if (metadata == null) {
return;
}
Constraint[] constraints = null;
int length = 0;
try {
constraints = (Constraint[]) metadata.getValue();
} finally {
if (constraints == null || (length = constraints.length) == 0) {
return;
}
}
int index = 0;
for (; index < length; index++) {
if (!constraints[index].complies(value)) {
StringBuilder buffer = new StringBuilder();
buffer.append("value does not comply the constraint :");
buffer.append(constraints[index].getJSON());
String message = buffer.toString();
throw new InvalidValueException(message);
}
}
}
/**
* @inheritDoc
*
* @see DescribablePrimitive# afterChange(java.lang.Object)
*
* @throws InvalidValueException
*/
private void afterChange(Object value, boolean hasChanged) throws InvalidValueException {
super.afterChange(value);
this.resource.updated(this, value, hasChanged);
}
/**
* Defines the {@link ResourceImpl} to notify when the value of this Attribute
* has changed
*
* @param recipient
* the change notifications recipient
*/
protected void addRecipient() {
this.recipient++;
}
/**
* Defines the change notifications recipient as null
*/
protected void deleteRecipient() {
this.recipient--;
}
/**
* @inheritDoc
*
* @see Primitive # createDescription(java.lang.String,java.lang.Class)
*/
@Override
protected AttributeDescription createDescription() {
AttributeDescription description = new AttributeDescription(this, this.getAllDescriptions());
return description;
}
/**
* @inheritDoc
*
* @see Primitive #isModifiable()
*/
@Override
public Modifiable getModifiable() {
Modifiable modifiable = Modifiable.UPDATABLE;
Metadata metadata = null;
if ((metadata = this.get(Metadata.MODIFIABLE)) == null) {
return modifiable;
}
try {
modifiable = (Modifiable) metadata.getValue();
} catch (ClassCastException e) {
if (this.mediator.isErrorLoggable()) {
this.mediator.error(e, e.getMessage());
}
}
return modifiable;
}
/**
* Returns true if this Attribute's value is locked; returns false otherwise
*
* @return
* <ul>
* <li>true is this Attribute's value is locked</li>
* <li>false otherwise</li>
* </ul>
*/
public boolean getLocked() {
boolean locked = false;
try {
locked = (Boolean) this.get(Metadata.LOCKED).getValue();
} catch (Exception e) {
this.mediator.error(e);
}
return locked;
}
/**
* Defines this Attribute's value as locked
*/
protected void lock() {
try {
this.get(Metadata.LOCKED).setValue(true);
} catch (Exception e) {
this.mediator.error(e);
}
}
/**
* Defines this Attribute value as unlocked
*/
protected void unlock() {
try {
this.get(Metadata.LOCKED).setValue(false);
} catch (Exception e) {
this.mediator.error(e);
}
}
/**
* Returns true if this Attribute JSON description is hidden; returns false
* otherwise
*
* @return
* <ul>
* <li>true if this Attribute JSON description is hidden;</li>
* <li>false otherwise</li>
* </ul>
*/
public boolean isHidden() {
Metadata metadata = this.get(Metadata.HIDDEN);
boolean hidden = false;
if (metadata != null) {
try {
hidden = ((Boolean) metadata.getValue()).booleanValue();
} catch (ClassCastException e) {
this.mediator.error(e, e.getMessage());
}
}
return hidden;
}
/**
* Returns the {@link Metadata} of this Attribute, whose name is the same as the
* specified one
*
* @param name
* the name of the searched {@link Metadata}
* @return the {@link Metadata} with the specified name
*/
protected Metadata get(String name) {
int index = -1;
Metadata metadata = null;
synchronized (this.metadata) {
if ((index = this.metadata.indexOf(new Name<Attribute>(name))) != -1) {
metadata = this.metadata.get(index);
}
}
return metadata;
}
/**
* Removes the {@link Metadata} whose name is passed as parameter
*
* @param primitive
* the name of the {@link Metadata} to remove
* @return the removed {@link Metadata}
*/
private Metadata remove(String name) {
Metadata metadata = this.get(name);
if (metadata == null) {
synchronized (this.metadata) {
this.metadata.remove(metadata);
super.weakDescription = null;
}
}
return metadata;
}
/**
* Returns the array of {@link MetadataDescription}s of all {@link Metadata} of
* this Attribute
*
* @return the {@link MetadataDescription}s array of this Attribute
*/
public MetadataDescription[] getAllDescriptions() {
int index = 0;
MetadataDescription[] descriptions = new MetadataDescription[this.metadata.size()];
synchronized (this.metadata) {
Iterator<Metadata> iterator = this.metadata.iterator();
while (iterator.hasNext()) {
descriptions[index++] = iterator.next().getDescription();
}
}
return descriptions;
}
}