Newer
Older
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
##### Linking to openBIS entities
Astute readers may have already noticed that the
[IElementFactory](http://svnsis.ethz.ch/doc/openbis/current/ch/systemsx/cisd/openbis/generic/shared/managed_property/api/IElementFactory.html)
also offers methods that create
[IEntityLinkElement](http://svnsis.ethz.ch/doc/openbis/current/ch/systemsx/cisd/openbis/generic/shared/managed_property/api/IEntityLinkElement.html)
instances. An IEntityLinkElement denotes a link to another object in
openBIS.
Currently the entity links can lead to entities that don't exist in the
database. Such links are displayed as text instead of html links in
grids defined for managed properties.
##### Converting to property value
Once you a have created the desired data structure in form of
[IElement](http://svnsis.ethz.ch/doc/openbis/current/ch/systemsx/cisd/openbis/generic/shared/managed_property/api/IElement.html)-s,
you can use an
[IStructuredPropertyConverter](http://svnsis.ethz.ch/doc/openbis/current/ch/systemsx/cisd/openbis/generic/shared/managed_property/api/IStructuredPropertyConverter.html)
to convert it to a property value. An instance of
[IStructuredPropertyConverter](http://svnsis.ethz.ch/doc/openbis/current/ch/systemsx/cisd/openbis/generic/shared/managed_property/api/IStructuredPropertyConverter.html)
can be created from the [\#Predefined
Functions](#ManagedProperties-PredefinedFunctions).
Managed properties can be stored either as XML Strings or as JSON
Strings. The script writer makes the decision for a serialization type
by either calling `xmlPropertyConverter()` or `jsonPropertyConverter()`.
Note that both converters can convert from XML or JSON Strings
to `IElement` lists, detecting automatically which type of String they
get. The two converters only differ in what type of serialization they
use when converting from `List<IElement>` to a String. By this mechanism
it is even possible to change the serialization type after values of the
managed property have been created and stored in the database without
breaking the functionality of managed properties. To maintain this
transparency it is recommended that API users avoid parsing the XML or
JSON Strings themselves and let the converter do the job.
##### Jython example script
Sometimes a few lines of code are worth a thousand words. Have a look at
the example code below. It is extracted from a Jython script and
demonstrates the basics of constructing and serializing structured
content within a managed property.
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
factory = elementFactory()
converter = xmlPropertyConverter()
def initialCreationOfPropertyValue():
"""
Create an element data structure containing
1) A link to Sample with specified perm id
2) A link to Material with specified type and typCode
3) An application specific element "testElement"
"""
elements = [
factory.createSampleLink("samplePermId"),
factory.createMaterialLink("type", "typeCode"),
factory.createElement("testElement").addAttribute("key1", "value1").addAttribute("key2", "value2")
]
# save the created data structure into the property value
property.value = converter.convertToString(elements)
def updateDataStructure():
"""
This function imitates an update procedure. The content of the property
is parsed to a list of elements, several modifications are made on the elements
and these are then saved back in the property.
"""
# read the stored data structure
elements = list(converter.convertToElements(property))
# we assume, the contents from the "create..." method above
elements[0] = factory.createSampleLink("modifiedLink")
elements[1].children = [
factory.createElement("nested1").addAttribute("na1", "na2")
]
# replaces the old value of the "key2" attribute
elements[2].addAttribute("key2", "modifiedvalue")
# update the property value to reflect the modified data structure
property.value = converter.convertToString(elements)
At the end of the function `initialCreationOfPropertyValue()`, the
variable `property.value` will contain an XML representation of the
created data structure, which will look like
<root>
<Sample permId="samplePermId"/>
<Material permId="type (typeCode)"/>
<testElement key1="value1" key2="value2"/>
</root>
The function `updateDataStructure()` assumes that the
`initialCreationOfPropertyValue()` has already been called and modifies
the data structure to what would translate to the following XML snippet:
<root>
<Sample permId="modifiedLink"/>
<Material permId="type (typeCode)">
<nested1 na1="na2"/>
</Material>
<testElement key1="value1" key2="modifiedvalue"/>
</root>
#### Unofficial API
In addition to the variable `property`, the variable `propertyPE` is
also available to managed property scripts. Its use is not officially
supported and code that uses it is not guaranteed to work after an
upgrade of openBIS, but it can be used to get access to useful
information that is not available through the official API.
#### 'Real World' example
The following example shows a complete implementation of a managed
property script for handling list of log entries. The property value is
stored as an XML document.
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
from java.util import Date
"""
Example XML property value handled by this script:
<root>
<logEntry date="2011-02-20 14:15:28 GMT+01:00" person="buczekp" logType="INFO">Here is the 1st log entry text.<logEntry>
<logEntry date="2011-02-20 14:16:28 GMT+01:00" person="kohleman" logType="WARN">Here is the 2nd log entry text - a warning!<logEntry>
<logEntry date="2011-02-20 14:17:28 GMT+01:00" person="tpylak" logType="ERROR">Here is the 3rd log entry text - an error!!!<logEntry>
<logEntry date="2011-02-20 14:18:28 GMT+01:00" person="brinn" logType="ERROR">Here is the 4th log entry text - an error!!!<logEntry>
<logEntry date="2011-02-20 14:19:28 GMT+01:00" person="felmer" logType="WARN">Here is the 5th log entry text - a warning!<logEntry>
</root>
"""
LOG_ENTRY_ELEMENT_LABEL = 'logEntry'
LOG_TYPES = ['INFO', 'WARN', 'ERROR']
""" labels of table columns and corresponding input fields """
DATE_LABEL = 'Date'
PERSON_LABEL = 'Person'
LOG_TYPE_LABEL = 'Log Type'
LOG_TEXT_LABEL = 'Log Text'
""" names of attributes of XML elements for log entries """
DATE_ATTRIBUTE = 'date'
PERSON_ATTRIBUTE = 'person'
LOG_TYPE_ATTRIBUTE = 'logType'
""" action labels (shown as button labels in UI) """
ADD_ACTION_LABEL = 'Add Log Entry'
EDIT_ACTION_LABEL = 'Edit'
DELETE_ACTION_LABEL = 'Delete'
def configureUI():
"""Create table builder and add headers of columns."""
builder = createTableBuilder()
builder.addHeader(DATE_LABEL, 250) # date and log text values are long, override default width (150)
builder.addHeader(PERSON_LABEL)
builder.addHeader(LOG_TYPE_LABEL)
builder.addHeader(LOG_TEXT_LABEL, 300)
"""
Extract XML elements from property value to a Python list.
For each element (log entry) add add a row to the table.
"""
elements = list(xmlPropertyConverter().convertToElements(property))
for logEntry in elements:
row = builder.addRow()
row.setCell(DATE_LABEL, Date(long(logEntry.getAttribute(DATE_ATTRIBUTE))))
row.setCell(PERSON_LABEL, logEntry.getAttribute(PERSON_ATTRIBUTE))
row.setCell(LOG_TYPE_LABEL, logEntry.getAttribute(LOG_TYPE_ATTRIBUTE))
row.setCell(LOG_TEXT_LABEL, logEntry.getData())
"""Specify that the property should be shown in a tab and set the table output."""
property.setOwnTab(True)
uiDescription = property.getUiDescription()
uiDescription.useTableOutput(builder.getTableModel())
"""
Define and add actions with input fields used to:
1. specify attributes of new log entry,
"""
addAction = uiDescription.addTableAction(ADD_ACTION_LABEL)\
.setDescription('Add a new log entry:')
widgets = [
inputWidgetFactory().createComboBoxInputField(LOG_TYPE_LABEL, LOG_TYPES)\
.setMandatory(True)\
.setValue('INFO'),
inputWidgetFactory().createMultilineTextInputField(LOG_TEXT_LABEL)\
.setMandatory(True)
]
addAction.addInputWidgets(widgets)
"""
2. modify attributes of a selected log entry,
"""
editAction = uiDescription.addTableAction(EDIT_ACTION_LABEL)\
.setDescription('Edit selected log entry:')
# Exactly 1 row needs to be selected to enable action.
editAction.setRowSelectionRequiredSingle()
widgets = [
inputWidgetFactory().createMultilineTextInputField(LOG_TEXT_LABEL).setMandatory(True)
]
editAction.addInputWidgets(widgets)
# Bind field name with column name.
editAction.addBinding(LOG_TEXT_LABEL, LOG_TEXT_LABEL)
"""
3. delete selected log entries.
"""
deleteAction = uiDescription.addTableAction(DELETE_ACTION_LABEL)\
.setDescription('Are you sure you want to delete selected log entries?')
# Delete is enabled when at least 1 row is selected.
deleteAction.setRowSelectionRequired()
def updateFromUI(action):
"""Extract list of elements from old value of the property."""
converter = xmlPropertyConverter()
elements = list(converter.convertToElements(property))
"""Implement behaviour of user actions."""
if action.name == ADD_ACTION_LABEL:
"""
For 'add' action create new log entry element with values from input fields
and add it to existing elements.
"""
element = elementFactory().createElement(LOG_ENTRY_ELEMENT_LABEL)
"""Fill element attributes with appropriate values."""
element.addAttribute(DATE_ATTRIBUTE, str(Date().getTime())) # current date
element.addAttribute(PERSON_ATTRIBUTE, action.getPerson().getUserId()) # invoker the action
"""Retrieve values from input fields filled by user on the client side."""
element.addAttribute(LOG_TYPE_ATTRIBUTE, action.getInputValue(LOG_TYPE_LABEL))
"""Set log text as a text element, not an attribute."""
element.setData(action.getInputValue(LOG_TEXT_LABEL))
"""Add the new entry to the end of the element list."""
elements.append(element)
elif action.name == EDIT_ACTION_LABEL:
"""
For 'edit' action find the log entry element corresponding to selected row
and replace it with an element with values from input fields.
"""
selectedRowId = action.getSelectedRows()[0]
elements[selectedRowId].setData(action.getInputValue(LOG_TEXT_LABEL))
elif action.name == DELETE_ACTION_LABEL:
"""
For 'delete' action delete the entries that correspond to selected rows.
NOTE: As many rows can be deleted at once it is easier to delete them in reversed order.
"""
rowIds = list(action.getSelectedRows())
rowIds.reverse()
for rowId in rowIds:
elements.pop(rowId)
else:
raise ValidationException('action not supported')
"""Update value of the managed property to XML string created from modified list of elements."""
property.value = converter.convertToString(elements)
### Creating and Deploying Java Plugins
To create valid Java plugin for Managed Properties, one should create a
class that is
implementing `ch.systemsx.cisd.openbis.generic.shared.managed_property.api.IManagedPropertyHotDeployEvaluator` interface.
The class should be annotated
with `ch.ethz.cisd.hotdeploy.PluginInfo` annotation specifying the name
of the plugin,
and `ch.systemsx.cisd.openbis.generic.shared.managed_property.api.IManagedPropertyHotDeployEvaluator` class
as a plugin type.
Such a plugin should be exported to a jar file and put
into `<<openBIS installation directory>>/servers/entity-related-plugins/managed-properties` directory.
The plugin will be detected automatically and will be automatically
available to openBIS. No restart is needed.
- No labels