Написание впечатляющего программного кода с помощью API-интерфейсов платформы IBM FileNet P8: Приложение HelloDocument

Часть 1. Как приступить к созданию своей первой программы для платформы FileNet P8

В статье описывается разработка простого приложения HelloDocument с помощью API-интерфейса IBM® FileNet® P8 Content API. Пройдя через последовательность простых операций, вы научитесь использовать шаблоны кодирования для выполнения обширного набора своих собственных операций. API-интерфейсы для платформы P8 весьма обширны, что создает определенные трудности новичкам, желающим приступить к практической разработке. Эта статья позволяет преодолеть эту проблему: она обеспечивает общую ориентацию и служит «стартовым столом», на основе которого вы сможете легко построить свои собственные приложения. Даже если вы уже обладаете большим стажем разработки для платформы P8, вы, несомненно, найдете полезную информацию в этой статье и в последующих статьях данной серии. В будущих статьях этой серии будут глубже рассмотрены некоторые темы, имеющие отношение к процессам и к контентным API-интерфейсам.

Билл Карпентер, архитектор по программным продуктам, IBM

Билл Карпентер (Bill Carpenter) является архитектором по программным продуктам для управления корпоративным контентом (ECM) в корпорации IBM (Сиэтл, штат Вашингтон, США). На протяжении 10 лет он является сотрудником направления IBM Enterprise Content Management в качестве разработчика, менеджера по разработке и архитектора. Карпентер – один из авторов книги IBM FileNet Content Manager Implementation Best Practices and Recommendations (IBM FileNet Content Manager. Типовые методики и рекомендации по внедрению). До прихода в IBM Карпентер участвовал в построении крупномасштабных программных систем для компаний из списка Fortune 50, а также выполнял обязанности главного технического директора в одном из Интернет-стартапов. Он является активным участником нескольких проектов в области продуктов с открытым исходным кодом. Билл Карпентер – обладатель ученых степеней по математике и компьютерной технике, полученных в Политехническом институте Ренсселера (Трой, штат Нью-Йорк, США).



13.01.2010

Знакомство с программой HelloDocument

Данная статья представляет собой обзор полноценного, самодостаточного приложения для среды IBM FileNet P8. P8 – это платформа IBM для управления корпоративным контентом (ECM). Хотя в реальных условиях большинство программ для P8 является частью более крупного фреймворка, например, J2EE или .Net, ваша карьера разработчика скорее всего начнется с создания автономной программы. При работе с автономной программой вы сможете сконцентрироваться на деталях платформы P8, не отвлекаясь на сложности более крупного фреймворка.

Тренировочное приложение HelloDocument выполняет определенный набор задач, некоторые из которых не представляют для вас никакого интереса, а другие могут оказаться весьма полезными именно вам. Кроме того, вы сможете использовать методы, проиллюстрированные в этом коде, и расширить их для выполнения действий, которые в большей степени соответствуют вашим сценариям применения. Вполне возможно, что не менее важным для вас окажется следующее обстоятельство. Запустив приложение HelloDocument на исполнение, вы можете быть уверены в том, что ваша среда сконфигурирована надлежащим образом. Это позволит вам сконцентрироваться на деталях собственного приложения, не имея при этом каких-либо сомнений относительно своей среды.

Для успешного освоения данной статьи вы в достаточной степени должны знать технологию Java™ и понимать описания исходного текста на Java. Данная статья не занимается подробным описанием каждой строки файла HelloDocument.java, а останавливается на важнейших моментах, которые иллюстрируют основные аспекты API-интерфейса CE. Приведенный код содержит значительный объем комментариев, поэтому при наличии определенной доли авантюризма вы можете пропустить текст статьи и перейти непосредственно к исходному тексту (см. раздел Загрузка).

Приложение HelloDocument структурировано в виде одного исходного файла на языке Java объемом 400–500 строк. Даже с учетом значительного объема комментариев в исходном тексте это весьма значительное количество строк только для того, чтобы сказать «Hello». В файле HelloDocument.java присутствует ряд моментов, которые вы, скорее всего, переместили бы в отдельные классы или в отдельные исходные файлы. Все эти моменты специально собраны в одном месте – для того, чтобы вы смогли убедиться в наличии всего необходимого. Например, в исходный файл HelloDocument.java включена полная последовательность входа JAAS (Java Authentication and Authorization Service), включая внутренний класс для обработки обратных вызовов. Вне всякого сомнения, для реального приложения это было бы не совсем обычно. В классе HelloDocument реализован интерфейс PrivilegedAction , и большая часть его бизнес-логики инкапсулирована внутри метода run только для того, чтобы нагляднее проиллюстрировать явную модель входа JAAS. Объем самой бизнес-логики в приложении HelloDocument не превышает 50–100 строк.

На момент написания данной статьи текущий выпуск платформы P8 имел номер 4.0.1, а цикл разработки выпуска P8 4.5.0 приближался к своему завершению. Данный код способен исполняться в любом выпуске. (Данная статья, как и другие статьи этой серии, не тратит много времени на API-интерфейсы для выпусков P8 3.x. В некоторых случаях эти API-интерфейсы имеют весьма существенные отличия). Используемые классы и методы API-интерфейса составляют его ядро, поэтому они очень устойчивы. Весьма вероятно, что приложение HelloDocument без каких-либо модификаций сможет исполняться на нескольких следующих выпусках платформы P8. Приложение HelloDocument написано на языке Java. Поскольку API-интерфейс P8 Content Java API отличается от API-интерфейса P8 Content .Net API преимущественно соглашениями о присвоении имен и другими вторичными обстоятельствами, задача переписывания приложения HelloDocument на языке платформы .Net, таком как C#, фактически сводится к простой транслитерации. (Достаточно крупное различие между двумя указанными API-интерфейсами находится в области аутентификации. Несмотря на то, что в данной статье обсуждается Java-аутентификация для приложения HelloDocument, общее рассмотрение вопросов аутентификации выходит за ее рамки).

Итак, что же реально делает приложение HelloDocument? Оно создает документ, загружает в него контент из файла, проверяет этот контент, а затем помещает его в папку. Затем оно прочитывает этот контент, после чего сравнивает загруженный контент с контентом исходного файла. При желании вы можете сконфигурировать свое приложение, чтобы пропускать часть файла во время загрузки и сравнения. Операции создания документов и помещение их на хранение в виде файла являются типовыми для многих сценариев использования, однако операции сравнения не являются таковыми. Фактически, вам никогда не придется проверять контент, который вы до этого загрузили. Эти дополнительные фрагменты в процессе загрузки предназначены лишь для того, чтобы проиллюстрировать некоторые аспекты кодирования API. Приложение HelloDocument спроектировано таким образом, чтобы вам не приходилось выполнять каких-либо специфических настроек CE перед исполнением (за одним исключением: вам необходимо удостовериться в том, что вы имеете права доступа для создания новых папок и документов). После того, как это приложение будет работать в вашей среде, вы сможете неоднократно запускать его, не получая сообщений об ошибках.


Конфигурация

Встроенная конфигурация

Приложение HelloDocument определяется несколькими конфигурационными элементами. Некоторые из них, такие как URI-идентификатор (Uniform Resource Identifier) соединения с сервером Content Engine (CE), типичны для большинства приложений P8. Другие элементы определяются спецификой тренировочного приложения HelloDocument. В случае реального приложения вам почти наверняка не придется жестко программировать эти элементы конфигурации. Вы будете использовать аргументы командной строки, конфигурационный файл или какой-либо другой механизм для отделения конфигурации от кода самого приложения. Для удобства работы с приложением HelloDocument все его конфигурационные элементы определены как константы в статическом внутреннем классе по имени ConfigInfo, который в сокращенной форме показан в листинге 1. Когда в каком-либо другом месте кода вы увидите ссылки на ConfigInfo.SOME_VALUE, они будут ссылаться именно на эти константы.

Листинг 1. Статический внутренний класс ConfigInfo
private static final class ConfigInfo
{
    // . . .

    /**
     * This URI tells us how to find the CE and what protocol to use.
     */
    static String CE_URI = "http://myCEserver:7001/wsi/FNCEWS40DIME";
    /**
     * This ObjectStore must already exist.
     */
    static String OBJECT_STORE_NAME = "MyObjectStore";

    // . . .
}

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

Другие элементы конфигурации

Данная статья посвящена написанию программного кода, поэтому в ней не описывается детальная настройка вашей среды Java classpath и других внешних конфигурационных элементов. В документации по платформе P8 подробно описывается, как настроить среду «толстого» клиента. Эта настройка имеет различия для каждого конкретного сервера J2EE-приложений. В том же разделе документации описывается, как сконфигурировать параметры JAAS. Примеры и инструкции в остальной части этой статьи исходят из того, что все необходимые параметры сконфигурированы надлежащим образом.


Get или fetch?

Java-объект в API-интерфейсе – это не то же самое, что объект в репозитарии CE. Он представляет собой всего лишь ссылку на соответствующий CE-объект, с помощью которой вы можете проанализировать значения свойства, осуществить навигацию по OVP-свойствам (object-valued properties) и осуществить различные обновления. Установление реального соответствия между объектом API и объектом CE происходит в тот момент, когда API осуществляет с CE-сервером операцию round-trip («круговой» обмен сообщениями с сервером). Во многих случаях именно число операций round-trip с сервером является основным фактором, определяющим производительность приложения, поэтому API-интерфейс обеспечивает весьма детальный контроль над реальным осуществлением операций round-trip, а также над тем, какие данные передаются при каждой такой операции. Приложение HelloDocument стремится свести к минимуму число операций round-trip, однако не слишком тщательно ограничивает объемы данных, участвующих в запросах или ответах. Вы можете настроить размеры этой полезной нагрузки с помощью фильтров свойств и других механизмов. Эти фильтры свойств более подробно будут рассматриваться в следующей статье данной серии. На данном этапе вам достаточно знать, что этот API-интерфейс спроектирован таким образом, чтобы работать без каких-либо неожиданностей в том случае, если вы не будете использовать фильтры свойств. Как только вы освоите этот API на базовом уровне, вам непременно захочется использовать фильтры свойств, поскольку они позволяют радикально повысить производительность приложения посредством уменьшения объема полезной нагрузки и посредством объединения некоторых операций round-trip.

Описываемый API-интерфейс поддерживает полезное соглашение для реализации Java-объектов. В именах фабричных методов (и методов некоторых других типов) используется префикс get, чтобы указать на локальную операцию, и префикс fetch, чтобы указать, что с CE-сервером будет осуществлена операция round-trip. (Кроме того, существует еще один префикс create, который используется в том случае, когда вы собираетесь реализовать не только Java-объект, но и новый CE-объект в репозитарии или в другом месте). На жаргоне API-интерфейса реализация Java-объекта без операции round-trip с сервером называется fetchless instantiation. Так, например, реализация conn = Factory.Connection.getConnection(ConfigInfo.CE_URI) является локальной. Во многих местах исходного кода приложения HelloDocument присутствует комментарий no R/T, указывающий на реализацию fetchless instantiation или на наличие другой операции, избавляющей от необходимости round-trip. Обратите внимание, что методы get используются везде, где только возможно.

При использовании реализации fetchless instantiation может произойти одна любопытная вещь. API-интерфейс полагает, что CE-объекты, на которые вы ссылаетесь, реально существуют. При желании, вы можете солгать API-интерфейсу, однако в большинстве случаев это не самый продуктивный подход. Момент расплаты наступит в процессе осуществления операции round-trip с участием упомянутого объекта. В этой точке CE-сервер методично приведет ваши ссылки на объекты в соответствие с реальными CE-объектами. Естественно, создание CE-объекта – это особый случай, который должен обрабатываться ожидаемым вами способом. Существуют некоторые другие ситуации (выходящие за рамки этой статьи), в которых ссылки на несуществующие CE-объекты могут оказаться полезными; необходимо лишь создать эти объекты к тому моменту, когда о них «узнает» механизм CE.

Подведем некоторые итоги. Использование fetchless instantiation повышает производительность, однако вполне возможно, что на одной из последующих стадий механизму обработки ошибок вашего приложения придется разбираться с отсутствующими объектами. На практике, накладные расходы на обработку подобных ошибок не слишком велики.


Как добраться до репозитария ObjectStore

Метод HelloDocument.run является именно тем местом, где функционирует главная бизнес-логика верхнего уровня. Начало этого метода, вместе с переменной экземпляра для Connection, иллюстрирует широко распространенный шаблон кодирования (листинг 2). Вне всякого сомнения, большинство контентных приложений имеет дело только с объектами внутри репозитариев CE, а большинство из приложений этой категории имеет дело только с одним репозитарием.

Листинг 2. Метод HelloDocument.run and friend
/**
 * All interaction with the server will make use of this Connection object.
 * Connections are actually stateless, so you don't have to worry about 
 * holding open some CE resource.
 *
 * no R/T
 */
private Connection conn = Factory.Connection.getConnection(ConfigInfo.CE_URI);
// ...
/**
 * This method contains the actual business logic.  Authentication has
 * already happened by the time we get here.
 */
public Object run()
{
    // Standard Connection -> Domain -> ObjectStore
    //no R/T
    Domain dom = Factory.Domain.getInstance(conn, null);
    //no R/T
    ObjectStore os = Factory.ObjectStore.getInstance(dom, 
                                           ConfigInfo.OBJECT_STORE_NAME);

    String containmentName = createAndFileDocument(dom, os);

    File f = new File(ConfigInfo.LOCAL_FILE_NAME);
    long fileSize = f.length();
    System.out.println("Local content size is " + fileSize + " for file " 
                                                    + ConfigInfo.LOCAL_FILE_NAME);
    long skipPoint = 0L;
    if (ConfigInfo.USE_SKIP)
    {
        long midPoint = fileSize / 2;
        // pick a random point in the second half of the content
        skipPoint =  midPoint + (long)Math.floor((Math.random() * midPoint));
    }
    System.out.println("Will skip to " + skipPoint + " of " + fileSize);

    readAndCompareContent(os, containmentName, skipPoint);
    return null;
}

Шаблон кодирования для доступа к репозитарию ObjectStore имеет следующий вид:

  1. Применить операцию Get к объекту Connection.
  2. Применить операцию Get к объекту Domain.
  3. Применить операцию Get к объекту ObjectStore.

Connection – это довольно «легковесный» класс. Он говорит API-интерфейсу, как подключиться к CE-серверу. С точки зрения CE-сервера, взаимодействия между API и CE-сервером не меняют своего состояния в процессе исполнения (stateless), поэтому объект Connection не удерживает в открытом состоянии никаких дорогостоящих ресурсов на стороне сервера. Главным информационным элементом, содержащимся в объекте Connection , является URI-идентификатор, подлежащий использованию. На основе этого идентификатора API-интерфейс определяет местоположение CE-сервера и метод подключения к нему. Особо следует отметить, что объект Connection не содержит никакой пользовательской информации. Для объекта Connection можно задать ряд дополнительных конфигурационных параметров, однако этот механизм не описан в данной статье.

В объекте Domain содержатся ресурсы P8 уровня ObjectStore или выше. Для реализации Java-объекта Domain вам потребуется объект Connection и доменное имя. На сегодняшний день развертывание платформы P8 предусматривает только один домен, поэтому API-интерфейс позволяет в качестве доменного имени передавать в фабричный метод неопределенное значение (null). Доменное имя задается на этапе установки P8. Если вам для каких-либо целей потребуется это имя, посмотрите на значение свойства Name объекта Domain.

Объект ObjectStore представляет собой репозитарий CE. Обратите внимание, что фабричный метод для создания объекта ObjectStore использует не объект Connection, а объект Domain (правильнее было бы сказать, что ObjectStore «накладывается» (scoped) на Domain, однако эта тема выходит за рамки данной статьи). Внутри API-интерфейса один и тот же объект Connection используется как для объекта Domain, так и для реализованных на его основе объектов. Позднее, когда объект ObjectStore сам будет использоваться для реализации объектов, объект Connection будет автоматически передаваться этим объектам внутри API.

Этот фрагмент кода также является хорошим местом для общего знакомства с использованием фабричных методов. API-интерфейс CE имеет большое количество так называемых «фабричных» классов, примерно по одному на каждый класс CE. Для удобства они организованы в виде вложенных классов внутри класса com.filenet.api.core.Factory. Чтобы найти фабричный класс для определенного класса CE, ищите в классе Factory внутренний класс с именем Factory внутренний класс с именем Document, Folder или с другим именем, соответствующим типу нужного вам класса CE. Внутри этого внутреннего класса вы найдете всего лишь несколько методов для реализации Java-объектов для соответствующего класса CE. Нетрудно выяснить, какие параметры используются для этих фабричных методов. Фабричные методы являются «типобезопасными» (type-safe) в том смысле, что они возвращают вполне определенные типы. Например, каждый из методов Factory.Folder возвращает объект типа Folder. В общем случае, идея фабричных методов и типобезопасности состоит в сокращении возможностей для программных ошибок. Поскольку у вас нет необходимости осуществлять приведение типов, существенно повышается вероятность того, что любые проблемы будут выявлены уже на этапе компиляции. Это предпочтительнее, чем проверка типов в процессе исполнения.

Существует еще одно, менее крупное семейство методов (мало используемых в приложении HelloDocument), которые не являются типобезопасными. На жаргоне API-интерфейса – это так называемые commodity-методы.

Характерным примером является метод ObjectStore.getObject, способный возвращать объекты практически любого независимого класса CE. Указанные методы предназначены для использования в определенных шаблонах прикладного кода, которые по своей природе должны иметь дело с широким разнообразием типов. Если вы имеете дело с заранее известными конкретными типами, то в использовании commodity-методов, как правило, нет особого смысла.


Создание нового документа Document

Первая группа строк внутри метода HelloDocument.createAndFileDocument закладывает основу для создания нового документа с контентом (листинг 3). Результатом исполнения этих строк кода является готовый Java-экземпляр Document с большинством действий, которые вы желаете предпринять, однако CE-экземпляр Document все еще не создан (поскольку вы пока не осуществили необходимую операцию round-trip с сервером).

Листинг 3. Внутри метода HelloDocument.createAndFileDocument, часть 1
//no R/T
ContentTransfer ct = Factory.ContentTransfer.createInstance();
ct.setCaptureSource(fis);
// optional
ct.set_RetrievalName(ConfigInfo.LOCAL_FILE_NAME);
// optional
ct.set_ContentType("application/octet-stream");

ContentElementList cel = Factory.ContentElement.createList();
cel.add(ct);

//no R/T
Document doc = Factory.Document.createInstance(os, null);
//not required
doc.getProperties().putValue("DocumentTitle", ConfigInfo.DOCUMENT_TITLE);
doc.set_ContentElements(cel);
//no R/T
doc.checkin(AutoClassify.DO_NOT_AUTO_CLASSIFY, CheckinType.MAJOR_VERSION);

Последовательность операций в листинге 3 может казаться вам несколько запутанной до тех пор, пока вы не познакомитесь с моделью CE-объектов, особенно с отношениями между документом Document и его контентом. Фрагмент контента (например, электронная таблица или текстовый документ) размещается в объекте под названием content element(элемент контента). Конкретнее, поскольку контент хранится в репозитарии вместе с документом Document, используется подкласс элементов контента с названием Document, используется подкласс элементов контента с названием ContentTransfer. (В случае, если «реальные биты» хранятся где-то в другом месте, используется другой подкласс под названием ContentReference). Документ Document может иметь любое число контентных элементов. Поскольку в терминологии CE эти элементы являются так называемыми «зависимыми объектами» (которые не могут существовать самостоятельно, а нуждаются в содержащем их в себе объекте Document), то свойство ContentElements документа Document имеет тип ContentElementList.

Будем надеяться, что теперь ситуация несколько прояснилась. С помощью фабричных методов вы создаете объект ContentTransfer, объект ContentElementList и объект Document. Вы добавляете объект ContentTransfer к объекту ContentElementList, а затем присваиваете свойству ContentElements объекта Document значение ContentElementList. В порядке выполнения этих операций допускается определенная гибкость, поэтому в своем собственном приложении вы сможете организовать их в наиболее удобной для себя последовательности.

Этот код наглядно иллюстрирует использование «типобезопасных» методов-акцессоров (property accessor method). Например, с целью задания для элемента контента так называемого «поискового имени» (retrieval name – дополнительное свойство, подсказывающее имя файла приложениям, которые впоследствии будут загружать контент) вы осуществляете вызов метода ct.set_RetrievalName(ConfigInfo.LOCAL_FILE_NAME).

Этот метод принимает только строковый аргумент, поэтому попытка использовать в качестве значения аргумента целое число вызовет ошибку на этапе компиляции. Соответствующий getter-метод ContentTransfer.get_RetrievalNameтакже является типобезопасным и возвращает строковое значение. Согласно соглашениям об именовании в API-интерфейсе Java, названия set_ и get_ (с подчеркиванием) сигнализируют о том, что вы имеет дело именно со свойством (в смысле репозитария CE), а не просто с типичным полем Java-объекта. Для каждого определяемого системой свойства каждого класса CE существуют типобезопасные методы-акцессоры. Для свойств, которые по своей природе допускают только чтение, не существует никаких типобезопасных setter-методов.

Задание свойства DocumentTitle несколько отличается от вызовов других setter-методов: doc.getProperties().putValue("DocumentTitle", ConfigInfo.DOCUMENT_TITLE). Что происходит в данном случае? Многие люди не понимают, что DocumentTitle – это не определяемое системой свойство (с точки зрения CE-сервера, определяемое системой свойство – это свойство, для которого CE-сервер создает значение, или свойство, влияющее на поведение CE-сервера, или то и другое одновременно). Это свойство определяется посредством AddOn в процессе создания ObjectStore, и вы нечасто увидите ObjectStore без свойства DocumentTitle. Однако, поскольку это не определяемое системой свойство, для него не существует никакого типобезопасного метода-акцессора в API-интерфейсах. Вместо этого вам придется задавать его значение с помощью какого-либо commodity-метода.

Метод Document.getProperties возвращает объект Properties, представляющий локально кэшированные значения свойств для данного документа Document, а класс Properties содержит commodity-методы для получения (get) и задания (set) значений свойств. При работе с любым реальным приложением вам, несомненно, придется иметь дело с особыми свойствами, поэтому вы должны убедиться в том, что понимаете модель доступа к значению commodity-свойства.

Как указывалось выше, даже после вызова метода checkin, документ Document в репозитарии CE все еще не создан. Вместо этого имеет место одно или более так называемых «незавершенных действий» (Pending Action), Незавершенное действие – это внутренняя маркировка объекта, для которого на сервере должна быть выполнена та или иная операция (в описываемом API-интерфейсе существуют определенные средства для исследования незавершенных действий, однако скорее всего вам никогда не придется этим заниматься). Когда вы вызвали фабричный метод для создания Java-объекта Document, он был автоматически помечен как имеющий незавершенное действие Create (создать). Когда вы вызвали метод checkin, было добавлено незавершенное действие Checkin. Многие удивляются, когда узнают, что для всех классов во всем API-интерфейсе существует всего лишь 18 типов незавершенных действий. Столь малая величина этого числа объясняется тем, что многие операции в CE фактически состоят из обычных CRUD-операций (create, retrieve, update, delete – создать, восстановить, обновить, удалить), применяемых к объектам и к свойствам. Обновление значения свойства, независимо от задействованного свойства или класса, всегда осуществляется с помощью незавершенного действия Update или как вспомогательное действие для некоторого другого незавершенного действия. Незавершенные действия можно рассматривать в качестве инструкций CE-серверу; они говорят CE-серверу, что именно нужно сделать с тем или иным объектом, когда этот объект прибудет на «сторону сервера».

Теперь посмотрим более пристально на вызов метода checkin: doc.checkin(AutoClassify.DO_NOT_AUTO_CLASSIFY, CheckinType.MAJOR_VERSION).

Он принимает два аргумента, которые определены в раздельных классах констант. Эти классы констант для значений параметров закодированы с помощью типобезопасного шаблона enum. Описываемый API-интерфейс первоначально был разработан для среды Java 1.4 и по-прежнему способен функционировать в этой среде, поэтому шаблоны enum языка Java недоступны. «Типобезопасность» шаблонов enum достигается следующим образом: они вынуждают вас использовать только константы из допустимого перечня альтернатив. Например, компилятор Java не позволит вам непредумышленно реверсировать порядок двух упомянутых выше аргументов метода checkin. Это приведет к ошибке в процессе компиляции вместо ошибки в процессе исполнения. В отсутствие подобной «типобезопасности» вы могли бы не получить признаков ошибки при некорректном поведении.


Запись документа Document в файл

В ближайшее время мы перейдем к реализации объекта Folder, однако сначала рассмотрим запись документа Document в этот объект Folder.

В описываемом API-интерфейсе имеются два метода с именами Folder.file и Folder.unfile, соответственно. Многие находят удивительным, что фактически это лишь helper-методы, а не основополагающие методы API-интерфейса. Конечно, идея о хранении в папках – это базовая особенность CE, однако акт помещения в папку фактически создает объект взаимосвязей (relationship object), соединяющий между собой Folder и containee. Вне зависимости от причины, эта идея заставляет многих новичков сделать паузу, поэтому в листинге 4 показано, как записать в файл документ Document, не вызывая для этого метода Folder.file.

Листинг 4. Внутри метода HelloDocument.createAndFileDocument, часть 2
Folder folder = instantiateFolder(os);
//no R/T
DynamicReferentialContainmentRelationship rcr = 
    Factory.DynamicReferentialContainmentRelationship.createInstance(os, null, 
                       AutoUniqueName.AUTO_UNIQUE, 
                       DefineSecurityParentage.DO_NOT_DEFINE_SECURITY_PARENTAGE);
rcr.set_Tail(folder);
rcr.set_Head(doc);
rcr.set_ContainmentName(ConfigInfo.CONTAINMENT_NAME);

Несмотря на то что класс DynamicReferentialContainmentRelationship(DRCR) имеет длинное имя, его создание происходит достаточно просто. Этот класс связывает папку (Folder) с содержащимся в ней документом (Document). Если представить эти отношения графически – в виде стрелки, направленной от Folder к Document – то нетрудно понять, почему OVP-связывание с папкой называется Tail (хвост), а OVP-связывание с документом называется Head (голова).

Класс DRCR является динамическим, поскольку его свойство Tail автоматически обновляется при каждом изменении целевого объекта Document. Для реализации класса DRCR вызовите применимый фабричный метод, который создает Java-экземпляр с незавершенным действием Create, а затем задайте несколько системных свойств. В данном случае используйте AutoUniqueName.AUTO_UNIQUE, чтобы дать CE-серверу указание на изменение соответствующего имени containment name в случае конфликта имен (в пределах конкретной папки Folder эти имена должны быть уникальными). В противном случае данная операция потерпит неудачу и завершится исключением.

Класс DRCR допускает разбиение на подклассы, поэтому впоследствии вы сможете создать нужный вам подкласс и добавить некоторые собственные свойства для метаданных. Не забывайте, что класс DRCR пока еще не создан в репозитарии CE (то же самое относится и целевому документу Document).


Сохранение в репозитарии

Наконец, пришло время осуществить реальную запись в репозитарий CE. Для этого можно было бы ограничиться применением методов save к объекту Document и к классу DRCR, однако в соответствии с линией на минимизацию операций round-trip будем осуществлять сохранение с помощью UpdatingBatch (листинг 5).

Листинг 5. Внутри метода HelloDocument.createAndFileDocument, часть 3
UpdatingBatch ub = UpdatingBatch.createUpdatingBatchInstance(dom, 
                                                           RefreshMode.REFRESH);
ub.add(doc, null);
ub.add(rcr, null);
System.out.println("Doing updates via UpdatingBatch");
ub.updateBatch();

С концептуальной точки зрения, UpdatingBatch можно рассматривать как «резервуар» для переноса нужного содержимого на CE-сервер. Вы создаете экземпляр UpdatingBatch, добавляете к нему нужные объекты, а затем вызываете метод updateBatch для пересылки всего необходимого на сервер. Конечно, имеет смысл добавлять только те объекты, у которых есть изменения, подлежащие сохранению в репозитарии CE. Помимо сведения к минимуму числа операций round-trip, у UpdatingBatch есть еще одна важная особенность. Все действия в UpdatingBatch происходят как одна элементарная транзакция с сервером, которая завершается успехом или терпит неудачу. Для приложения HelloDocument это не имеет значения, однако можно отыскать и вполне реальные сценарии использования, которые нуждаются в подобном поведении транзакции. UpdatingBatch – это хороший способ получения результата при приемлемом потреблении производительности.

При создании UpdatingBatch был задействован аргумент, указывающий API-интерфейсу на необходимость возвращения обновленных экземпляров объектов после операции round-trip с сервером. Во многих случаях у вас не будет необходимости в обновлении объектов (или вы будете использовать фильтры свойств для ограничения масштабов этого обновления), однако в данном случае я использовал имя containment name для иллюстрации некоторых моментов в приложении HelloDocument. Поскольку мы дали серверу указание автоматически выбирать уникальное имя containment name, оно может отличаться от того имени, которое было использовано с классом DRCR. Обновленное значение свойства говорит нам о том, какое имя реально использовал CE-сервер.


Реализация объекта Folder

В свое время мы перескочили через реализацию объекта Folder, поэтому теперь вернемся к этому вопросу. В листинге 6 показан метод HelloDocument.instatiateFolder.

Листинг 6. Метод HelloDocument.instantiateFolder
private Folder instantiateFolder(ObjectStore os)
{
    Folder folder = null;
    try
    {
        //no R/T
        folder = Factory.Folder.createInstance(os, null);
        //no R/T
        Folder rootFolder = Factory.Folder.getInstance(os, null, "/");
        folder.set_Parent(rootFolder);
        folder.set_FolderName(ConfigInfo.FOLDER_NAME);
        //R/T
        folder.save(RefreshMode.NO_REFRESH);
    }
    catch (EngineRuntimeException ere)
    {
        // Create failed.  See if it's because the folder exists.
        ExceptionCode code = ere.getExceptionCode();
        if (code != ExceptionCode.E_NOT_UNIQUE)
        {
            throw ere;
        }
        System.out.println("Folder already exists: /" + ConfigInfo.FOLDER_NAME);
        //no R/T
        folder = Factory.Folder.getInstance(os, null, "/" + ConfigInfo.FOLDER_NAME);
    }
    return folder;
}

Реализация метода instantiateFolder является несколько искусственной, поскольку я хотел обеспечить возможность для беспрепятственного многократного исполнения приложения HelloDocument без какой-либо промежуточной настройки. Я решил предпринять попытку создания объекта Folder, а в случае неудачи вернуться к реализации fetchless instantiation. Скорее всего, я не поступал бы так в случае реального приложения, поскольку это было бы неэффективно. В качестве альтернативного варианта можно было бы осуществить с объектом Folder реальную операцию fetch и вернуться к созданию этого объекта в том случае, если бы он не существовал. Чтобы убедиться в существование папки Folder, по-прежнему необходима одна операция round-trip (хотя ваше приложение, скорее всего, знает о существовании этой папки). Таким образом, наилучшим решением с точки зрения производительности является использование для объекта Folder реализации типа fetchless instantiation (при условии, что эта папка уже существует в репозитарии). С другой стороны, это потребует введения в ваш код соответствующих механизмов для обработки ошибок – на случай, если в действительности эта папка все же не существует. (Я не хотел загромождать используемый пример кода усложненным вариантом механизма обработки ошибок).


Чтение контента

Эта статья не тратит много времени на описание чтения контента из репозитария, поскольку большая часть логики при этом сводится к обработке Java-потока. Вместо этого мы рассмотрим несколько более интересных моментов, начиная с листинга 7.

Листинг 7. Внутри метода HelloDocument.readAndCompareContent
String fullPath = "/" + ConfigInfo.FOLDER_NAME + "/" + containmentName;
System.out.println("Document: " + fullPath);
//no R/T
Document doc = Factory.Document.getInstance(os, null, fullPath);
//R/T
doc.refresh(new String[] {PropertyNames.CONTENT_ELEMENTS});
InputStream str = doc.accessContentStream(0);

Исключительно для того, чтобы показать, что это может быть сделано «в принципе», полный путь к документу Document был построен на основе конфигурационных значений, а реальное имя containment name было возвращено из CE-сервера. С такой же легкостью вы можете получить для создаваемого документа Document значение идентификатора ID по результатам обновления, а затем использовать этот идентификатор для реализации указанного документа. (Использование идентификатора ID несколько эффективнее, чем использование пути, поскольку CE-серверу в любом случае приходится преобразовать путь в идентификатор ID).

С помощью фабричного метода документ Document реализуется в виде fetchless instantiation, после чего немедленно осуществляется вызов метода refresh (обновить), чтобы получить значение свойства ContentElements. В данном случае все это было сделано главным образом для того, чтобы продемонстрировать вызов метода refresh. С точки зрения производительности, реализация объекта Document с помощью фабричного метода fetchInstance обеспечивает примерно такой же уровень эффективности, особенно при использовании соответствующего фильтра свойств для ограничения объема извлекаемых данных. Метод Document.accessContentStream – это так называемый «метод для удобства» (convenience method), обеспечивающий вызов accessContentStream на соответствующем зависимом объекте ContentTransfer.


Аутентификация

В этом параграфе мы вернемся к началу приложения HelloDocument. Данная статья не предполагает подробного рассмотрения деталей аутентификации, поэтому ограничимся описанием того, что делает аутентификационный код в приложении HelloDocument.

В модели CE аутентификация полностью делегирована JAAS-сервисам. Это означает, что в реальных условиях вы никогда не «входите» в API-интерфейс CE или в CE. Напротив, API-интерфейс CE предполагает, что прежде чем осуществлять какие-либо вызовы CE, вы сначала выполните последовательность входа JAAS. Преимущество этого подхода состоит в том, что в API-интерфейс CE не нужно интегрировать никаких методов для аутентификации. Основанная на подключаемых модулях архитектура JAAS позволяет применять традиционные схемы идентификатор/пароль пользователя, устройства чтения отпечатков пальцев, переносные устройства идентификации и многие другие технологии. Вне зависимости от используемого способа аутентификации программный код вашего приложения для API-интерфейса CE остается неизменным.

Аутентификация с помощью helper-методов API-интерфейса CE

Обратите внимание на высокоуровневый аутентификационный код в методе HelloDocument.main (листинг 8). Позднее мы вернемся к рассмотрению метода loginAndRun, а пока посмотрите на оператор else.

Класс UserContext API-интерфейса CE имеет несколько так называемых «методов для удобства» (convenience method), относящихся к аутентификации. Эти методы предназначены исключительно для упрощения аутентификации в унаследованной системе, возможности которой ограничены традиционной схемой «идентификатор/пароль пользователя». Используйте метод UserContext.createSubject для создания экземпляра субъекта JAAS Subject. Объект UserContext также способен поддерживать стек субъектов JAAS Subject, в котором самый верхний субъект фактически используется для операций round-trip с CE-сервером. Методы UserContext.pushSubject и popSubject реализуют экземпляр стандартного стека. Сам стек хранится в локальном хранилище потоков, благодаря чему он связан с конкретным исполняемым потоком. Обратите внимание на то, что вызов popSubject находится внутри блока finally. Это гарантирует, что он будет осуществлен вне зависимости от любых произошедших событий. Это очень важно. Значимость этого механизма, не имеющего большого значения для автономного приложения HelloDocument, существенно повышается в J2EE-приложениях, в которых для обслуживания запросов обычно используются пулы потоков. Заранее не известно, когда прекратится многократное использование того или иного программного кода, поэтому написание любого кода всегда должно осуществляться надлежащим образом. После завершения вызовов CE необходимо «вычистить» аутентификационный контекст из потока.

Листинг 8. Методы аутентификации приложения HelloDocument
public static void main(String[] args) throws LoginException 
{
    System.out.println("CE is at " + ConfigInfo.CE_URI);
    System.out.println("ObjectStore is " + ConfigInfo.OBJECT_STORE_NAME);
    HelloDocument fd = new HelloDocument();
    if (ConfigInfo.USE_EXPLICIT_JAAS_LOGIN)
    {
        loginAndRun(fd, ConfigInfo.USERID, ConfigInfo.PASSWORD);
    }
    else
    {
        // This is the standard Subject push/pop model for the helper methods.
        Subject subject = UserContext.createSubject(fd.conn, ConfigInfo.USERID, 
                                  ConfigInfo.PASSWORD, ConfigInfo.JAAS_STANZA_NAME);
        UserContext.get().pushSubject(subject);
        try
        {
            fd.run();
        }
        finally
        {
            UserContext.get().popSubject();
        }
    }
}

Стандартная аутентификация по стандарту JAAS

Что произойдет, если вы не используете UserContext.pushSubject для активизации Subject с целью использования в операциях round-trip с CE-сервером? В этом случае внутренние компоненты API-интерфейса CE будут искать так называемый внешний (ambient) субъект JAAS Subject. Термин ambient в данном случае означает субъект, который был связан с потоком посредством стандартных JAAS-механизмов. Например, в Web-приложении может осуществляться вход (login) в Web-контейнер J2EE. В приложении HelloDocument вход осуществляется в явном виде как часть метода loginAndRun.

Листинг 9. Метод loginAndRun
private static final void loginAndRun(HelloDocument fd, String userid, 
                                              String password) throws LoginException
{
    LoginContext lc = new LoginContext(ConfigInfo.JAAS_STANZA_NAME, 
                                new HelloDocument.CallbackHandler(userid, password));
    lc.login();
    Subject subject = lc.getSubject();
    Subject.doAs(subject, fd);
}

Как можно увидеть в листинге 9, метод loginAndRun довольно прост. Он реализует стандартную последовательность входа JAAS, хотя в этом листинге не показана реализация внутреннего класса HelloDocument.CallbackHandler, поскольку это также стандартная накладная нагрузка JAAS. К сожалению, в этой простоте кроется некая «уловка». Вызов метода Subject.doAs в последней строке листинга 9 зависит от конкретного сервера J2EE-приложений. Эта деталь не полностью описана в спецификациях J2EE, поэтому каждый поставщик реализовал ее наиболее удобным для себя способом. Вполне вероятно, что в конечном итоге эта проблема в спецификациях J2EE будет разрешена, и вам не приходилось применять в своих вызовах логику, ориентированную на определенного поставщика. Конечно, при использовании настоящего J2EE-клиента вместо «толстого» клиента вы наверняка будете осуществлять аутентификацию с помощью J2EE-контейнера и позволите ему заниматься всеми деталями.


Заключение

Эта статья последовательно провела вас через все аспекты самодостаточного приложения HelloDocument. Вы увидели, как подключаться к репозитарию ObjectStore, как создавать объекты Folder, Document и объекты других типов, как прочитать и задать значения свойств, как выгрузить и загрузить контент. Несмотря на все это, пробное приложение осталось достаточно простым, тем не менее оно позволяет читателю познакомиться с шаблонами кодирования, которые могут быть использованы для решения очень многих задач.

Если вы только приступаете к написанию программного кода для платформы P8, то можете использовать приложение HelloDocument в качестве отправной точки. Полный исходный файл доступен для загрузки по ссылке, которая сопровождает эту статью. Вы сможете экспериментировать, изменяя этот файл с целью опробования различных аспектов. Если вы собираетесь быстро создать небольшое приложение, то вы окажетесь далеко не первым, кто уже решил подобную задачу на основе доработанной копии приложения HelloDocument.java. Воспользуйтесь им! Желаю успеха и не пропустите следующие статьи этой серии!


Загрузка

ОписаниеИмяРазмер
Исходный код на языке Java для данной статьиHelloDocument.zip6KB

Ресурсы

Научиться

  • Оригинал статьи: Writing great code with the IBM FileNet P8 APIs, Part 1: Hello, Document! (EN).
  • Прочитайте книги IBM FileNet Content Manager Implementation Best Practices and Recommendations (EN) (IBM FileNet Content Manager. Типовые методики и рекомендации по внедрению) и Introducing IBM FileNet Business Process Manager (EN) (Введение в IBM FileNet Business Process Manager). Эти книги выходят далеко за рамки API-интерфейсов CE и PE, которым посвящена данная серия статей, однако они чрезвычайно полезны в качестве пособий по изучению соответствующих продуктов.
  • Воспользуйтесь поиском по платформе filenet (EN), чтобы получить другие релевантные документы из серии IBM Redbooks.
  • В технической библиотеке (EN) представлен разнообразный материал от многих авторов, связанный с продуктами IBM FileNet.
  • В учебном пособии Develop applications using the IBM Enterprise Content Management Java APIs with IBM Rational Application Developer (EN) сравниваются методы кодирования при использовании API-интерфейсов для нескольких ECM-продуктов IBM, включая IBM FileNet Content Manager.
  • В статье Upgrade from FileNet P8 3.5 to 4.0: An architectural shift (EN) (Модернизация от FileNet P8 3.5 к 4.0: Архитектурные изменения) рассматриваются некоторые ключевые аспекты интеграции платформы P8 с J2EE. Без понимания архитектурной интеграции P8 с J2EE невозможна разработка специализированных решений.
  • В серии статей Build BPM applications using FileNet (EN) (Построение BPM-приложений с использованием FileNet) подробно описывается построение решения весьма распространенного типа с использованием фреймворка IBM FileNet Business Process Framework (BPF).

Получить продукты и технологии

Обсудить

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Information Management, Технология Java
ArticleID=461343
ArticleTitle=Написание впечатляющего программного кода с помощью API-интерфейсов платформы IBM FileNet P8: Приложение HelloDocument
publish-date=01132010