XML

Точками входа для работы с XML служат функции Spin io.openbpm.spin.Spin.S и io.openbpm.spin.Spin.XML. Вторая дает строго типизированный доступ к XML API Spin, поэтому в коде на Java лучше использовать ее. В скриптах доступна только функция S. Возвращаемая обертка Spin предоставляет методы для изменения данных, записи их в XML и отображения XML на Java-объекты. Функциям входа можно передавать и Java-объекты: они неявно преобразуются в промежуточное XML-представление Spin.

Ниже показано, как работать с данными XML через Spin в процессном движке. Допустим, есть переменная процесса customer типа String, содержащая XML:

<?xml version="1.0" encoding="UTF-8"?>
<customer xmlns="http://openbpm.ru/example" name="Jonny">
  <address>
    <street>12 High Street</street>
    <postCode>1234</postCode>
  </address>
</customer>

Подробнее об использовании Spin см. справочник по форматам данных Spin.

Интеграция с языком выражений

Функции входа Spin можно использовать везде, где процессный движок допускает язык выражений. В следующем фрагменте BPMN условие потока управления проверяет почтовый индекс клиента:

...
<sequenceFlow>
  <conditionExpression xsi:type="tFormalExpression">
    ${XML(customer).xPath("/customer/address/postCode").element().textContent() == "1234"}
  </conditionExpression>
</sequenceFlow>
...

Если переменная уже хранится как XML-переменная, а не строка, как в предыдущем примере, вызов XML(…​) можно опустить и обратиться к переменной напрямую:

...
<sequenceFlow>
  <conditionExpression xsi:type="tFormalExpression">
    ${customer.xPath("/customer/address/postCode").element().textContent() == "1234"}
  </conditionExpression>
</sequenceFlow>
...

Интеграция со скриптами

Следующий скрипт на JavaScript через Spin API извлекает адрес из переменной customer, добавляет к нему город и сохраняет результат в переменную процесса:

...
<scriptTask id="task" name="Script Task" scriptFormat="javascript">
  <script>
    <![CDATA[
    var address = S(customer).element("address");
    var city = XML("<city>New York</city>");
    address.append(city);
    execution.setVariable("address", address.toString());
    ]]>
  </script>
</scriptTask>
...

Встроенные значения переменных XML

Встроенные значения переменных XML позволяют сразу разобрать строку XML и обернуть ее в объект без отдельного класса, представляющего XML. Например, чтобы сохранить XML в переменной процесса для дальнейшего использования, в JavaDelegate можно написать:

public class MyDelegate implements JavaDelegate {

  @Override
  public void execute(DelegateExecution execution) throws Exception {
    String xml = "<customer xmlns=\"http:\\/\\/example.org/customer\" name=\"Jonny\">"
          + "<address>"
            + "<street>12 High Street</street>"
            + "<postCode>1234</postCode>"
          + "</address>"
        + "</customer>";
    XmlValue xmlValue = SpinValues.xmlValue(xml).create();
    execution.setVariable("customerJonny", xmlValue);
  }
}

Вызов SpinValues.xmlValue(…​).create() преобразует строку в объект DomXML, обернутый в Spin.

Чтобы получить XML в другом JavaDelegate и, например, дополнить его, достаточно написать:

public class AddDataDelegate implements JavaDelegate {
  @Override
  public void execute(DelegateExecution execution) throws Exception {
    XmlValue customer = execution.getVariableTyped("customerJonny");
    SpinXmlElement xmlElement = customer.getValue().append(Spin.XML("<creditLimit>1000.00</creditLimit>"));
    customer = SpinValues.xmlValue(xmlElement).create();
    execution.setVariable("customerJonny", customer);
    //<?xml version="1.0" encoding="UTF-8"?><customer xmlns="http:\/\/example.org/customer" name="Jonny"><address><street>12 High Street</street><postCode>1234</postCode></address><creditLimit xmlns="">1000.00</creditLimit></customer>
  }
}

Через execution.getVariableTyped() XML-значение можно получить в сериализованном или десериализованном виде. При вызове getVariableTyped("name") или getVariableTyped("name", true) переменная десериализуется, и XmlValue содержит обернутый объект DomXML с данными XML. При вызове getVariableTyped("name", false) XmlValue содержит только исходную строку. Это удобно, если нужна только строка, например чтобы передать ее в другой API.

Сериализация переменных процесса

Java-объект можно сериализовать во встроенный формат данных Spin. Допустим, есть два Java-класса, com.example.Customer и com.example.Address. Формат XML Spin по умолчанию основан на JAXB, поэтому сериализацию можно настроить аннотациями JAXB, например @XmlRootElement, @XmlAttribute и @XmlElement. Эти аннотации необязательны. Классы выглядят так:

@XmlRootElement(namespace = "http://openbpm.ru/example")
public class Customer {
  protected String name;
  protected Address address;

  @XmlAttribute
  public String getName() { .. }

  @XmlElement(namespace = "http://openbpm.ru/example")
  public Address getAddress() { .. }

  /* конструктор и сеттеры опущены для краткости */
}

public class Address {
  protected String street;
  protected int postCode;

  @XmlElement(namespace = "http://openbpm.ru/example")
  public String getStreet() { .. }

  @XmlElement(namespace = "http://openbpm.ru/example")
  public int getPostCode() { .. }

  /* конструктор и сеттеры опущены для краткости */
}

Следующий код на Java записывает в переменную процесса объект Customer, сериализованный в формат данных XML Spin:

Address address = new Address("12 High Street", 1234);
Customer customer = new Customer("jonny", address);

ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("aProcess");

ObjectValue typedCustomerValue =
  Variables.objectValue(customer).serializationDataFormat("application/xml").create();

runtimeService.setVariable(processInstance.getId(), "customer", typedCustomerValue);

Ключевая команда:

ObjectValue typedCustomerValue =
  Variables.objectValue(customer).serializationDataFormat("application/xml").create();

Она создает значение переменной для объекта Customer. Вызов serializationDataFormat("application/xml") сообщает процессному движку, в каком формате сериализовать переменную. Имя должно совпадать с именем формата данных, известного Spin. Например, application/xml служит именем встроенного формата данных XML.

После записи переменной ее сериализованное значение можно получить через API типизированных значений переменных. Например:

ObjectValue customer = runtimeService.getVariableTyped(processInstance.getId(), "customer");
String customerXml = customer.getValueSerialized();

/*
customerXml содержит:
<?xml version="1.0" encoding="UTF-8"?>
<customer xmlns="http://openbpm.ru/example" name="Jonny">
  <address>
    <street>12 High Street</street>
    <postCode>1234</postCode>
  </address>
</customer>
*/

При чтении переменной сериализованное значение десериализуется, если оно не было закешировано раньше. Это небезопасно, если ненадежные источники могут записывать в переменные процесса сериализованные значения: при десериализации такие значения могут выполнить вредоносный код. Подробнее см. рекомендации по безопасности.

Формат сериализации по умолчанию

Движок можно настроить так, чтобы все объекты без явно заданного формата данных сохранялись в базе данных как XML. Для этого в конфигурации процессного движка есть свойство defaultSerializationFormat: установите его в application/xml. Тогда вызов runtimeService.setVariable(processInstance.getId(), "customer", new Customer()) сразу сериализует объект customer в XML без явного указания формата.

Документация создана на основе Camunda 7 Docs (Camunda, docs.camunda.org) и распространяется по лицензии Creative Commons Attribution-ShareAlike 3.0 Unported.