8 сентября 2010 г.



Динамические выражения в Eclipse

Задача: разработать удобный механизм, позволяющий вводить и изменять выражения на специальном языке и получать результат их выполнения в любом месте приложения. Выражения вводятся, изменяются и вычисляются «на лету» без компиляции и остановок приложения.

Данная задача, безусловно, может очень часто встречаться в бизнес-приложениях:
  • расчет показателей на основе часто меняющихся данных (котировки акций, коэффициенты ставок и т.д.)
  • разнообразные преобразования данных из одного представления в другое
  • расчет параметров на основе введенных пользователем данных
  • и т.п.
К примеру, на моем текущем проекте динамические выражения используются для расчета цены заказа на основе выбранных пользователем продуктов и их параметров, автоматическая коррекция заказа, преобразование данных заказа для записи в файл Excel. Есть предчувствие, что это еще далеко не все и механизм будет использоваться в будущем для решения других проблем.

Итак, расскажу как это устроено и работает у нас.
Механизм построен на основе Eclipse проекта BIRT (кто не знает что это, см. ссылки в конце статьи). Выражения представляют собой сценарии на языке JavaScript, т.е. имеется поддержка большинства функций и возможностей данного языка. Кроме того, стандартные функции JavaScript можно дополнить своими собственными и это немаловажно.

Ввод и редактирование выражений
Выражения можно вводить прямо в приложении используя специальный редактор из платформы BIRT (см. Рисунок 1).

Рисунок 1. Редактор выражений

Как видно, ребята из проектной группы BIRT постарались облегчить ввод сценариев на JavaScript для непрофессионалов. Есть три основных категории функций, которые выполняют функцию справочника и позволяют добавлять функции в пару кликов мышкой. Если бы добавить сюда автоматическое форматирование кода и средства навигации - цены бы не было этому редактору. А так - на больших выражениях придется помучиться, неудобно.

Как хранить введенные таким образом выражения - решать вам. Важно только то, что на исполнении они должны представлять из себя строку (java.lang.String).

Собственные функции
Как уже писалось выше есть возможность определять собственные функции для использования внутри выражений. Они будут добавляться в категорию «Функции BIRT». Сделать это проще простого.

Для начала нужно расширить extension-point org.eclipse.birt.core.ScriptFunctionService и описать новую функцию в plugin.xml:

  1.   <extension point="org.eclipse.birt.core.ScriptFunctionService"
  2.     id="extension_id"
  3.     name="Extension_Name">
  4.     <Category
  5.       desc="category description"
  6.       factoryclass="foo.bar.TestFunctionFactory"
  7.       name="CategoryName">
  8.       <Function
  9.         desc="function description"
  10.         name="myBirtFunction">
  11.         <DataType value="Object">
  12.         DataType>
  13.         <Argument name="param1">
  14.           <DataType value="String">
  15.           DataType>
  16.         Argument>
  17.         <Argument name="param2">
  18.           <DataType value="Integer">
  19.           DataType>
  20.         Argument>
  21.         Function>
  22.       Category>
  23.   extension>
* This source code was highlighted with Source Code Highlighter.

Наша новая функция называется myBirtFunction и расположена в категории CategoryName. Данная категория появится в редакторе выражений как подкатегория «Функций BIRT». Как видим, принимает она два параметра: строку и число; и возвращает тип Object (т.е. что угодно). Вообще говоря, данное описание типа параметров и возвращаемого значения никак не влияет на интерпретацию вашей функции в BIRT. Т.е. вы можете запросто передать  и возвратить параметры других типов. Более того, вы даже можете передать другое число параметров. Описание в XML используется для представления вашей функции в справочнике редактора выражений. Конечно, желательно чтобы описание функции совпадало с ее реальным поведением :)

Итак, наша функция уже появилась в редакторе выражений, но она, конечно же, ничего не делает. Опишем ее внутренности.

Можно заметить, что кроме описания функции в extension задается атрибут factoryclass (строка 6). Он указывает на реализацию интерфейса org.eclipse.birt.core.script.functionservice.IScriptFunctionFactory и его единственная задача - создать объект, который и будет представлять собой реализацию нашей новой функции.

Этот объект, в свою очередь, является реализацией интерфейса org.eclipse.birt.core.script.functionservice.IScriptFunctionExecutor с единственным методом Object execute(Object[] args). Глядя на параметры этого метода становится понятно, почему описание функции в plugin.xml никак не влияет на реальный набор и тип параметров и возвращаемых значений. Таким образом, вся ответственность за их соблюдение ложится на программиста.

Простейшие примеры реализации:

  1. public class SampleFunctionFactory implements IScriptFunctionFactory{
  2.  
  3.   @Override
  4.   public IScriptFunctionExecutor getFunctionExecutor(String functionName)
  5.       throws BirtException {
  6.     
  7.     if ("myBirtFunction".equals(functionName)){
  8.       
  9.       return new MyBirtFunctionExecutor();
  10.       
  11.     }
  12.     
  13.     return null;
  14.   }
  15.   
  16. }
* This source code was highlighted with Source Code Highlighter.

  1.   public class MyBirtFunctionExecutor implements IScriptFunctionExecutor{
  2.  
  3.     @Override
  4.     public Object execute(Object[] args) throws BirtException {
  5.       
  6.       if (args.length == 2
  7.           && args[0] instanceof String
  8.           && args[1] instanceof Integer){
  9.         
  10.         return "Hello, world!";
  11.         
  12.       }
  13.       
  14.       return null;
  15.     }
  16.     
  17.   }
* This source code was highlighted with Source Code Highlighter.

Итак, наша функция готова поприветствовать весь мир. Осталось ее вызвать и интерпретировать.

Выполнение выражений
Без лишних предисловий приведу код для выполнения выражений. Он довольно компактный:

  1.   ExecutionContext context = new ExecutionContext();
  2.   try {
  3.     IReportContext reportContext = new ReportContextImpl(context);
  4.     context.setReportContext(reportContext);
  5.     context.getScriptContext().registerBean("context"someObject);
  6.     // hack with classloader
  7.     context.setApplicationClassLoader(new ClassLoaderExtension(getClassLoader()));
  8.     return context.evaluate(expression);
  9.  
  10.   } catch (BirtException e) {
  11.  
  12.   }finally{
  13.     context.unregisterBean("context");
  14.   }
* This source code was highlighted with Source Code Highlighter.

Теперь о его содержании.

ExecutionContext - контекст выполнения выражения, экземпляр класса org.eclipse.birt.report.engine.executor.ExecutionContext. Именно он и выполняет (execute) выражение  в строковом виде (expression) на строке 8. Интерпретация происходит с помощью движка Rhino от Mozilla. Кроме того, обратите внимание на небольшой хак с classloader'ом (строка 7). Кроме того, нашему контексту выполнения нужен IReportContext. Никаких проблем, нужен - значит нужен (строки 3-4).

Отдельно обратите внимание на строки 5 и 13. Мы передаем контексту исполнения дополнительный контекст, который является нашей собственной реализацией и предназначен для возможности передачи объекта внутрь собственных функций. В этом случае при вызове функций в редакторе выражений нужно передавать параметр с именем context (как зарегистрировано) и конечно же желательно его описать в plugin.xml.

Вот, пожалуй, и все. Исполняем выражение, которое вызывает нашу функцию, и получаем результат - «Hello, world!»

Заключение
По моему мнению BIRT дает довольно удобное средство для поддержки динамических выражений. Всю полностью платформу BIRT использовать при этом необязательно. Она довольна большая и тяжелая. По моим наблюдениям, достаточно использовать следующие ее части:


  • org.eclipse.birt.core
  • org.eclipse.birt.data
  • org.eclipse.birt.report.engine
  • org.eclipse.birt.report.designer.core;
  • org.eclipse.birt.report.designer.ui;
  • org.eclipse.birt.report.model;
  • org.eclipse.birt.report.data.adapter
  • org.eclipse.birt.report.model.adapter
  • org.mozilla.rhino


Все приведенные примеры работают на версии BIRT 2.3.1 (версия плагина с rhino1.6.7.r22x). Сейчас самая новая версия 2.6. Возможно, в ней будут некоторые отличия.

Удачных разработок!


Полезные ссылки:






Понравилось сообщение - подпишитесь на блог Подписка на блогFollow grodnosoft on Twitter




Читайте также:


Комментов: 0

Отправить комментарий