Для использования библиотеки необходимо ее подключить в проект. Способ подключения зависит от вида проекта. Например, для maven проекта нужно добавить зависимость
<dependency> <groupId>ru.funsys.avalanche</groupId> <artifactId>avalanche</artifactId> <version>${avalanche.version}</version> </dependency>
где ${avalanche.version} - номер версии библиотеки, например:
<properties> <avalanche.version>1.3.2</avalanche.version> </properties>
Помимо добавления зависимости возможно потребуется добавить maven репозиторий, в котором опубликованы библиотеки "Avalanche - application framework for Java". Это можно сделать несколькими способами, например в файл pom.xml проекта или в файл settings.xml локального maven репозитория.
Конфигурационный файл приложения определяет, что именно выполняется на этом узле системы. На каждом узле системы должен быть собственный файл конфигурации. В частном случае разные узлы системы могут иметь одинаковые файлы конфигурации, например дублирующие узлы.
Конфигурационный файл приложения передается приложению в параметре запуска.
Конфигурационный файл представляет из себя XML структуру с базовыми элементами конфигурации (тегами):
Пример конфигурационного файла (определяются только требуемые в приложении элементы)
<?xml version="1.0" encoding="UTF-8"?> <avalanche name="Demo Application"> <locale name="ru.app.demo.web.LocaleStrings" /> <interface name="rmi-interface" uri="rmi://localhost:23000/rmi-connector" /> <interface name="http-interface" uri="http://localhost:8080/demo/connector/http-connector" /> <function class="ru.funsys.demo.avalanche.DemoFunction" name="info-function" description="Сведения об узле системы" /> <application class="ru.funsys.demo.avalanche.DemoApplication" name="DemoApp" > <adapter class="ru.funsys.demo.avalanche.DemoAdapter" name="info" uri="info-function" /> </application> <application class="ru.funsys.demo.avalanche.DemoApplication" name="RMIApp" > <adapter class="ru.funsys.demo.avalanche.DemoAdapter" name="info" uri="rmi-interface/info" /> </application> <application class="ru.funsys.demo.avalanche.DemoApplication" name="HTTPApp" > <adapter class="ru.funsys.demo.avalanche.DemoAdapter" name="info" uri="http-interface/info" /> </application> <connector class="RMIConnector" name="rmi-connector" port="23000" > <publish name="info" function="info-function" /> </connector> <connector class="HTTPConnector" name="http-connector" > <publish name="info" function="info-function" /> </connector> </avalanche>
Во всех тегах элементов конфигурации приложения есть зарезервированные имена атрибутов
Для определение собственного атрибута конфигурации достаточно определить одноименное поле класса. Преобразование строкового значения атрибута из файла конфигурации в тип поля произойдет автоматически для простых типов либо с использованием конструктора класса со строковым параметром. Для удобства восприятия программного кода и чтобы можно было отличать поля атрибутов конфигурации от других полей класса рекомендуется помечать поля атрибутов аннотацией CfgAttribute.
Пример фрагмента конфигурации с собственным атрибутом timeout
<application class="ru.domain.MyApplication" name="MyApp" timeout="10000" > ... </application>
Пример фрагмента кода класса с собственным атрибутом timeout
... public class ru.domain.MyApplication extends Application { @CfgAttribute(description="Таймаут в мс., значение по умолчанию 30000 мс") private long timeout = 30000L; ... }
Если же требуется реализация более сложной логики при установке значения, например проверки допустимого диапазона, то требуется реализовать метод set. Правило формирования имени метода - первая буква имени атрибута преобразуется в верхний регистр и добавляется префикс set. Т.е. для атрибута с именем timeout требуется определить метод setTimeout,
Пример определения метода set для атрибута конфигурации timeout
... public class ru.domain.MyApplication extends Application { @CfgAttribute(description="Таймаут в мс., значение по умолчанию 30000 мс") private long timeout = 30000L; ... /** * проверка значения атрибута конфигурации на допустимый диапазон */ public void setTimeout(long timeout) { if (timeout < 10000L || timeout > 60000L) { // сообщение в файле локализации с кодом CODE001W // - Ошибка установки значения атрибута {0}. Значение вне допустимого диапазона {1} - {2}. Значение {3} проигнорировано. warn("CODE001W", "timeout", 10000, 60000, timeout); } else { this.timeout = timeout; } } ... }
Установка значения с использованием метода set имеет более высокий приоритет, чем установка значения поля класса.
При реализации логики приложения часто требуется определить дочерние элементы конфигурации для собственных программных элементов системы. Для этого программного элемента придумывается имя тега и реализуется метод add в классе родительского элемента конфигурации для множественного добавления элементов или метод set для установки значения поля (одиночное значение в конфигурации).
Метод add принимает два значения, имя элемента (значение атрибута name) и созданный экземпляр класса. Метод add должен возвращать значение true, если дочерний элемент успешно добавлен или false, если обнаружена какая либо ошибка, например дублирование значения атрибута name в нескольких дочерних тегах. Пример. Допустим, что в классе MyApplication нужно реализовать добавление множества экземпляров класса Query. Что именно делает этот класс и для чего он предназначен для примера не важно.
... public class Query { @CfgAttribute(description="Имя запроса") private String name; // другие атрибуты класса ... // другие поля класса ... // методы класса ... }
Присвоим этому классу тег <query> в конфигурационном файле приложения. Соответственно нужно определить метод addQuery в классе MyApplication
... public class ru.domain.MyApplication extends Application { // атрибуты класса ... // поле класса (не атрибут конфигурации) HashMap<String, Query> queries = new HashMap<String, Query>(); // другие поля класса ... // методы класса ... /** * Добавить экземпляр класса Query. В методе проверяется наличие элемента с указанным именем. * * @param name имя экзепляра класса * @param query экземпляр класса запроса * * @return true, если экземпляр упешно добавлен, иначе false. */ public boolean addQuery(String name, Query query) { Query item = queries.get(name); boolean result = item == null; if (result) { queries.put(name, query); } else { // сообщение в файле локализации с кодом CODE002E // - Дублирование имени {0} элемента конфигурации в теге {1}. error("CODE002E", name, "query"); // это сообщение можно и не выводить, если метод возвратит false, то в лог попадет предупреждение об ошибке. } return result; } }
Пример фрагмента файла конфигурации с дочерними элементами Query, обязательно требуется определить атрибут class в теге <query>, в значении которого указывается полное имя класса Query.
... <application class="ru.domain.MyApplication" name="MyApp" timeout="10000" > <query name="query1" class="ru.domain.Query" ...другие...атрибуты... /> <query name="query2" class="ru.domain.Query" ...другие...атрибуты... /> ... </application> ...
Если в классе Query требуется определить собственные дочерние элементы, то в нем нужно определить собственный методы add для каждого типа элемента. Глубина вложенности дочерних элементов ни чем не ограничена.
Пример конфигурационного файла демонстрационного кластерного приложения
<?xml version="1.0" encoding="UTF-8"?> <avalanche name="cluster 0.0.1-SNAPSHOT" buildInfo="ru.app.demo.cluster.BuildInfo.properties" version="0.0.1-SNAPSHOT" description="Демонстрационное кластерное WEB приложение"> <!-- Определение файлов локализации приложения --> <locale name="ru.funsys.avalanche.cluster.LocaleStrings" /> <locale name="ru.funsys.avalanche.rs.LocaleStrings" /> <locale name="ru.app.demo.cluster.LocaleStrings" /> <!-- Доступ к БД --> <!-- Параметры: --> <!-- class - имя класса элемента конфигурации --> <!-- name - имя, под которым этот элемент конфигурации будет известен другим --> <!-- локальным элементам конфигурации приложения --> <!-- logger - имя логгера (файла журнала), это имя должно присутствовать в --> <!-- конфигурации log4j. По умолчанию: ROOT логгер --> <!-- resource - полное JNDI имя источника данных, которое определяется в --> <!-- конфигурации WEB сервера --> <!-- description - описание назначения элемента конфигурации --> <!-- function - роль функции, true - может быть использована в других секциях --> <!-- <application> --> <application class="ru.funsys.avalanche.sql.Database" name="database" description="Функция доступа к БД" resource="java:/comp/env/jdbc/demo" function="true"> </application> <!-- Кластер: Понятие кластер объединяет множество хостов. --> <!-- Узел кластера: Узел кластера создается автоматически. Узлу кластера присваивается имя --> <!-- хоста --> <!-- Параметры кластера: --> <!-- class - имя класса элемента конфигурации --> <!-- name - имя кластера под которым этот элемент конфигурации будет --> <!-- известен другим локальным элементам конфигурации. Разные узлы --> <!-- кластера должны иметь одно и то же имя для их автоматического --> <!-- объединения в один кластер. --> <!-- logger - имя логгера (файла журнала), это имя должно присутствовать в --> <!-- конфигурации log4j. По умолчанию: ROOT логгер --> <!-- lockbackMode - эта опция используется сетевым кодом платформы как подсказка для --> <!-- установки того, будут ли многоадресные данные передаваться --> <!-- обратно в локальный сокет. По умолчанию: false --> <!-- networkInterface - локальный адрес сетевого интерфейса, который будет использоваться --> <!-- для открытия UDP сокета для приема данных многоадресной рассылки. --> <!-- Если в узле (хосте) более 1 сетевого интерфейса или определено --> <!-- более 1 IP адреса на одном интерфейсе, то требуется обязательно --> <!-- определить этот параметр. Обязательно нужно явно указать значение --> <!-- MTU у сетевого интерфейса (отключить режим auto) --> <!-- multicast - IP адрес многоадресной рассылки. По умолчанию: 239.52.1.1 --> <!-- multicastPort - UDP порт многоадресной рассылки. По умолчанию: 8081 --> <!-- size - размер буфера приема пакетов по UDP сокету. По умолчанию: 4096 --> <!-- TTL - устанавливает время жизни многоадресных пакетов (число "прыжков"),--> <!-- отправляемых через MulticastSocket. Значение TTL должно --> <!-- находиться в диапазоне 0 <= TTL <=255. По умолчанию: 1 --> <!-- nodeUp - запускать или нет узел кластера при старте приложения. Если --> <!-- указанно значение false, то узел кластера не запускается. --> <!-- В дальнейшем узел кластера можно запустить средствами управления --> <!-- кластера. По умолчанию: true --> <!-- controlTimeout - таймаут контроля состояния узлов кластера в мс. --> <!-- По умолчанию: 30000 --> <!-- timeout - таймаут при запуске и остановке узла кластера в мс. Необходим для --> <!-- ожидания завершения инициализации или освобождения используемых --> <!-- в кластере других элементов конфигурации. По умолчанию: 2000 --> <!-- description - описание назначения элемента конфигурации --> <!-- --> <!-- Адаптеры: определяют доступные локальные и удаленные элементы конфигурации ресурсам --> <!-- кластера. В ресурсе кластера должно использоваться имя поля класса, --> <!-- аналогичное имени адаптера --> <!-- database - адаптер доступа к БД, используется при выполнении тестового запроса --> <!-- --> <!-- Группы: группа объединяет в себе множество ресурсов кластера. В кластере может быть --> <!-- определено множество групп --> <application class="ru.funsys.avalanche.cluster.Cluster" name="cluster" lockbackMode="false" networkInterface="192.168.0.100" description="Кластер планировщика заданий ВРП"> <!-- Параметры адаптера: --> <!-- class - имя класса адаптера (элемента конфигурации) --> <!-- name - имя адаптера, должно совпадать с именами полей в классах --> <!-- реализации ресурсов --> <!-- uri - имя локального или удаленного элемента конфигурации. Адресация --> <!-- локального элемента указывается именем локального элемента. --> <!-- Адресация удаленного элемента конфигурации указывается --> <!-- составным именем "имя локального интерфейса"/"имя удаленного --> <!-- элемента publish" --> <!-- description - описание назначения элемента конфигурации --> <adapter class="ru.funsys.avalanche.sql.Adapter" name="database" uri="database" description="Адаптер доступа у БД" /> <!-- Группа ресурсов, которые выполняются только на одном узле --> <!-- --> <!-- Параметры группы: --> <!-- class - имя класса группы, если не указана, то используется класс --> <!-- по умолчанию --> <!-- name - имя группы, на всех узлах должно быть указано одно и то же --> <!-- имя. --> <!-- single - признак запуска группы только на одном узле кластера. --> <!-- По умолчанию: false --> <!-- description - описание назначения элемента конфигурации --> <!-- --> <!-- Ресурсы группы: множество ресурсов группы. На всех узлах в одной и той же --> <!-- группе должны быть определены одни и те же ресурсы. У каждого --> <!-- ресурса группы могут быть свои индивидуальные параметры --> <!-- конфигурации --> <group name="single" single="true" description="Группа ресурсов кластера, выполняющихся только на одном узле"> <!-- Параметры ресурса кластера зависят от реализации. --> <!-- В демонстрационном ресурсе кластера какие либо параметры не определены --> <!-- class - имя класса планировщика заданий --> <!-- name - имя планировщика заданий, на всех узлах должно быть --> <!-- description - описание назначения элемента конфигурации --> <!-- timeout - таймаут выполнения пока кластерного ресурса в мс. --> <!-- По умолчанию: 2000 --> <!-- sql - SQL запрос демонстрации работы с БД --> <resource name="singleResource" sql="select 'test' as test" class="ru.app.demo.cluster.app.DemoResource" description="Демонстрационный ресурс кластера"> </resource> </group> <!-- Группа ресурсов, которые выполняются на всех узлах --> <!-- Описание параметров см. выше --> <group name="all" description="Группа ресурсов кластера, выполняющихся только на каждом узле"> <!-- Ресурс, который выполняются на всех узлах --> <!-- Описание параметров см. выше --> <resource name="allNodes" timeout="30000" sql="select 'test' as test" class="ru.app.demo.cluster.app.DemoResource" description="Демонстрационный ресурс кластера"> </resource> </group> </application> <!-- Демонстрация выполнения кластерного ресурса вне кластера --> <!-- Описание параметров см. выше --> <application name="outCluster" timeout="40000" sql="select 'test' as test" class="ru.app.demo.cluster.app.DemoResource" description="Демонстрационный ресурс, выполняющийся вне кластера"> <adapter class="ru.funsys.avalanche.sql.Adapter" name="database" uri="database" description="Адаптер доступа у БД" /> </application> </avalanche>
Система ведения журналов (лог файлов) приложения основана на библиотеке log4j. Конфигурационный файл системы ведения журналов приложения передается параметром при запуске приложения.
В конфигурации приложения в зависимости от цели и назначения может быть определенно несколько файлов журналов. В конфигурации элемента системы требуемый для вывода сообщений файл журнала указывается атрибутом logger, в значении которого указывается имя логгера из файла конфигурации log4j. Если атрибут logger не указан, то используется значение родительского элемента. По умолчанию используется root логгер.
При необходимости, разные экземпляры одного и того же класса могут выводить сообщения в разные файлы журналов.
Пример конфигурационного файла системы ведения журналов приложения
<?xml version="1.0" encoding="UTF-8"?> <Configuration status="DEBUG"> <Properties> <Property name="base">${sys:catalina.base}/logs</Property> <Property name="app">cluster</Property> </Properties> <Appenders> <RollingFile name="${app}" fileName="${base}/${app}.log" filePattern="${base}/arcs/${symbol_dollar}{app}-%d{yyyy-MM-dd-HH}-%i.log.gz"> <PatternLayout pattern="%d %p %c{1.} [%t] %m%n"/> <Policies> <!-- TimeBasedTriggeringPolicy / --> <SizeBasedTriggeringPolicy size="10MB" /> </Policies> <DefaultRolloverStrategy max="100"> <Delete basePath="${base}/arcs" maxDepth="2"> <IfFileName glob="${app}-*.log.gz" /> <IfLastModified age="20d" /> </Delete> </DefaultRolloverStrategy> </RollingFile> </Appenders> <Loggers> <root level="info" additivity="false"> <AppenderRef ref="${app}" /> </root> </Loggers> </Configuration>