Skip to content
Snippets Groups Projects
properties-handled-by-scripts.md 53.1 KiB
Newer Older
  • Learn to ignore specific revisions
  • Marco Del Tufo's avatar
    ..
    Marco Del Tufo committed
    
    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.
    
    
    Marco Del Tufo's avatar
    ..
    Marco Del Tufo committed
        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)
    
    Marco Del Tufo's avatar
    ..
    Marco Del Tufo committed
    
    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
    
    
    Marco Del Tufo's avatar
    ..
    Marco Del Tufo committed
        <root>
          <Sample permId="samplePermId"/>
          <Material permId="type (typeCode)"/>
          <testElement key1="value1" key2="value2"/>
        </root>
    
    Marco Del Tufo's avatar
    ..
    Marco Del Tufo committed
    
    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:
    
    Marco Del Tufo's avatar
    ..
    Marco Del Tufo committed
    
    
    Marco Del Tufo's avatar
    ..
    Marco Del Tufo committed
        <root>
          <Sample permId="modifiedLink"/>
          <Material permId="type (typeCode)">
            <nested1 na1="na2"/>
          </Material>
          <testElement key1="value1" key2="modifiedvalue"/>
        </root>
    
    Marco Del Tufo's avatar
    ..
    Marco Del Tufo committed
    
    #### 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.
    
    
    Marco Del Tufo's avatar
    ..
    Marco Del Tufo committed
        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)
    
    Marco Del Tufo's avatar
    ..
    Marco Del Tufo committed
    
    ### 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