Connect to an ODS Server
The ODS data provider factory is used to connect to an ODS server. It takes parameters, shown in the expample below, to establish a connection. On success a data provider is returned.
List<Value> parameters = new ArrayList<Value>();
String nameservice = "corbaloc::1.2@in-dbserv1:2809/NameService";
parameters.add(ValueType.STRING.newValue("ods_nameservice", nameservice));
parameters.add(ValueType.STRING.newValue("ods_servicename", "MDMTEST01.ASAM-ODS"));
parameters.add(ValueType.STRING.newValue("ods_user", "sa"));
parameters.add(ValueType.STRING.newValue("ods_password", "sa"));
ODSDataProviderFactory odsDataProviderFactory = new ODSDataProviderFactory();
BaseDataProvider dataProvider = odsDataProviderFactory.connect(parameters);
/*
* use the data provider to retrieve data from the ODS server
*/
// don't forget to disconnect
odsDataProviderFactory.disconnect(dataProvider);
Value in DataItem
Nearly each result returned by the data provider is an instances of the type DataItem. Due to the generic application model (attributes, elements and relations can be added at any time) it is not possible to provide getters and settes for each attribute. Instead the POJOs provide getters / setters for currently known and widly used attributes. One can use the getValues() method defined in the DataItem interface to retrieve all available attribute values mapped by their name. A value is a generic container which can hold any kind of a value. Furthermore each data item is instantiated with a complete set with ALL of it’s attributes, no matter a value is set or not. It is not possible to remove Value containers instead the value stored in the container has to be set to null.
The current value implementation is NOT type safe. This will definatley change in the future. Sequence values are currently returned as they come from the ODS server. So for example a sequence of integers (DT_LONG) results in an array with primitive component type: int[]. Since it is much easier to work with collections than arrays with primitive component type, such value sequences may be stored in lists rather than arrays. Enumerations are returned as integers (will change in the future). External references are currently not supported. |
Data Provider
Once a connection is established the returned data provider can be used to retrieve data. One can use the get() method to retrieve all instances of a certain kind:
List<Test> tests = dataProvider.get(Test.class);
for (Test test : tests) {
System.out.println();
System.out.println(test);
}
The shown example produces an output similar to the following:
connecting to ODS Server ... AoFactory name: MDMTEST01 AoFactory description: ASAM ODS Embedded SQL Driver AoFactory interface version: V5.3.0 AoFactory type: connecting to ODS Server ... done! Test(Name = ExampleTest0, Description = test description of Test ExampleTest0, Auftragsnummer = , MimeType = application/x-asam.aosubtest.test, DateCreated = 20141008114337, LockingDate = , DateClosed = 20141008114337) Test(Name = ExampleTest1, Description = test description of Test ExampleTest1, Auftragsnummer = , MimeType = application/x-asam.aosubtest.test, DateCreated = 20141008114803, LockingDate = , DateClosed = 20141008114803) [...]
Accessing cached Types
Instances of Unit, Quantity, PhysicalDimension and User are frequently used (eg. Tests always know the responsible person), therefore they are loaded once. So for example the following request returns a cached result.
List<Unit> units = dataProvider.get(Unit.class);
Retrieve related Children
The data provider can retrieve all related children to a given data item. This is done using the getChildren() method: dataProvider.getChildren(test, Test.CHILD_TYPE_TESTSTEP)
. This method takes a parent data item and a corresponding child class type. The latter can be found as public field in the parent’s POJO. The following lists currently supported combinations:
-
Test → TestStep
-
TestStep → Measurement
-
Measurement → Channel
-
Measurement → ChannelGroup
-
ChannelGroup → Channel (virtual - a channel may be related to multiple channel groups!)
Retrieve Context Data
In MDM a context is either related to TestStep (as ordered) or Measurement (as measured). Therefore only instances of these types can be passed to the getContext() method. It takes a context describable and the required ContextTypes (UNITUNDERTEST, TESTSEQUENCE, TESTEQUIPMENT). If none ContextType is passed, then all types will be returned.
for(ContextRoot contextRoot : dataProvider.getContext(measurement).values()) {
System.out.println();
System.out.println(contextRoot);
}
An output of the upper example may look like:
UnitUnderTest(Name = abcTest,MimeType = application/x-asam.aounitundertest.unitundertest,Version = 22772,[abc(Name = abc, Test1 = 1, MimeType = application/x-asam.aounitundertestpart.abc.abc, Test2 = , Test3 = )]) TestSequence(Name = Messversuch,MimeType = application/x-asam.aotestsequence.testsequence,Version = 22733,[Motorbetrieb(Name = Motorbetrieb, Auftragsnummer = , MimeType = application/x-asam.aotestsequencepart.motorbetrieb.motorbetrieb, MotordrehzahlStop = 34, MotordrehzahlStart = 343, Messart = linear, Dauer = 434)]) TestEquipment(Name = Prüfstand,MimeType = application/x-asam.aotestequipment.testequipment,Version = 22723,[Notebook(Seriennummer = , Name = Notebook, Hersteller = , MimeType = application/x-asam.aotestequipmentpart.notebook.notebook, Typ = ), Soundkarte(Name = Soundkarte, MimeType = application/x-asam.aotestequipmentpart.soundkarte.soundkarte, Bezeichnung = sdf, Typ = asdf), CANInterface(Name = CANInterface, Konfiguration = , MimeType = application/x-asam.aotestequipmentpart.caninterface.caninterface, Bezeichnung = , Typ = ), SoundRecorderSoftware(Name = SoundRecorderSoftware, MimeType = application/x-asam.aotestequipmentpart.soundrecordersoftware.soundrecordersoftware, Bezeichnung = , Version = ), CANDecoder(Name = CANDecoder, Konfiguration = , MimeType = application/x-asam.aotestequipmentpart.candecoder.candecoder, Bezeichnung = , Version = )])
Create / Update / Delete
Meanwhile it is possible to create, modify and delete data items. How this is done is described in the following chapters. To be able to do this it is currently required to cast the dataProvider to ODSDataProvider
because this part of the API is still under development and therefore not present in the BaseDataProvider
interface. Full examples with create, update and delete can be found in the Main.java which is described in the last chapter: Try the examples.
Create
A data item consists of attributes and may have relations to other data items. In the following we want to create 2 test data items with names 'Test1' and 'Test2', which we, the logged in user, will be responsible for.
odsDataProvider.startTransaction();
User user = odsDataProvider.getLoggedOnUser().get();
List<String> testNameList = Arrays.asList("Test1", "Test2");
List<Test> tests = odsDataProvider.create(Test.class, testNameList, user); // (1)
odsDataProvider.commitTransaction();
At (1) we specify the class of the data items we want to create and their names. In addition to that we pass a user data item so that the created test data items will be directly related with the passed user. Similarly to this example we can create test steps and immediately relate them with a parent test and so on.
Any created data item has initially only the mandatory attributes filled with values (ID - is auto generated, Name - is specified, MimeType - A default mime type is taken). |
Update
Mostly any attribute of a data item is allowed to be changed. So let’s say we have a test data item and want to change its description. In addition to that we also want to replace the currently associated responsible person. This is easily done as shown below:
User newResponsiblePerson = ...;
Test test = ...;
test.setDescription("new description");
test.setResponsiblePerson(newResponsiblePerson);
To make those changes permanent the modified test has to be passed to the update() method of the ODSDataProvider
:
odsDataProvider.startTransaction();
odsDataProvider.update(Collections.singletonList(test));
odsDataProvider.commitTransaction();
Delete Data
Any data item that implements the Deletable interface is allowed to be deleted. So if for example a test is deleted, all of it’s related children will be deleted as well. In the following example we will delete all test data we previously have created:
odsDataProvider.startTransaction();
// relations to children are recursively followed and deleted as well!
odsDataProvider.delete(odsDataProvider.get(Test.class));
odsDataProvider.commitTransaction();
Write / Read Mass Data
Basic write / read operations for mass data is implemented and still under development therefore the data provider has to be cast to ODSDataProvider
to read or write mass data. Full examples with write and read can be found in the Main.java which is described in the last chapter: Try the examples.
Following examples are build on top of the given examples in the previous chapters Create / Update / Delete! |
Write
In the following example we will show how channel values with an explicit value sequence are written . At first the length of the value sequence for each channel must be set. This is done by setting the ChannelGroup.ATTR_NUMBER_OF_VALUES
attribute (e.g.: 10 - 10 values per channel):
odsDataProvider.startTransaction();
ChannelGroup channelGroup = odsDataProvider.getChildren(measurement, Measurement.CHILD_TYPE_CHANNELGROUP).get(0); // we assume there is exactly one
channelGroup.getValue(ChannelGroup.ATTR_NUMBER_OF_VALUES).set(valueSEQLength);
odsDataProvider.update(Collections.singletonList(channelGroup));
odsDataProvider.commitTransaction();
In the next step we define channel write requests for each channel:
ChannelValuesWriteRequest request = ChannelValuesWriteRequest.create(
channel1, channelGroup, // channel and associated channel group
new ChannelValue(
ValueType.FLOAT_SEQUENCE.newValue("Values", new float[] { 1,2,3,4,5,6,7,8,9,10 }), // value sequence
new boolean[0]), // flags - new boolean[0] -> all flags are valid
AxisType.X_AXIS, // the axis type
Representation.EXPLICIT, // explicit means no calculation is required
true, // this is an independent channel
new double[0])); // genration parameters are not required
Finally the created request has to be passed the ODSDataProvider
:
odsDataProvider.startTransaction();
odsDataProvider.writeChannelValues(request);
odsDataProvider.commitTransaction();
Read
The following example shows how to read channel value sequences in pieces. At first we need to define a read request as follows:
ChannelValuesReadRequest readRequest = ChannelValuesReadRequest.create(
channelGroup, // this may either be a measurement or a channel group
Collections.emptyList(), // empty list means all related channels
3, // number of values that will be returned for each channel
0); // start position - may be used to skip first n values
This request is now fully described and can be used to iterate over the complete value sequence of all channels related to given channel group:
Optional<ChannelValuesReadRequest> request = Optional.of(readRequest);
while(request.isPresent()) {
System.out.println(baseDataProvider.readChannelValues(request.get())); // retrieve part of the sequence and print the values
request = request.get().next(); // trigger the request for the next part of the value sequence
}
The output may look like:
// part 1 [Channel_1 = [1.0, 2.0, 3.0] [-], Channel_2 = [true, true, false] [-], Channel_3 = [5, 32, 42] [-], Channel_4 = [423, 645, 221] [-], Channel_5 = [s1, s2, s3] [-], Channel_6 = [2015-12-23T07:55:49, 2015-12-24T07:55:49, 2015-12-25T07:55:49] [-], Channel_7 = [[B@38cccef, [B@5679c6c6, [B@27ddd392] [-]] // part 2 [Channel_1 = [4.0, 5.0, 6.0] [-], Channel_2 = [true, true, false] [-], Channel_3 = [9, 17, 65] [-], Channel_4 = [111, 675, 353] [-], Channel_5 = [s4, s5, s6] [-], Channel_6 = [2015-12-26T07:55:49, 2015-12-27T07:55:49, 2015-12-28T07:55:49] [-], Channel_7 = [[B@19e1023e, [B@7cef4e59, [B@64b8f8f4] [-]] // part 3 [Channel_1 = [7.0, 8.0, 9.0] [-], Channel_2 = [true, false, false] [-], Channel_3 = [13, 8, 15] [-], Channel_4 = [781, 582, 755] [-], Channel_5 = [s7, s8, s9] [-], Channel_6 = [2015-12-29T07:55:49, 2015-12-30T07:55:49, 2015-12-31T07:55:49] [-], Channel_7 = [[B@3cd1f1c8, [B@3a4afd8d, [B@1996cd68] [-]] // part 4 [Channel_1 = [10.0] [-], Channel_2 = [false] [-], Channel_3 = [21] [-], Channel_4 = [231] [-], Channel_5 = [s10] [-], Channel_6 = [2016-01-01T07:55:49] [-], Channel_7 = [[B@3339ad8e] [-]]
Custom Queries
The data provider gives access to its query service which allows to build and execute custom queries. A Query is build by using classes representing the application model:
-
Attribute - represents application attributes
-
Entity - represents application elements
-
Relation - represents relations between application elements
To build a custom query we need entities whose data we want to retrieve:
QueryService qs = dataProvider.getQueryService().get();
Entity testEntity = qs.getEntity(Test.class);
Entity testStepEntity = qs.getEntity(TestStep.class);
Entity measurementEntity = qs.getEntity(Measurement.class);
The next step is to define selected columns (entity attributes):
Query query = qs.createQuery();
// select test name and id
query.select(testEntity, Test.ATTR_NAME, Test.ATTR_ID);
// select all test step attributes
query.selectAll(testStepEntity);
// select measurement id and name
query.select(measurementEntity, Measurement.ATTR_NAME, Measurement.ATTR_ID);
Prior executing the query we need to add required joins:
// join from Test to TestStep
query.join(testEntity, testStepEntity);
// join from TestStep to Measurement
query.join(testStepEntity, measurementEntity);
Now the query is completely described and may be executed by calling query.fetch()
. Conditions (filtering criteria) may be defined to retrieve only a subset of the previous result. Conditions are put together unsing filters (AND, OR). The following example shows an AND filter which is used to exclude a measurement with given ID and select a test with a given id:
Filter filter = Filter.and();
// exclude measurement by ID
filter.add(Operation.NEQ.create(measurementEntity.getIDAttribute(), 498426L));
// AND Operator is implicitly added
// select test by ID
filter.add(Operation.EQ.create(testEntity.getIDAttribute(), 4418L));
// execute the query
query.fetch(filter).stream().forEach(System.out::println);
A corresponding output may look like:
Result(records = [Record(entity = MeaResult, values = [Id = 498425, Name = MINILINK.SIZE]), Record(entity = Test, values = [Id = 4418, Name = SC1]), Record(entity = TestStep, values = [Sortindex = 20, Description = , MDMLinks = , Optional = true, DateCreated = 2014-11-10T15:26:56, Id = 30088, MimeType = application/x-asam.aosubtest.teststep, Name = Altium])]) Result(records = [Record(entity = MeaResult, values = [Id = 498427, Name = MINILINK.SIZE]), Record(entity = Test, values = [Id = 4418, Name = SC1]), Record(entity = TestStep, values = [Sortindex = 40, Description = , MDMLinks = , Optional = true, DateCreated = 2014-11-10T15:26:57, Id = 30090, MimeType = application/x-asam.aosubtest.teststep, Name = Windriver])])
Search service
A more comfortable way to search for data within the connected source is possible with the search service dataProvider.getSearchService()
. This service executes internally a search query that is associated with a given data item type and returns found data item instances mapped to their related records. The supported data item types may be retrieved by using searchService.listSearchableTypes()
(search queries for Test, TestStep, Measurement and Channel are available).
Supported entities
Let’s say we want to search for measurements and need to know which entities are supported by the associated search query. By using searchService.listEntities(Measurement.class)
we will receive a distinct list of all supported entities:
[Test, TestStep, TestEquipment, User, MeaResult, Notebook]
In some cases it might be usefull to know how the entities are reloted to each other. Here comes searchService.getSearchableRoot(Measurement.class)
into play. It returns a tree of searchable nodes, where each searchable represents an entity:
// print the searchable tree
printSearchable("", searchService.getSearchableRoot(Measurement.class));
....
private static void printSearchable(String prefix, Searchable searchable) {
System.out.println(prefix + "" + searchable);
if(!searchable.isLeaf()) {
searchable.getRelated().stream().forEach(s -> printSearchable(prefix + "\t", s));
}
}
The example above produces a result similar to:
Searchable(entity = Test, implicit = false, related = [TestStep, User]) Searchable(entity = TestStep, implicit = false, related = [TestEquipment, MeaResult]) Searchable(entity = TestEquipment, implicit = false, related = [Notebook]) Searchable(entity = Notebook, implicit = false) Searchable(entity = MeaResult, implicit = true) Searchable(entity = User, implicit = false)
Filter values
Now that we know the supported entities we can use searchService.getFilterValues()
to retrieve a distinct list of possible filter values for attributes that are associated with any of the supported entities. To keep this example simple we print all names of Test data items:
Entity testEntity = dataProvider.getQueryService().get().getEntity(Test.class);
System.out.println(searchService.getFilterValues(Measurement.class, testEntity.getNameAttribute(), Filter.and()));
The corresponding result should look like:
[Name = ExampleTest1, Name = ExampleTest0, Name = ExampleTest2]
Use the service
Finally we want to use this service to retrieve all measurements that belong to a test whose name is equal to 'ExampleTest0' including the records of the related test and test step:
Entity testStepEntity = dataProvider.getQueryService().get().getEntity(TestStep.class);
List<Entity> relatedEntities = Arrays.asList(testEntity, testStepEntity);
Filter filter = Filter.and().add(Operation.EQ.create(testEntity.getNameAttribute(), "ExampleTest0"));
for(Entry<Measurement, List<Record>> entry : searchService.fetchComplete(Measurement.class, relatedEntities, filter).entrySet()) {
System.out.println("// measurement data item");
System.out.println(entry.getKey());
System.out.println();
System.out.println("// related test record");
System.out.println(entry.getValue().get(0));
System.out.println();
System.out.println("// related test step record");
System.out.println(entry.getValue().get(1));
}
The corresponding output may look like:
// measurement data item Measurement(TEST WINTERREIFEN = , Description = , MDMLinks = [], test123 = 0, MeasurementEnd = null, DateCreated = 2014-10-08T12:10:14, MimeType = application/x-asam.aomeasurement, MeasurementBegin = null, Name = MeaResult) // related test record Record(entity = Test, values = [DateClosed = 2014-10-08T12:10:13, Description = test description of Test ExampleTest0, LockingDate = null, MDMLinks = [], DateCreated = 2014-10-08T12:10:13, Id = 4309, Auftragsnummer = , MimeType = application/x-asam.aosubtest.test, Name = ExampleTest0]) // related test step record Record(entity = TestStep, values = [Sortindex = 20, Description = , MDMLinks = [], Optional = true, DateCreated = 2014-10-08T12:10:13, Id = 29807, MimeType = application/x-asam.aosubtest.teststep, Name = ExampleTestStep])]
In the upper example we used searchService.fetchComplete()
to select all attributes of given entities. Sometimes it is sufficient to select only a subset of an entity’s attributes. In such a case searchService.fetch()
should be used. This method takes instead of entities a list of the selected attributes.
Try the examples
You can use this Main.java to try described examples or run your own on its basis (adjust connection details and ids at TODO tags).