Статьи

JBoss Fuse — Превратите свою статическую конфигурацию в динамические шаблоны с MVEL

Недавно я заново открыл функциональность JBoss Fuse, о которой я забыл, и подумал, что другие люди могут воспользоваться этим напоминанием .

Этот пост будет посвящен JBoss Fuse и Fabric8, но может заинтересовать и всех тех разработчиков, которые ищут минимально инвазивные способы добавления некоторой степени динамической поддержки в свои файлы статической конфигурации .

Идея динамической конфигурации в OSGi и в Fabric8

Фреймворк OSGi чаще всего запоминается за поведение при загрузке классов. Но отчасти это также определяет другие концепции и функциональные возможности, которые должна реализовывать инфраструктура. Одним из них является ConfigAdmin .

ConfigAdmin — это служба для определения внешнего набора файлов свойств, которые логически связаны с вашими единицами развертывания.

Жизненный цикл этих внешних файлов свойств связан с жизненным циклом пакета OSGi: если вы измените внешний файл свойств, ваш пакет будет уведомлен . В зависимости от того, как вы закодировали свой пакет, вы можете решить отреагировать на уведомление, и программно или с помощью различных вспомогательных сред, таких как Blueprint, вы можете вызвать код, который использует новую конфигурацию.

Этот механизм удобен и эффективен, и все разработчики, использующие OSGi, знакомы с ним.

Fabric8 основывается на идее ConfigAdmin и расширяет ее .

С его возможностями обеспечения, Fabric8 определяет концепцию профиля, которая включает в себя единицы развертывания и конфигурацию. Он добавляет некоторый уровень функциональности поверх простого OSGi и позволяет управлять любым типом модуля развертывания, не только пакетами OSGi, а также любым типом конфигурации или статического файла.

Если вы посмотрите официальную документацию, вы найдете список «расширений», которые предлагает слой Fabric8, и узнаете, что они разделены в основном на 2 группы: обработчики URL-адресов и преобразователи свойств .

Я предлагаю всем, кто интересуется этой технологией, копаться в документации; но чтобы предложить краткое резюме и короткий пример, представьте, что ваши профили Fabric имеют возможность разрешать некоторые значения во время выполнения с использованием определенных заполнителей. ех.

1
2
3
4
5
# sample url handler usage, ResourceName is a filename relative to the namespace of the containing Profile:
profile:ResourceName
 
# sample property handler, the value is read at deploy time, from the Apache Zookeeper distributed registry that is published when you run JBoss Fuse
${zk:/fabric/registry/containers/config/ContainerName/Property}

Из коробки доступно несколько обработчиков, охватывающих то, что разработчики считают наиболее распространенными вариантами использования: Zookeeper, Profiles, Blueprint, Spring, System Properties, Managed Ports и т. Д.

Также вы можете подумать о расширении механизма, определяющего ваше собственное расширение: например, вы можете захотеть реагировать на показатели производительности, которые вы храните в какой-либо системе, вы можете написать расширение с его синтаксическим соглашением, которое вводит значения, полученные из вашей системы.

Предел всей этой мощи: статические файлы конфигурации

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

Это означает, что эти функциональные возможности доступны, если вам нужно управлять профилями Fabric, свойствами OSGi или другими конкретными технологиями, которые взаимодействуют с ними, такими как Camel, но они не включены ни для чего, кроме Fabric-Unaware .

Представьте, что у вас есть собственный код, который читает файл конфигурации .xml . И представьте, что ваш код не ссылается на какой-либо объект или службу Fabric.

Ваш код будет обрабатывать этот .xml файл как есть. Не будет никакой волшебной замены токенов или путей, потому что, несмотря на то, что вы работаете внутри Fabric, вы НЕ используете напрямую поддерживаемую технологию и НЕ уведомляете Fabric о том, что вам могут потребоваться ее услуги.

Для решения этой проблемы у вас есть 3 варианта :

  1. Вы пишете расширение для Fabric для обработки и распознавания ваших статических ресурсов и делегируете динамическую замену коду инфраструктуры.
  2. Вы изменяете код, содержащийся в вашем модуле развертывания , и вместо непосредственного использования статических ресурсов вы просите службы Fabric интерполировать их для вас.
  3. * Вы используете обработчик mvel: url (и не трогайте любой другой код!)

Что такое MVEL?

MVEL на самом деле является языком программирования : https://en.wikipedia.org/wiki/MVEL . В частности, это также язык сценариев, который вы можете запускать напрямую из исходного кода, пропуская этап компиляции.

Он на самом деле имеет несколько специфических характеристик, которые могут сделать его интересным для встраивания в другое приложение и использования для определения нового поведения во время выполнения. По всем этим причинам, например, это также один из поддерживаемых языков для проекта JBoss Drools, который работает с бизнес-правилами, которые вы, возможно, захотите определить или изменить во время выполнения.

Почему это может быть полезно для нас? Главным образом по 2 причинам:

  1. это хорошо работает как шаблонный язык
  2. У Fabric8 уже есть обработчик mvel mvel: url, который неявно действует как обработчик ресурсов!

Язык шаблонов

Языки шаблонов — это семейство языков (часто это доменные языки), где вы можете чередовать статическую часть текста, которая читается как есть, и динамические инструкции, которые будут обрабатываться во время синтаксического анализа . Я, вероятно, более сложным образом говорю ту же идею, которую я уже представил выше: в вашем тексте могут быть токены, которые будут переведены в соответствии с определенным соглашением.

Это похоже на возможности, предоставляемые обработчиками, которые мы представили выше. С важным отличием: хотя это были контекстно- зависимые обработчики, MVEL — это технология общего назначения. Поэтому не ожидайте, что он будет знать что-либо о профилях Zookeeper или Fabric, но ожидайте, что он сможет поддерживать общие концепции языка программирования, такие как циклы, вызов кода, отражение и так далее.

Ткань это поддерживает!

Ссылку на поддержку в Fabric можно найти здесь: http://fabric8.io/gitbook/urlHandlers.html

Но позвольте мне добавить фрагмент исходного кода, который реализует эту функциональность, так как это та часть, где вы могли бы найти этот подход интересным даже вне контекста JBoss Fuse: https://github.com/fabric8io/fabric8/blob/1 .x / ткань / ткань-ядро / SRC / главная / Java / IO / fabric8 / сервис / MvelUrlHandler.java # L115-L126

01
02
03
04
05
06
07
08
09
10
11
12
public InputStream getInputStream() throws IOException {
  assertValid();
  String path = url.getPath();
  URL url = new URL(path);
  CompiledTemplate compiledTemplate = TemplateCompiler.compileTemplate(url.openStream());
  Map<String, Object> data = new HashMap<String, Object>();
  Profile overlayProfile = fabricService.get().getCurrentContainer().getOverlayProfile();
  data.put(“profile”, Profiles.getEffectiveProfile(fabricService.get(), overlayProfile));
  data.put(“runtime”, runtimeProperties.get());
  String content = TemplateRuntime.execute(compiledTemplate, data).toString();
  return new ByteArrayInputStream(content.getBytes());
}

Что тут происходит?

Во-первых, поскольку это не показано во фрагменте, помните, что это обработчик URL. Это означает, что поведение get запускается для файлов, на которые ссылаются через определенный URI. В данном случае это mvel: Например, допустимый путь может быть mvel:jetty.xml .

Другая интересная и относительно простая вещь, которую стоит заметить, это взаимодействие с интерпретатором MVEL. Как и большинство шаблонных технологий, даже самые простые, которые вы можете реализовать самостоятельно, вы обычно имеете:

  • движок / компилятор, вот это TemplateCompiler
  • переменная, которая содержит ваш шаблон, здесь это url
  • переменная, которая представляет ваш контекст, то есть набор переменных, которые вы хотите представить движку, здесь data

Сложите их все вместе, попросив движок выполнить свою работу, здесь с TemplateRuntime.execute(...) и в результате вы получите статическую строку. Уже не шаблонные инструкции, но вся логика, которую определял ваш шаблон, была применена и в конечном итоге дополнена некоторыми дополнительными входными значениями, взятыми из контекста.

Пример

Я надеюсь, что мое объяснение было достаточно простым, но, вероятно, пример — лучший способ выразить концепцию.

Давайте используем jetty.xml , содержащийся в JBoss Fuse default.profile , который является статическим ресурсом, который JBoss Fuse не обрабатывает как какой-либо специальный файл , поэтому он не предлагает никаких функций замены для него.

Я покажу здесь оба аспекта интеграции MVEL: чтение некоторого значения из контекстных переменных и покажу, как можно использовать программную логику (просто сумму 2 целых здесь):

1
<Property name="jetty.port" default="@{  Integer.valueOf( profile.configurations['org.ops4j.pax.web']['org.osgi.service.http.port'] ) + 10  }"/>

Мы изменяем значение по умолчанию для порта Jetty, беря его начальное значение из контекстной переменной «profile», которая является объектом с поддержкой Fabric и имеет доступ к остальной части конфигурации:

profile.configurations['org.ops4j.pax.web']['org.osgi.service.http.port']

мы явно приводим его из String к Integer:

Integer.valueOf( ... )

и мы добавляем статическое значение 10 к возвращаемому значению:

.. + 10

Давайте сохраним файл, остановим наш экземпляр fuse . Перезапустите его и заново создайте тестовую Fabric:

01
02
03
04
05
06
07
08
09
10
# in Fuse CLI shell
shutdown -f
 
# in bash shell
rm -rf data instances
 
bin/fuse
 
# in Fuse CLI shell
fabric:create --wait-for-provisioning

Просто подожди и проверь логи и … О-о-о. Ошибка! Что творится?

Это ошибка:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
2015-10-05 12:00:10,005 | ERROR | pool-7-thread-1  | Activator                        | 102 - org.ops4j.pax.web.pax-web-runtime - 3.2.5 | Unable to start pax web server: Exception while starting Jetty
java.lang.RuntimeException: Exception while starting Jetty
at org.ops4j.pax.web.service.jetty.internal.JettyServerImpl.start(JettyServerImpl.java:143)[103:org.ops4j.pax.web.pax-web-jetty:3.2.5]
Caused by: java.lang.reflect.InvocationTargetException
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)[:1.7.0_76]
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57)[:1.7.0_76]
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)[:1.7.0_76]
at java.lang.reflect.Constructor.newInstance(Constructor.java:526)[:1.7.0_76]
at org.eclipse.jetty.xml.XmlConfiguration$JettyXmlConfiguration.set(XmlConfiguration.java:572)[96:org.eclipse.jetty.aggregate.jetty-all-server:8.1.17.v20150415]
at org.eclipse.jetty.xml.XmlConfiguration$JettyXmlConfiguration.configure(XmlConfiguration.java:396)[96:org.eclipse.jetty.aggregate.jetty-all-server:8.1.17.v20150415]
Caused by: java.lang.NumberFormatException: For input string: “@{profile.configurations[’org.ops4j.pax.web'][‘org.osgi.service.http.port’] + 1}”
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)[:1.7.0_76]
at java.lang.Integer.parseInt(Integer.java:492)[:1.7.0_76]
at java.lang.Integer.<init>(Integer.java:677)[:1.7.0_76]
… 29 more

Если вы заметили, в сообщении об ошибке говорится, что наш шаблонный фрагмент не может быть преобразован в число .

Почему наш шаблонный фрагмент отображается в первую очередь? Движок шаблонов должен был выполнить свою часть работы и вернуть нам статическую строку без каких-либо ссылок на директивы шаблонов!

Я специально продемонстрировал вам эту ошибку, чтобы настаивать на концепции, которую я описал выше, но это может оказаться неосуществленным в первом случае.

Поддержка MVEL в Fabric, реализована в виде обработчика URL.

До сих пор мы только что изменили содержимое статического файла ресурсов, но мы не дали никаких указаний на Fabric, которые мы хотели бы обработать в этом файле как шаблон mvel.

Как это сделать?

Это просто вопрос использования правильного URI для ссылки на тот же файл.

Итак, измените файл default.profile/org.ops4j.pax.web.properties который является местом в профиле Fabric по умолчанию, где вы определяете, какой статический файл содержит конфигурацию Jetty:

1
2
# change it from org.ops4j.pax.web.config.url=profile:jetty.xml to
org.ops4j.pax.web.config.url=mvel:profile:jetty.xml

Теперь снова остановите экземпляр, удалите файлы конфигурации Fabric, заново создайте Fabric и обратите внимание, как правильно работает ваш экземпляр Jetty.

Мы можем проверить это следующим образом:

1
2
JBossFuse:karaf@root> config:list | grep org.osgi.service.http.port
   org.osgi.service.http.port = 8181

Хотя из вашего браузера вы можете убедиться, что Hawtio, веб-консоль JBoss Fuse, развернутая на верхней части Jetty, доступна для порта 8191 : http: // localhost: 8191 / hawtio