Caché + Java + Flex. Особенности интеграции технологий. Часть 3

в 0:58, , рубрики: cache, flex, intersystems cache, jade, java, Блог компании InterSystems, объектные субд, метки: , , , , ,

Как и было обещано во второй части эта статья посвящена исключительно практическим примерам, демонстрирующим особенности взаимодействия Caché + Java + Flex. Начнем с преобразования типов данных на протяжении всей связки. Мы не стали заострять внимание на преобразованиях таких типов данных, как Integer, Float, String, Boolean, поскольку данные типы идентичны в Caché и Java (а в ActionScript все числовые типы переходят в Number). Другое дело коллекции объектов. Их преобразование проходит достаточно нетривиально, поэтому остановимся на них подробнее.

Преобразование коллекций

Caché + Java + Flex. Особенности интеграции технологий. Часть 3"
Рассмотрим преобразования коллекций на протяжении всей связки 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 «каскадно» вызывает сохранение объектов, входящих в коллекции, обрабатываемого учебного плана.

Метод RevTranformCu.

///Трансформация УП
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 из БД. Кроме того, эта функция каскадно вызывает удаление объектов входящих в листы удаляемого учебного плана.

Метод deleteOneCurr.

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. В то же время мы оставили нераскрытым вопрос об использовании агентов. Поэтому, для тех, кому интересна данная тема, предлагаем ознакомиться с некоторыми пояснениями под спойлером.

Агент и микроагент

Данные компоненты проекта необходимы для дальнейшего расширения функционала в соответствии с конкретными требованиями к системе. На текущий момент реализована лишь связь web-приложения с агентом на сервере и вызов ряда его функций. Это в дальнейшем поможет в разработке системы, построенной на взаимодействии агентов в среде JADE, где представленный проект является лишь одним из визуальных модулей агента. Микроагент, по сути, является вспомогательным компонентом для облегчения работы с мультиагентной системой пользователей. Каждому пользователю предоставляется его собственный агент, через который он взаимодействует с другими агентами системы. Для обеспечения постоянной связи агентов с пользователем и используется микроагент, который устанавливается на компьютере пользователя. Микроагент информирует пользователя о происходящих событиях и получаемых сообщениях, передаваемых основным агентом. Он также обеспечивает запуск приложения на клиенте (браузер, отображающий web-страницы), позволяющего реализовать диалог пользователя с его агентом.
Продемонстрируем функцию, запускающую новый контейнер и агента на платформе 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

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js