Как и было обещано во второй части эта статья посвящена исключительно практическим примерам, демонстрирующим особенности взаимодействия Caché + Java + Flex. Начнем с преобразования типов данных на протяжении всей связки. Мы не стали заострять внимание на преобразованиях таких типов данных, как Integer, Float, String, Boolean, поскольку данные типы идентичны в Caché и Java (а в ActionScript все числовые типы переходят в Number). Другое дело коллекции объектов. Их преобразование проходит достаточно нетривиально, поэтому остановимся на них подробнее.
Преобразование коллекций
"
Рассмотрим преобразования коллекций на протяжении всей связки Caché + Java + Flex на примере списка дисциплин в определенном цикле. В качестве примера коллекции рассмотрим отношения — вид связей между классами Caché, реализованные как свойства типа Relationship, с определенным видом поддержки целостности. Подробнее об отношениях в Caché.
Class IKIT.cCicl Extends %Persistent {
…
Relationship ListOfDisc As IKIT.cDiscipline [Cardinality = many, Inverse = Cicl];
…
}
В сгенерированной java-проекции (пакет «Java c-classes») данное свойство представлено объектом класса com.intersys.classes.RelationshipObject из библиотеки cachedb.jar.
public com.intersys.classes.RelationshipObject getListOfDisc() throws com.intersys.objects.CacheException {
com.intersys.cache.Dataholder dh = mInternal.getProperty("ListOfDisc", true);
com.intersys.cache.CacheObject cobj = dh.getCacheObject();
if (cobj == null) return null;
return (com.intersys.classes.RelationshipObject)(cobj.newJavaInstance());
}
Данный класс имеет стандартный метод asList, который принимает RelationshipObject и возвращает List, что значительно облегчает работу со списком в дальнейшем.
В m-classes используются листы (List listOfDisc), поэтому необходимо преобразовать каждый объект c-classes из RelationshipObject в соответствующий объект m-classes. Данное действие происходит в классе CacheTransform.
for (int i=0;i<cur.getListOfDisc().size();i++) {
res.getListOfDisc().
add(TranformDi((cDiscipline)cur.getListOfDisc().asList().get(i)));
}
// cur – объект класса cCicl, res – объект mCicl
Далее выполняется проецирование m-классов во flex модуль по заданным в POM-файле для GraniteDS параметрам. GraniteDS по умолчанию использует для проекции типа List (в Java) соответствующий тип ListCollectionView (в ActionScript). Например, так будет выглядеть проекция списка в сгенерированном AS-классе mCiclBase.
[Bindable]
public class mCiclBase implements IExternalizable {
…
protected var _listOfDisc:ListCollectionView;
…
public function set listOfDisc (value: ListCollectionView):void {
_listOfDisc = value;
}
public function get listOfDisc():ListCollectionView {
return _listOfDisc;
}
…
}
Для дальнейшего упрощения работы непосредственно в ActionScript использовался класс ArrayCollection, который является классом наследником ListCollectionView и реализует стандартный flex интерфейс Ilist (cur.listOfDisc = new ArrayCollection();), который в случае необходимости можно легко преобразовать as ListCollectionView.
Обратное преобразование проходит по той же схеме. Изменения, происходящие в AS-проекции через GraniteDS, также выполняются в соответствующих классах пакета «Java m-classes». Преобразование же объектов коллекции из java m- в java c- классы происходит в классе CacheTransform в функциях с префиксом Rev. В нашем случае – в функции RevTranformCi(mCicl cur,Integer act), которая принимает объект m-класса и действие (перезапись или создание), записывает объект в БД и возвращает объект c-класса. Преобразование листов происходит в данной функции следующим образом.
for (int i=0;i<cur.getListOfDisc().size();i++) {
if(cur.getListOfDisc().get(i).getId()!=null) {
res.getListOfDisc().add(RevTranformDi(cur.getListOfDisc().get(i),1));
}
if(cur.getListOfDisc().get(i).getId()==null) {
res.getListOfDisc().add(RevTranformDi(cur.getListOfDisc().get(i),2));
}
}
После этого вызывается стандартный метод _save у объекта c-classes, который сохраняет данные в базу Caché, при этом приводит тип List в соответствующий тип в Caché com.intersys.classes.RelationshipObject.
Теперь, когда с коллекциями все понятно, перейдём к примеру, который демонстрирует добавление и удаление объектов.
Пример добавления и удаления объекта
Рассмотрим работу с интерфейсом сервисов на примере добавления и удаления учебного плана. Первым создаётся объект «учебный план» в AS-проекции класса mCurriculum. Создание этого объекта выполняется на стороне клиента, затем он передаётся на сервер и сохраняется в БД. Для этого вызываем через объект реализующий интерфейс сервисов сервера метод addOneCurr.
userService.addOneCurr(cur, k);
где сur – объект класса-проекции mCurriculum, созданный во flex, а k – переменная отвечающая за выбор действия (добавление или редактирование).
Здесь необходимо помнить, что GraniteDS посылает запросы асинхронно и, если необходимо выполнить какое-либо действие строго после завершения функции, его нужно помещать непосредственно в функцию-обработчик результата. Например:
userService.addOneCurr
(
cur,
k,
function(e:TideResultEvent):void
{
Alert.show("Добавление завершено");
updateOneCur(curCurriculum.id);
},
function (e:TideFaultEvent):void
{
Alert.show(e.fault.faultDetail);
}
);
Данная функция выведет сообщение «Добавление завершено» строго после удачного завершения функции addOneCurr.
Ниже приведён код функции addOneCurr в классе-проекции на ActionScript интерфейса IUserService сгенерированной GraniteDS.
public function addOneCurr(arg0:mCurriculum, arg1:Number, resultHandler:Object = null, faultHandler:Function = null):void
{
if (faultHandler != null)
callProperty("addOneCurr", arg0, arg1, resultHandler, faultHandler);
else if (resultHandler is Function || resultHandler is ITideResponder)
callProperty("addOneCurr", arg0, arg1, resultHandler);
else if (resultHandler == null)
callProperty("addOneCurr", arg0, arg1);
else
throw new Error("Illegal argument to remote call (last argument should be
Function or ITideResponder): " + resultHandler);
}
Та же функция в исходном java классе UserService, который реализует интерфейс IUserService.
@Override
public Boolean addOneCurr(mCurriculum cur,Integer k){
objT.RevTranformCu(cur,k);
return true;
}
Эта функция вызывает метод RevTranformCu(mCurriculum cur, Integer act) класса CacheTransform, предназначенный для преобразования объекта класса mCurriculum в объект класса cCurriculum и его записи в БД. Кроме того, функция addOneCurr «каскадно» вызывает сохранение объектов, входящих в коллекции, обрабатываемого учебного плана.
///Трансформация УП
public cCurriculum RevTranformCu(mCurriculum cur,Integer act) {
try {
cCurriculum res=null;
if (act==1) {
System.out.println("//MAS: TRY EDIT Curriculum: " + act.toString() +
"nID:" + cur.getId().toString());
res=(cCurriculum) cCurriculum._open(dbconnection, new Id(cur.getId()));
}
else {
System.out.println("//MAS: TRY ADD Curriculum");
res=new cCurriculum(dbconnection);
}
res.setName(cur.getName());
//Коллекция циклов
if(res.getListOfCicl()!=null) {
res.getListOfCicl()._clear();
}
if(cur.getListOfCicl()!=null) {
for (int i=0;i<cur.getListOfCicl().size();i++) {
cur.getListOfCicl().get(i).setCurriculum(cur.getId());
if(cur.getListOfCicl().get(i).getId()!=null) {
res.getListOfCicl().add(RevTranformCi
(cur.getListOfCicl().get(i),1));
}
if(cur.getListOfCicl().get(i).getId()==null) {
res.getListOfCicl().add(RevTranformCi
(cur.getListOfCicl().get(i),2));
}
}
}
//Коллекция семестров
if(res.getListOfSemestr()!=null) {
res.getListOfSemestr()._clear();
}
if(cur.getListOfSemestr()!=null) {
for (int i=0;i<cur.getListOfSemestr().size();i++) {
cur.getListOfSemestr().get(i).setCurriculum(cur.getId());
if(cur.getListOfSemestr().get(i).getId()!=null) {
res.getListOfSemestr().add(RevTranformSe
(cur.getListOfSemestr().get(i),1));
}
if(cur.getListOfSemestr().get(i).getId()==null) {
res.getListOfSemestr().add(RevTranformSe
(cur.getListOfSemestr().get(i),2));
}
}
}
//Список логов
if(res.getListOfLogs()!=null) {
res.getListOfLogs()._clear();
}
if(cur.getListOfLogs()!=null) {
for (int i=0;i<cur.getListOfLogs().size();i++) {
cur.getListOfLogs().get(i).setCurriculum(cur.getId());
if(cur.getListOfLogs().get(i).getId()!=null) {
res.getListOfLogs().add(RevTranformLo
(cur.getListOfLogs().get(i),1));
}
if(cur.getListOfLogs().get(i).getId()==null) {
res.getListOfLogs().add(RevTranformLo
(cur.getListOfLogs().get(i),2));
}
}
}
res._save();
return res;
}
catch (CacheException e) {
e.printStackTrace();
return null;
}
}
Как видно из примера, метод последовательно выполняет сохранение всех объектов из списков семестров, циклов и логов соответствующими методами RevTranformSe, RevTranformCi, RevTranformLo. При этом принцип остаётся прежним: если id отсутствует, то создаётся новый объект, иначе редактируется существующий.
Аналогично протекает и удаление учебного плана. Для удаления достаточно получить id объекта класса-проекции mCurriculum и передать его на сервер. За это отвечает функция delOneCurr в AS проекции функции интерфейса IUserService.
userService.delOneCurr
(
curId,
function (e:TideResultEvent):void
{
loadCur(0);
},
function (e:TideFaultEvent):void
{
Alert.show(e.fault.faultDetail);
}
);
Та же функция в исходном java классе UserService, который реализует интерфейс IUserService.
@Override
public void delOneCurr(Integer i)
{
objT.deleteOneCurr(i);
}
Функция delOneCurr вызывает метод deleteOneCurr(Integer dd) класса CacheTransform, предназначенный для удаления объекта класса cCurriculum из БД. Кроме того, эта функция каскадно вызывает удаление объектов входящих в листы удаляемого учебного плана.
public void deleteOneCurr(Integer dd) {
try {
System.out.println("//MAS: TRY DELETE Curriculum");
cCurriculum cur;
cur=(cCurriculum) cCurriculum._open(dbconnection, new Id(dd));
if(cur.getListOfCicl()!=null){
for(int i=0;i<cur.getListOfCicl().size();i++) {
cCicl k=(cCicl)cur.getListOfCicl().asList().get(i);
deleteOneCicl(Integer.parseInt(k.getId().toString()));
}
}
if(cur.getListOfSemestr()!=null) {
for(int i=0;i<cur.getListOfSemestr().size();i++) {
cSemestr k = (cSemestr)cur.getListOfSemestr().asList().get(i);
deleteOneSeme(Integer.parseInt(k.getId().toString()));
}
}
if(cur.getListOfLogs()!=null) {
for(int i=0;i<cur.getListOfLogs().size();i++) {
cLogs k=(cLogs)cur.getListOfLogs().asList().get(i);
deleteOneLog(Integer.parseInt(k.getId().toString()));
}
}
cur._close();
cCurriculum._deleteId(dbconnection, new Id(dd));
System.out.println("//MAS: DELETE Complite");
}
catch (CacheException e) {
e.printStackTrace();
}
}
Как видно из примера, метод последовательно выполняет удаление всех объектов из списков семестров, циклов и логов соответствующими методами deleteOneSeme, deleteOneCicl, deleteOneLog.
Несколько слов о «продакшене»
Так как Cache-проекции в полной мере поддерживают принципы ООП, то добавление поля или изменения типа в Cache-классе равносильно соответствующей операции непосредственно в Java.
Если говорить конкретнее, то использование проекций и дублирование классов позволяет относительно легко вносить изменения в проект при добавлении нового поля или при изменении типа данных в Cache.
Такое решение легко поддерживать, главное требование которое необходимо выполнять – соблюдение нотации при именовании классов, свойств и методов.
При увеличении числа классов, код растет прямо пропорционально, а если постараться унифицировать функции Tranform, то в разы меньше.
На данном этапе при внесении изменений или же добавлении нового класса необходимо:
- заново сгенерировать классы-проекции и заменить существующие (или же добавить новые);
- внести соответствующие изменения в m-классы (так как реализованы принципы ООП, то подобные изменения не составят особого труда);
- внести изменения в контроллер (при унификации можно избежать).
Заключение
Рассматривая все плюсы и минусы представленной комбинации и предложенного подхода, стоит учитывать дальнейшее развитие системы в сторону агентных технологий.
Поскольку в качестве платформы была выбрана JADE, то естественно, что она накладывает ряд ограничений на проектирование архитектуры всей системы. Так как сама JADE написана на языке Java и является кроссплатформенной системой работающей под виртуальной машиной, то и логика разрабатываемой МАС также строится в этой среде. Основной единицей (элементом) JADE является агент, который, по сути, является объектом java-класса. Вся логика функционирования этого агента также реализуется в этом классе. Долговременное хранение данных каждого агента удобно реализовывать, в объектно-ориентированной базе данных, т.к. сам агент предполагает наличие в нём интеллектуальной составляющей, которая может основываться на семантических сетях и онтологиях. Онтологии в свою очередь являются хранимыми данными и наследуют (если можно так выразиться) объектно-ориентированный подход. Таким образом, самой удобной СУБД для реализации данной системы является объектно-ориентированная и предоставляющая возможность работы с ней через java-классы. СУБД Caché, по нашему мнению, является хорошим кандидатом для решения данной задачи.
На данном этапе сложно оценить эффективность использования Flex технологии в качестве web-интерфейса, поскольку связка Caché-Java дает достаточно широкий выбор для дальнейшего развития.
К недостаткам предложенной архитектуры можно отнести дублирование java-проекций в пакете «Java m-classes» и реализацию дополнительных функций в классе CacheTransform, обеспечивающих синхронизацию объектов классов java-проекций и m-classes. В будущем данная проблема может быть решена путём доработки механизма генерации java-проекций в Caché, который сможет учитывать дополнительную специфику, определяемую внешними приложениями, используемыми java-проекции. Например, дополнительные требования к генерации проекций могут быть описаны в отдельном xml-файле, используя который механизм генерации будет создавать правильные java-проекции. Тогда необходимость в дубликатах классов java-проекций отпадёт.
От авторов
Надеемся, что вам было интересно читать данную статью, и вы смогли найти что-то полезное для себя. Мы, безусловно, продолжим работу над проектом (в том числе и работу над его оптимизацией), поэтому возможно в дальнейшем данный цикл статей будет расширяться.
Сейчас же мы рассмотрели лишь один из модулей разрабатываемой мультиагентной системы, и нашей основной целью было ознакомить читателя со связкой Caché + Java + Flex. В то же время мы оставили нераскрытым вопрос об использовании агентов. Поэтому, для тех, кому интересна данная тема, предлагаем ознакомиться с некоторыми пояснениями под спойлером.
Продемонстрируем функцию, запускающую новый контейнер и агента на платформе JADE при старте web-сервера.
public boolean StartAgent()
{
String aName = "ZavKaf Agent - ";
String aClass = "agents.ZavCafAgent";
rt= Runtime.instance();
p=new ProfileImpl();
p.setParameter("container-name","ZavKaf_Agent");
mainCont=rt.createAgentContainer(p);
try {
Object[] temp=new Object[1];
temp[0]=testSoul;
ac=mainCont.createNewAgent(aName, aClass, temp);
ac.start();
agSt=true;
System.out.println("Agent Start");
return true;
}
catch (Exception ex) {
testSoul.setA(null);
System.out.println("Agents ERROR: " + ex);
return false;
}
}
Ниже показан вызов одного из модулей агента отвечающий за функциональное построение учебного плана.
public mAlgRes doAlgorithm(List<mDiscipline> curL, mCurriculum curriclum, List<mControlForm> cf) {
return Agents.getA().doAlg(curL,curriclum,cf);
}
Здесь Agents это объект класса, хранящий в себе данные об активных агентах и агенте, связанным с текущим web-приложением.
Автор: yakuninyy