Как и было обещано во второй части эта статья посвящена исключительно практическим примерам, демонстрирующим особенности взаимодействия Caché + Java + Flex. Начнем с преобразования типов данных на протяжении всей связки. Мы не стали заострять внимание на преобразованиях таких типов данных, как Integer, Float, String, Boolean, поскольку данные типы идентичны в Caché и Java (а в ActionScript все числовые типы переходят в Number). Другое дело коллекции объектов. Их преобразование проходит достаточно нетривиально, поэтому остановимся на них подробнее.
"
Рассмотрим преобразования коллекций на протяжении всей связки Caché + Java + Flex на примере списка дисциплин в определенном цикле. В качестве примера коллекции рассмотрим отношения — вид связей между классами Caché, реализованные как свойства типа Relationship, с определенным видом поддержки целостности. Подробнее об отношениях в Caché.
В сгенерированной java-проекции (пакет «Java c-classes») данное свойство представлено объектом класса com.intersys.classes.RelationshipObject из библиотеки cachedb.jar.
Данный класс имеет стандартный метод asList, который принимает RelationshipObject и возвращает List, что значительно облегчает работу со списком в дальнейшем.
В m-classes используются листы (List listOfDisc), поэтому необходимо преобразовать каждый объект c-classes из RelationshipObject в соответствующий объект m-classes. Данное действие происходит в классе CacheTransform.
Далее выполняется проецирование m-классов во flex модуль по заданным в POM-файле для GraniteDS параметрам. GraniteDS по умолчанию использует для проекции типа List (в Java) соответствующий тип ListCollectionView (в ActionScript). Например, так будет выглядеть проекция списка в сгенерированном AS-классе mCiclBase.
Для дальнейшего упрощения работы непосредственно в 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-класса. Преобразование листов происходит в данной функции следующим образом.
После этого вызывается стандартный метод _save у объекта c-classes, который сохраняет данные в базу Caché, при этом приводит тип List в соответствующий тип в Caché com.intersys.classes.RelationshipObject.
Теперь, когда с коллекциями все понятно, перейдём к примеру, который демонстрирует добавление и удаление объектов.
Рассмотрим работу с интерфейсом сервисов на примере добавления и удаления учебного плана. Первым создаётся объект «учебный план» в AS-проекции класса mCurriculum. Создание этого объекта выполняется на стороне клиента, затем он передаётся на сервер и сохраняется в БД. Для этого вызываем через объект реализующий интерфейс сервисов сервера метод addOneCurr.
где сur – объект класса-проекции mCurriculum, созданный во flex, а k – переменная отвечающая за выбор действия (добавление или редактирование).
Здесь необходимо помнить, что GraniteDS посылает запросы асинхронно и, если необходимо выполнить какое-либо действие строго после завершения функции, его нужно помещать непосредственно в функцию-обработчик результата. Например:
Данная функция выведет сообщение «Добавление завершено» строго после удачного завершения функции addOneCurr.
Ниже приведён код функции addOneCurr в классе-проекции на ActionScript интерфейса IUserService сгенерированной GraniteDS.
Та же функция в исходном java классе UserService, который реализует интерфейс IUserService.
Эта функция вызывает метод RevTranformCu(mCurriculum cur, Integer act) класса CacheTransform, предназначенный для преобразования объекта класса mCurriculum в объект класса cCurriculum и его записи в БД. Кроме того, функция addOneCurr «каскадно» вызывает сохранение объектов, входящих в коллекции, обрабатываемого учебного плана.
Как видно из примера, метод последовательно выполняет сохранение всех объектов из списков семестров, циклов и логов соответствующими методами RevTranformSe, RevTranformCi, RevTranformLo. При этом принцип остаётся прежним: если id отсутствует, то создаётся новый объект, иначе редактируется существующий.
Аналогично протекает и удаление учебного плана. Для удаления достаточно получить id объекта класса-проекции mCurriculum и передать его на сервер. За это отвечает функция delOneCurr в AS проекции функции интерфейса IUserService.
Та же функция в исходном java классе UserService, который реализует интерфейс IUserService.
Функция delOneCurr вызывает метод deleteOneCurr(Integer dd) класса CacheTransform, предназначенный для удаления объекта класса cCurriculum из БД. Кроме того, эта функция каскадно вызывает удаление объектов входящих в листы удаляемого учебного плана.
Как видно из примера, метод последовательно выполняет удаление всех объектов из списков семестров, циклов и логов соответствующими методами deleteOneSeme, deleteOneCicl, deleteOneLog.
Так как Cache-проекции в полной мере поддерживают принципы ООП, то добавление поля или изменения типа в Cache-классе равносильно соответствующей операции непосредственно в Java.
Если говорить конкретнее, то использование проекций и дублирование классов позволяет относительно легко вносить изменения в проект при добавлении нового поля или при изменении типа данных в Cache.
Такое решение легко поддерживать, главное требование которое необходимо выполнять – соблюдение нотации при именовании классов, свойств и методов.
При увеличении числа классов, код растет прямо пропорционально, а если постараться унифицировать функции Tranform, то в разы меньше.
На данном этапе при внесении изменений или же добавлении нового класса необходимо:
Рассматривая все плюсы и минусы представленной комбинации и предложенного подхода, стоит учитывать дальнейшее развитие системы в сторону агентных технологий.
Поскольку в качестве платформы была выбрана 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. В то же время мы оставили нераскрытым вопрос об использовании агентов. Поэтому, для тех, кому интересна данная тема, предлагаем ознакомиться с некоторыми пояснениями под спойлером.
Преобразование коллекций
"
Рассмотрим преобразования коллекций на протяжении всей связки 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-сервера.
Ниже показан вызов одного из модулей агента отвечающий за функциональное построение учебного плана.
Здесь Agents это объект класса, хранящий в себе данные об активных агентах и агенте, связанным с текущим 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-приложением.