Dynamic Properties
Introduction
Dynamic Properties are one of two types of properties that use Jython scripts for providing special functionality to OpenBIS. To understand the basic concept read about Properties Handled By Plugins.
Defining dynamic properties
To create a dynamic property:
- Define a property type with appropriate name and data type (
Administration->Property Types->New
) - Define a script which should be used to calculate the value of your dynamic property (
Administration->Scripts
) - Assign the created property type to chosen entity type using the created script (e.g. for samples:
Administration->Property Types->Assign to Sample Type
):- select
Handled By Script
checkbox, - select
Dynamic Property Evaluator
radio option for script type, - choose the appropriate
Script
(formula)
- select
Creating scripts
To browse and edit existing scripts or add new ones, select Administration->Scripts from the top menu.
The scripts should be written using standard Jython syntax. Unlike custom columns and filters (which also require Jython syntax), dynamic properties can have more than one line of code. If the Script contains only one line, it will be evaluated and used as the value of appropriate dynamic property. If on the other hand a multi line script is needed, the function named "calculate"
will be expected and the the result will be used as property value.
To access the entity object from the script, use the following syntax:
entity.<requested method>
Currently available methods that can be called on all kinds of entities include:
code()
property(propertyTypeCode)
propertyValue(propertyTypeCode)
propertyRendered(propertyTypeCode)
properties()
For more details see IEntityAdaptor (interface implemented by entity
) and IEntityPropertyAdaptor (interface implemented by each property).
It is also possible to acces the complete Java Object by calling "entityPE()"
method, but this is appropach is not recomended, as the returned value is not a part of a well defined API and may change at any point. You may use it as a workaround, in case some data are not accessible via well defined API, but you should contact OpenBIS helpdesk and comunicate your needs, so the appropriate methods can be added to the official API.
You can test your script on selected entities (samples/experiments/materials/data sets) using the testing environment - part of Script Editor
.
Simple Examples
- Show a value of a Sample property which is named 'Multiplicity'
entity.propertyValue('Multiplicity')
2. Takes an existing property and multiplies the value by 1.5
float(entity.propertyValue('CONCENTRATION_ORIGINAL_ILLUMINA'))*1.5
Advanced Examples
- Show all entity properties as one dynamic property:
def get_properties(e): """Automatically creates entity description""" properties = e.properties() if properties is None: return "No properties defined" else: result = "" for p in properties: result = result + "\n" + p.propertyTypeCode() + ": " + p.renderedValue() return result def calculate(): """Main script function. The result will be used as the value of appropriate dynamic property.""" return get_properties(entity)
2. Calculate a new float value based some other values
import java.lang.String as String def calculateValue(): nM = 0 uLDNA = 0 if entity.propertyValue('CONCENTRATION_PREPARED_ILLUMINA') != '' and \ entity.propertyValue('FRAGMENT_SIZE_PREPARED_ILLUMINA') != '' : nM = float(entity.propertyValue('CONCENTRATION_PREPARED_ILLUMINA')) / \ float(entity.propertyValue('FRAGMENT_SIZE_PREPARED_ILLUMINA')) * \ 1000000 / 650 if float(entity.propertyValue('UL_STOCK')) !='' : uLDNA = float(entity.propertyValue('UL_STOCK')) * 2 / nM uLEB = float(entity.propertyValue('UL_STOCK')) - uLDNA return String.format("%16.1f", uLEB) return 0 def calculate(): """Main script function. The result will be used as the value of appropriate dynamic property.""" return calculateValue()
3. Calculate a time difference between two time stamps:
from datetime import datetime def dateTimeSplitter(openbisDate): dateAndTime, tz = openbisDate.rsplit(" ", 1) pythonDateTime = datetime.strptime(dateAndTime, "%Y-%m-%d %H:%M:%S") return pythonDateTime def calculate(): try: start = entity.propertyValue('FLOW_CELL_SEQUENCED_ON') end = entity.propertyValue('SEQUENCER_FINISHED') s = dateTimeSplitter(start) e = dateTimeSplitter(end) diffTime = e-s return str(diffTime) except: return "N/A"
4. Illumina NGS Low Plexity Pooling Checker: checks if the complexity of a pooled sample is good enough for a successful run:
def checkBarcodes(): ''' 'parents' are a HashSet of SamplePropertyPE ''' VOCABULARY_INDEX1 = 'BARCODE' VOCABULARY_INDEX2 = 'INDEX2' RED = set(['A','C']) GREEN = set(['T', 'G']) SUCCESS_MESSAGE="OK" NO_INDEX = "No Index" listofIndices = [] boolList = [] positionList = [] returnString = " " for e in entity.entityPE().parents: for s in e.properties: if s.entityTypePropertyType.propertyType.simpleCode == VOCABULARY_INDEX1: index = s.getVocabularyTerm().code if len(listofIndices) > 0: for n in range(0,len(index)-1): listofIndices[n].append(index[n]) else: for n in range(0,len(index)-1): listofIndices.append([index[n]]) # remove any duplicates setofIndices=[set(list) for list in listofIndices] # Test whether every element in the set 's' is in the RED set boolList=[setofNuc.issubset(RED) for setofNuc in setofIndices] if boolList: for b in boolList: if b: positionList.append(boolList.index(b)+1) # set the value to False, because 'index' returns only the first occurrence boolList[boolList.index(b)]=False else: return NO_INDEX # if s.entityTypePropertyType.propertyType.simpleCode == VOCABULARY_INDEX2: # pass if positionList: for pos in positionList: returnString += "WARNING! Base position " + str(pos) + " of " + \ VOCABULARY_INDEX1 + \ " does not contain both color channels" + \ "\n" else: returnString = SUCCESS_MESSAGE return returnString def calculate(): """Main script function. The result will be used as the value of appropriate dynamic property.""" return checkBarcodes()
Data Types
Any data type that can be used by openBIS properties is supported by dynamic properties. The script always returns just a string representation of a property value. The value is then validated and in special cases converted before being saved. The string formats and validation rules are the same as in batch import/update of samples/experiments/data sets/materials.
The non-trivial cases are properties with data type:
- CONTROLLED VOCABULARY - use code of vocabulary term as string representation,
- MATERIAL - use function
material(code, typeCode)
to create the string representation.
Creating and Deploying Java Plugins
To create valid Java plugin for Dynamic Properties, one should create a class that is implementing ch.systemsx.cisd.openbis.generic.server.dataaccess.dynamic_property.calculator.api.IDynamicPropertyCalculatorHotDeployPlugin
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.server.dataaccess.dynamic_property.calculator.api.IDynamicPropertyCalculatorHotDeployPlugin
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/dynamic-properties
directory. The plugin will be detected automatically and will be automatically available to openBIS. No restart is needed.
Dynamic properties evaluator
Evaluation of dynamic properties may be very time consuming, therefore it is not done automatically after each metadata update. To make sure that the potential inconsistencies are repaired, the maintenance task can be defined (service.properties
), that runs in specified intervals:
maintenance-plugins = dynamic-property-evaluator dynamic-property-evaluator.class = ch.systemsx.cisd.openbis.generic.server.task.DynamicPropertyEvaluationMaintenanceTask # run daily at midnight dynamic-property-evaluator.interval = 86400 dynamic-property-evaluator.start = 00:00
If the value of a dynamic property has not yet been calculated, it will be shown as (pending evaluation)
.