The exisiting set of Metadata can be extended with own types.

The are two types of metadata:

Important:

Creating a new base metadata type

In our example we will use the trust metadata (de.uniol.inf.is.odysseus.trust). This is just a double value.

To create new base metadata type you first need to create an interface to set and retrieve the values that should be contained in this metadata. This interface must extend IMetaAttribute.

public interface ITrust extends IMetaAttribute {
	
	void setTrust(double trustValue);
	double getTrust();

}

This interface must be implemented by the content holding class:

public final class Trust extends AbstractBaseMetaAttribute implements ITrust {

	private static final String TRUSTVALUE = "trustvalue";
	private static final String NAME = "Trust";

	@SuppressWarnings("unchecked")
	public final static Class<? extends IMetaAttribute>[] classes = new Class[] { ITrust.class };

	public static final List<SDFMetaSchema> schema = new ArrayList<>(classes.length);

	static {
		List<SDFAttribute> attributes = new ArrayList<>();
		attributes.add(new SDFAttribute(NAME, TRUSTVALUE, SDFDatatype.DOUBLE));
		schema.add(SDFSchemaFactory.createNewMetaSchema(NAME, Tuple.class, attributes, ITrust.class));
	}

	@Override
	public List<SDFMetaSchema> getSchema() {
		return schema;
	}

	@Override
	public Class<? extends IMetaAttribute>[] getClasses() {
		return classes;
	}

	private static final long serialVersionUID = -426212407481918604L;
	private double trustValue;

	public Trust() {
		this.trustValue = 1;
	}

	public Trust(Trust trust) {
		this.trustValue = trust.trustValue;
	}

	@Override
	public String getName() {
		return NAME;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		} else if (!(obj instanceof Trust)) {
			return false;
		}
		return this.trustValue == ((Trust) obj).trustValue;
	}

	@Override
	public void retrieveValues(List<Tuple<?>> values) {
		@SuppressWarnings("rawtypes")
		Tuple t = new Tuple(1, false);
		t.setAttribute(0, Double.valueOf(this.trustValue));
		values.add(t);
	}

	@Override
	public void writeValue(Tuple<?> value) {
		this.trustValue = ((Double) (value.getAttribute(0))).doubleValue();
	}

	@SuppressWarnings("unchecked")
	@Override
	public <K> K getValue(int subtype, int index) {
		switch (index) {
		case 0:
			return (K) Double.valueOf(this.trustValue);
		default:
			return null;
		}
	}

	@Override
	public void setTrust(double value) {
		this.trustValue = value;
	}

	@Override
	public double getTrust() {
		return this.trustValue;
	}

	@Override
	protected IInlineMetadataMergeFunction<? extends IMetaAttribute> getInlineMergeFunction() {
		return new TrustMergeFunction();
	}

	@Override
	public ITrust clone() {
		return new Trust(this);
	}

	@Override
	public String toString() {
		return "" + this.trustValue;
	}

}

There are quite a lot of methods that need to be implemented. This is necessary to allow combination of metadata types.


	@SuppressWarnings("unchecked")
	public final static Class<? extends IMetaAttribute>[] classes = new Class[] { ITrust.class };

	public static final List<SDFMetaSchema> schema = new ArrayList<>(classes.length);

	static {
		List<SDFAttribute> attributes = new ArrayList<>();
		attributes.add(new SDFAttribute(NAME, TRUSTVALUE, SDFDatatype.DOUBLE));
		schema.add(SDFSchemaFactory.createNewMetaSchema(NAME, Tuple.class, attributes, ITrust.class));
	}

	@Override
	public List<SDFMetaSchema> getSchema() {
		return schema;
	}

	@Override
	public Class<? extends IMetaAttribute>[] getClasses() {
		return classes;
	}


First of all, this class must define, which metatype it provides (here ITrust.class, line 2) and provide a schema for the metadata. This is the same as for operators where an output schema need to be defined with one difference: The created schema must be a Metaschema (line 9).

Then a default constructor and a copy constructor must be defined, and a method that returns the name of the metadata:

	private double trustValue;

	public Trust() {
		this.trustValue = 1;
	}

	public Trust(Trust trust) {
		this.trustValue = trust.trustValue;
	}

	@Override
	public String getName() {
		return NAME;
	}

The next part contains methods to set and get the values:

	@Override
	public void setTrust(double value) {
		this.trustValue = value;
	}

	@Override
	public double getTrust() {
		return this.trustValue;
	}

The following methods must be used to handle cases with tuples. This methods are important to allow sending metadata via network or writing to a file.

retrieveValues requires, that the metadata is returned inside a tuple. So if there are multiple values (e.g. with latency) use different positions inside the tuple. Important use for reading and writing the same positions! 

writeValue ist used to set the value of the metadata object from a tuple object (same position 0 as in retrieve). 

getValue is used to get a single value from the metadata, index is the same as the position inside the tuple (see Latency for a complexer example). subtype is not used in base metadata. This is only used in combined metadata.

	@Override
	public void retrieveValues(List<Tuple<?>> values) {
		@SuppressWarnings("rawtypes")
		Tuple t = new Tuple(1, false);
		t.setAttribute(0, Double.valueOf(this.trustValue));
		values.add(t);
	}

	@Override
	public void writeValue(Tuple<?> value) {
		this.trustValue = ((Double) (value.getAttribute(0))).doubleValue();
	}

	@SuppressWarnings("unchecked")
	@Override
	public <K> K getValue(int subtype, int index) {
		switch (index) {
		case 0:
			return (K) Double.valueOf(this.trustValue);
		default:
			return null;
		}
	}

Metadata must be combined, e.g. in a join or in an aggregation. The method getInlineMergeFunction returns a function that can do the merge.

	@Override
	protected IInlineMetadataMergeFunction<? extends IMetaAttribute> getInlineMergeFunction() {
		return new TrustMergeFunction();
	}

This merge function must be provided as follows:

public class TrustMergeFunction implements IInlineMetadataMergeFunction<ITrust> {

	@Override
	public void mergeInto(ITrust result, ITrust inLeft, ITrust inRight) {
		result.setTrust(Math.min(inLeft.getTrust(), inRight.getTrust()));
	}

	@Override
	public IInlineMetadataMergeFunction<? super ITrust> clone() {
		return this;
	}

	@Override
	public Class<? extends IMetaAttribute> getMetadataType() {
		return ITrust.class;
	}

}

There must be implemented three methods:

getMetadataType() must deliver the metadata type for which this function will be used. (line 

clone() must return an instance of this function. If the function has a state, it is important to create a new function. Typically, this is not the case, so just return this.

mergeInto is the function that is called by the framework to merge two metadata values. The result must be written to the inout parameter result (this is necessary for easy handling of combined metadata). In this case, the new trust value is the mininum of both values.

Finally, this metadata must be registered. This is done with an OSGi component:

<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="de.uniol.inf.is.odysseus.trust.Trust">
   <implementation class="de.uniol.inf.is.odysseus.trust.Trust"/>
   <service>
      <provide interface="de.uniol.inf.is.odysseus.core.metadata.IMetaAttribute"/>
   </service>
</scr:component>

and registered inside the MANIFEST.MF (line 11)

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Trust Metadata
Bundle-SymbolicName: de.uniol.inf.is.odysseus.trust
Bundle-Version: 1.0.0.qualifier
Require-Bundle: de.uniol.inf.is.odysseus.core,
 de.uniol.inf.is.odysseus.core.server,
 de.uniol.inf.is.odysseus.transform;bundle-version="1.0.0",
 de.uniol.inf.is.odysseus.ruleengine;bundle-version="1.0.0"
Export-Package: de.uniol.inf.is.odysseus.trust
Service-Component: OSGI-INF/Trust.xml
Automatic-Module-Name: de.uniol.inf.is.odysseus.trust
Bundle-ActivationPolicy: lazy
Import-Package: org.slf4j

Finally, there should be operators that could set the values.

For examples see:

Creating a combined metadata type

Different metadata types can be used together. For this, in the best case, a single class should be provided, that combines this data. Typically, this class just delegates to the base classes. In the following is an example with trust and timeinterval:

public class IntervalTrust extends AbstractCombinedMetaAttribute implements ITimeInterval, ITrust {

	private static final long serialVersionUID = 1599620389994530920L;

	@SuppressWarnings("unchecked")
	public final static Class<? extends IMetaAttribute>[] classes = new Class[] { ITimeInterval.class, ITrust.class };

	@Override
	public Class<? extends IMetaAttribute>[] getClasses() {
		return classes;
	}

	public static final List<SDFMetaSchema> schema = new ArrayList<>(classes.length);

	static {
		schema.addAll(TimeInterval.schema);
		schema.addAll(Trust.schema);
	}

	@Override
	public List<SDFMetaSchema> getSchema() {
		return schema;
	}

	@Override
	public String getName() {
		return "IntervalTrust";
	}

The new class must extend AbstractCombinedMetaAttribute and implements the base interfaces for this combination.

The classes for this metadata is the same set of interfaces, and should be in lexical order.

The schema is simply a list containing the subschema of the containing metadata.

	private final ITrust trust;
	private final ITimeInterval timeInterval;

	public IntervalTrust() {
		this.trust = new Trust();
		this.timeInterval = new TimeInterval();
	}

	public IntervalTrust(IntervalTrust other) {
		this.trust = (ITrust) other.trust.clone();
		this.timeInterval = (ITimeInterval) other.timeInterval.clone();
	}

	@Override
	public IntervalTrust clone() {
		return new IntervalTrust(this);
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		} else if (!(obj instanceof IntervalTrust)) {
			return false;
		}
		IntervalTrust other = (IntervalTrust) obj;
		return this.trust.equals(other.trust) && this.timeInterval.equals(other.timeInterval);
	}

Also similiar to the basetypes, there must be a default constructor, a copy constructor a clone-method and an equals method.

	// ------------------------------------------------------------------------------
	// Methods that need to merge different types
	// ------------------------------------------------------------------------------

	@Override
	public void retrieveValues(List<Tuple<?>> values) {
		this.timeInterval.retrieveValues(values);
		this.trust.retrieveValues(values);
	}

	@Override
	public void writeValues(List<Tuple<?>> values) {
		this.timeInterval.writeValue(values.get(0));
		this.trust.writeValue(values.get(1));
	}

	@Override
	public List<IInlineMetadataMergeFunction<? extends IMetaAttribute>> getInlineMergeFunctions() {
		List<IInlineMetadataMergeFunction<? extends IMetaAttribute>> list = new ArrayList<>();
		list.addAll(this.timeInterval.getInlineMergeFunctions());
		list.addAll(this.trust.getInlineMergeFunctions());
		return list;
	}

	@Override
	public <K> K getValue(int subtype, int index) {
		switch (subtype) {
		case 0:
			return this.timeInterval.getValue(0, index);
		case 1:
			return this.trust.getValue(0, index);
		default:
			return null;
		}
	}

	@Override
	public String toString() {
		return "( i= " + this.timeInterval.toString() + " | " + " l=" + this.trust + ")";
	}

The next block contains methods to retrieve and set the combined values and merge functions. As you can see, here is always delegated to the base metadata. Important here is to keep the right order!

In getValue, the subtype, says from which metadata the value should be retrieved.

The last block just contains the getter and setter for the metadata and is always delegated to the base types:


	// ------------------------------------------------------------------------------
	// Delegates for timeInterval
	// ------------------------------------------------------------------------------

	@Override
	public PointInTime getStart() {
		return this.timeInterval.getStart();
	}

	@Override
	public PointInTime getEnd() {
		return this.timeInterval.getEnd();
	}

	@Override
	public void setStart(PointInTime point) {
		this.timeInterval.setStart(point);
	}

	@Override
	public void setEnd(PointInTime point) {
		this.timeInterval.setEnd(point);
	}

	@Override
	public void setStartAndEnd(PointInTime start, PointInTime end) {
		this.timeInterval.setStartAndEnd(start, end);
	}

	@Override
	public int compareTo(ITimeInterval o) {
		return this.timeInterval.compareTo(o);
	}

	// ------------------------------------------------------------------------------
	// Delegates for trust
	// ------------------------------------------------------------------------------

	@Override
	public double getTrust() {
		return this.trust.getTrust();
	}

	@Override
	public void setTrust(double trustValue) {
		this.trust.setTrust(trustValue);
	}
}

When Odysseus at Runtime does not find such a combined metadata it will create a new one and compile it. This is slower and does not always work. So it is better to create a combination.

You could use: GenerateMetadataClassCode (https://git.swl.informatik.uni-oldenburg.de/projects/ODY/repos/odysseus_core/browse/common/core/src/de/uniol/inf/is/odysseus/core/metadata/GenerateMetadataClassCode.java) as a generator for the combined metadata. 

Remark: Typically, it is best to put each combination of metadata to an own bundle to avoid to many dependencies.

Finally, this class need to be registered as metadata, too:

<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="de.uniol.inf.is.odysseus.interval_trust.IntervalTrust">
   <implementation class="de.uniol.inf.is.odysseus.interval_trust.IntervalTrust"/>
   <service>
      <provide interface="de.uniol.inf.is.odysseus.core.metadata.IMetaAttribute"/>
   </service>
</scr:component>