Хранение Java-объектов в Apache Directory Server, Часть 2

Хранение, поиск и извлечение объектов Java в ApacheDS

Во второй части своего руководства по хранению Java™объектов в Apache Сервере Каталога (ApacheDS), Билал Сиддикви представит девять примеров приложений для наглядной демонстрации понятий, описанных в Части 1. В дополнение к рассмотрению всех этапов хранения, поиска, извлечения и модификации Java-объектов с использованием ApacheDS, Билал завершает раздел Java классом многократного пользования, совмещающего эти функции при помощи компонентов LDAP схемы в ApacheDS.

Билал Сиддикви, внештатный консультант, WaxSys

Билал Сиддикви (Bilal Siddiqui) является инженером-электронщиком, консультантом по XML и соучредителем WaxSys, компании, чья деятельность направлена на упрощение электронного бизнеса. После окончания в 1995 г. Инженерно-технологического Университета, г. Лахор, и получения степени по электронной технике, он начал разрабатывать программные продукты для промышленных систем управления. В дальнейшем он занимался XML и использовал свой опыт в программировании C++ для разработки Web- и Wap-базируемых инструментов для XML-технологий, серверных парсинговых программных продуктов и служебных приложений. Билал – проповедник передовых технологий и часто публикуется в этой области.



09.02.2007

В первой половине данного раздела, я представил вам понятийный аппарат хранения Java-объектов в ApacheDS. Я объяснил корневую структуру ApacheDS и разъяснил, как она реализует службу каталога и встраиваемую поддержку протокола. Я ознакомил вас с LDAP понятиями и терминологией. Я объяснил как ApacheDS реализует LDAP протокол и рассмотрел различные компоненты, используемые для хранения и управления объектами в ApacheDS. В заключении я описал основы сериализации Java-объектов и RMI, которые необходимы для понимания прежде, чем вы начнете хранить и извлекать объекты Java в ApacheDS. Я также представил пример приложения -- систему управления данными производственной компании -- и использовал ее для демонстрации некоторых из рассматриваемых понятий.

Во второй части раздела я сосредоточу внимание прежде всего на девяти примерах. Они основаны на системе управления данными, представленной в Части 1, и способствуют получению вами навыков по хранению, поиску, извлечению и обновлению объектов Java в Apache DS.

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

Не теряйтесь!

Обратите внимание на то, что вы должны овладеть основной терминологией LDAP и понятиями, такими как Distinguished Name (DN) (Уникальное Имя), Relative Distinguished Name (RDN) (Относительное Уникальное Имя), именованный контекст, класс объекта и тип атрибута для работы с примерами в данном разделе. Если данные термины вам покажутся незнакомыми, прочтите первую часть данного руководства, прежде, чем продолжить работу.

Приложение 1. Хранение объектов Java

Я начну с нескольких приложений, демонстрирующих как хранить объекты Java в ApacheDS. Для этого вам необходимо использовать Java Naming and Directory Interface (JNDI) (Интерфейс Наименования и Каталога), который обеспечивает интерфейсы и методы для работы с объектами и атрибутами в каталоге. См. Хранение Java-объектов в Apache Сервере Каталога, Часть 1 для рассмотрения того, как ApacheDS расширяет свою службу каталога, используя JNDI интерфейс.

JNDI не является собственно интерфейсом LDAP; вы можете получить реализацию JNDI для любого типа службы каталога. Если вы хотите внедрить свою собственную службу каталога и расширить ее функциональные возможности с помощью JNDI, то вы реализуете JNDI интерфейсы для вашей службы каталога. Обратите внимание, что Java 2 Standard Edition (J2SE) поступает с клиентской JNDI реализацией для LDAP, которую вы можете использовать для сообщения с ApacheDS. Я использую клиентскую реализацию во всем тексте моего руководства.

Листинг 1 является простым приложением, именуемым StoreAlicePreferences. Я использую данное приложение для того, чтобы показать вам как хранить параметры пользователя Alice в качестве объекта Java в ApacheDS.

Листинг 1. StoreAlicePreferences
public class StoreAlicePreferences {

    public StoreAlicePreferences () 
    {
        try {
            //------------------------------------------
            //Step1: Setting up JNDI properties for ApacheDS
            //------------------------------------------
            InputStream inputStream = new FileInputStream( "ApacheDS.properties");
            Properties properties = new Properties();
            properties.load(inputStream); 
            properties.setProperty("java.naming.security.credentials", "secret");

            //------------------------------------------
            //Step2: Fetching a DirContext object
            //------------------------------------------
            DirContext ctx = new InitialDirContext(properties);

            //------------------------------------------
            //Step3: Instantiate a Java object
            //------------------------------------------
            MessagingPreferences preferences = new MessagingPreferences();

            //------------------------------------------ 
            //Step4: Store the Java object in ApacheDS
            //------------------------------------------ 
            String bindContext = "cn=preferences,uid=alice,ou=users"; 
            ctx.bind( bindContext, preferences);
        } catch (Exception e) {
            System.out.println("Operation failed: " + e);
        }
    }

    public static void main(String[] args) {
       StoreAlicePreferences storeAlicePref = new StoreAlicePreferences();
    }
}

Как вы можете увидеть, судя по моим комментариям к Листингу 1, сохранение Java-объекта в ApacheDS происходит в четыре этапа (это параметры Alice). В нижеприведенных пунктах подробно разъясняется каждый шаг.


Шаг 1. Установить JNDI свойства для ApacheDS

Первым этапом в Листинге 1 является трансформация файла свойств ApacheDS в Properties (Свойства) объект. Это означает, что вы должны сначала ввести ваши JNDI свойства в отдельный файл свойств, как показано в Листинге 2:

Листинг 2. Файл ApacheDS.properties
java.naming.factory.initial=com.sun.jndi.ldap.LdapCtxFactory
java.naming.provider.url=ldap://localhost:389/ou=system
java.naming.security.authentication=simple
java.naming.security.principal=uid=admin,ou=system

Вам необходим отдельный файл свойств, потому что приложения, использующие клиентские JNDI реализации, предназначены для работы вне зависимости от той или иной JNDI реализации. Данное Java приложение (например StoreAlicePreferences) должно быть в состоянии работать с ApacheDS или любой другой службой каталога. Службе каталога не обязательно использовать LDAP.

Значение файла свойств станет ясным, когда вы изучите простой сценарий. Предположим, вы разработали приложение Java, использующее клиентскую реализацию JNDI для LDAP для сообщения с ApacheDS. Далее вы приобретаете другой сервер каталога, который использует не LDAP, а какой-либо другой протокол.

В данном случае вам необходима новая клиентская JNDI реализация, совместимая с вашим новым сервером каталога. Ваше Java приложение продолжит работу само по себе без какой-либо перекодировки. Вы просто обновляете ваш файл со свойствами для отображения свойств вашего нового сервера каталога.

Если не существует никаких программный проблем с аппаратной кодировкой свойств в вашем приложении, то данное действие приведет к тому, что ваше приложение станет зависимым от конкретной реализации, на которую вы ее аппаратно закодировали. Это препятствует использованию JNDI, целью которой является независимость от какой-либо конкретной реализации.

Элементы файла свойств

Теперь посмотрите на ApacheDS.properties файл, показанный в Листинге 2. Файл свойств состоит из ряда пар имени-значения, где каждое имя представляет свойство.

Эти свойства используются при обработке объекта, который экспонирует JNDI интерфейс, именуемый Context (Контекст). Context является фактически самым важным интерфейсом в JNDI. Этот интерфейс определяет методы для работы с именованными контекстами. (Я ввел понятие именованных контекстов наряду с примером приложения в первой части этого раздела.)

Например, важным методом, определяемым в интерфейсе Context является bind() (связывание), которое связывает объект Java с именованным контекстом. Привязка объекта к именованному контексту означает, что вы храните ваш объект в каталоге с данным контекстом с конкретным именем. Вскоре я покажу вам, как использовать метод bind().Сначала, давайте рассмотрим пары имени-значения в файле свойств.

Пары Имя-Значение

Первой парой имени-значения в Листинге 2 является java.naming.factory.initial=com.sun.jndi.ldap.LdapCtxFactory, которая задает JNDI-свойство java.naming.factory.initial. Свойство java.naming.factory.initial указывает имя производственного объекта, которое вы используете в качестве части вашей JNDI клиентской реализации. Этот производственный объект создает объект Context, который вы используете при работе с именованными контекстами в ApacheDS.

Так как вы используете LDAP-базирующуюся реализацию JNDI, вы указываете com.sun.jndi.ldap.LdapCtxFactory в качестве значения данного свойства. Как вы можете догадаться, класс com.sun.jndi.ldap.LdapCtxFactory создает объект Context, способный сообщаться с ApacheDS в соответствии с LDAP-протоколом.

Второй парой имени-значения в Листинге 2 является java.naming.provider.url=ldap://localhost:389/ou=system. Свойство java.naming.provider.url указывает URL полного контекста каталога, в котором вы хотите работать.

Полный контекст каталога состоит из двух компонентов: URL, где ApacheDS находится в режиме ожидания, и именованного контекста в ApacheDS, в котором вы хотите работать. Последовательность ldap://localhost:389/ указывает URL, где ApacheDS находится в режиме ожидания. Оставшаяся часть последовательности (ou=system) указывает именованный контекст, где вы будете работать.

Третьей парой имени-значения является java.naming.security.authentication=simple. Это свойство указывает степень защиты, задействованной вашим ApacheDS для проверки прав доступа пользователя. Это свойство может иметь одно из трех значений: none (отсутствует), simple (простая) или strong (прочная):

  • Если вы выбираете "none," то ApacheDS не проверяет право доступа, и любой пользователь может войти в систему без указания пароля.
  • Если вы выбираете "simple," то ApacheDS осуществляет простую проверку права доступа через пароль, означающую, что пароль является открытым и доступным в сети.
  • Если вы выбираете "strong," то строгое значение пароля пользователя (вместо фактического пароля в открытой форме) передается в ApacheDS для подтверждения права доступа.

Текущая версия ApacheDS не поддерживает "strong" уровень подтверждения права доступа.

Четвертой парой имени-значения в Листинге 2 является java.naming.security.principal=uid=admin,ou=system. Это свойство указывает DN пользователя, пытающегося войти в систему ApacheDS. (Я использую DN администратора ApacheDS (uid=admin,ou=system) в качестве значения данного свойства.)

Вы увидели четыре пары имени-значения в ApacheDS.properties файле Листинга 2. Теперь вновь обратитесь к Шагу 1 в Листинге 1, в котором вы переводите файл свойств в объект Properties. Вскоре вы будете использовать этот объект Properties во время работы с JNDI.

Установка пароля пользователя

Вам также необходимо включить пароль пользователя в объект Properties. Практическое приложение, как правило, не содержит пароль пользователя в конфигурационном файле, тем не менее; пароль задается пользователем через его GUI. В Листинге 1, я установил пароль как значение свойства, именуемого java.naming.security.credentials. Это свойство фактически дает мандат для подтверждения личности пользователя. Возможны несколько типов мандатов (например, пароль или мандат Kerberos); в данном разделе я использую аутентификацию по паролю.

Теперь все свойства установлены, и объект Properties готов к работе.


Шаг 2. Извлечь объект DirContext

Далее вы реализуете класс, именуемый InitialDirContext. Этот класс является частью JNDI и экспонирует интерфейс, называемый DirContext. Конструктор InitialDirContext требует объект Properties, рассмотренный выше.

Объект InitialDirContext способен к выполнению всех операций каталога, которые вы бы хотели получить в ApacheDS, включая хранение нового объекта, поиск уже сохраненных объектов, добавление атрибутов к уже существующему объекту и другие.

Интерфейс DirContext

Интерфейс DirContext расширяет Context интерфейс. Интерфейс Context представляет именованный контекст, а интерфейс DirContext обеспечивает функциональные возможности, относящиеся к добавлению, редактированию и управлению атрибутами, ассоциируемыми с именованными контекстами.

Проще говоря, интерфейс Context обеспечивает именование функциональных возможностей, а интерфейс DirContext расширяет эти именованные функциональные возможности путем добавления поддержки для атрибутов. Именование и функциональные возможности атрибутов вместе составляют directory service (службу каталога).

Вы можете сказать, что объект InitialDirContext является wrapper (надстройкой) для объекта DirContext, реализованного заводским объектом. В данном примере InitialDirContext конструктор использует контекстный производственный объект, заданный первым свойством в Листинге 2 (это com.sun.jndi.ldap.LdapCtxFactory). Производственный объект реализует объект, экспонирующий объект DirContext, а объект InitialDirContext использует данный объект DirContext для выполнения операций каталога, как того требует клиентское приложение.

Преимущества использования ApacheDS

Главным преимуществом ApacheDS службы каталога, таким образом, является возможность сделать клиентское приложение независимым от какой бы то ни было реализации. Клиентское приложение указывает производственный объект в конфигурационном файле, а InitialDirContext объект использует производственный объект для реализации DirContext объекта, который содержит все логические структуры для осуществления сообщения с удаленным журналом каталога.

Например, Листинг 1 применяет com.sun.jndi.ldap.LdapCtxFactory заводской объект от Sun Microsystems. Данный производственный объект создает объект DirContext, способный к разработке LDAP-запросов, которые ApacheDS может распознать.

Далее, если вы хотите запустить приложение StoreAlicePreferences (из Листинга 1) с некоторыми не-LDAP службами каталога, вы можете просто заменить имя заводского объекта в Листинге 2 вашим новым заводским объектом, в соответствии с бизнес-логикой вашей не-LDAP службы. Тогда приложение StoreAlicePreferences начинает работать с вашей не-LDAP службой.


Шаг 3. Обработать Java-объект

Далее, вы реализуете класс, именуемый MessagingPreferences, показанный в Листинге 3, который отображает параметры сообщения Alice. (Вспомните мое разъяснение messaging preferences (параметров сообщения) в Части 1.)

Листинг 3. Класс MessagingPreferences
public class MessagingPreferences extends 
    Preferences implements java.io.Serializable {
    static final long serialVersionUID = -1240113639782150930L;

    //Methods of the MessagingPreferences class
}

На этот раз вы также можете вызвать методы класса MessagingPreferences для установки параметров пользователя Alice.

В Листинге 3, класс MessagingPreferences реализует Serializable интерфейс (который я представлял в "Сериализация/Упорядочивание объекта Java" пункте в Части 1), и нечто, именуемое serialVersionUID, которое я кратко поясню, прежде, чем продолжить.

Получение serialVersionUID

Желательно, чтобы все сериализуемые классы содержали приватный элемент статических данных, именуемый serialVersionUID, типа long (длительный). Вам не нужно использовать этот элемент данных повсюду в сериализуемых классах. Рабочий цикл Java использует данный элемент во время сериализации и десериализации.

Спецификация сериализации объекта Java (см. Resources (Ресурсы)) определяет комплексный алгоритм для вычисления значения serialVersionUID. Алгоритм использует имя сериализуемого класса, имена всех реализуемых им интерфейсов, все элементы данных сериализуемого класса и т.д. Вам можно не беспокоиться о деталях данного сложного алгоритма; Java-платформа предоставляет инструмент, называемый serialver, который вычисляет для вас это значение.

Для создания serialVersionUID для вашего объекта MessagingPreferences, вы можете использовать инструмент serialver из следующей строки команд:

X:\jdk1.5\bin\serialver MessagingPreferences

Как вы можете видеть, я уже делал подобное для класса MessagingPreferences, показанного в Листинге 3.


Шаг 4. Сохранить Java-объект в ApacheDS

Теперь у вас имеются установленные объекты DirContext и MessagingPreferences, готовые к работе. Все, что осталось, это использовать объект DirContext для хранения объекта MessagingPreferences в ApacheDS.

Хранение записи данных в LDAP сервере называется операцией bind (связывание). Интерфейс Context имеет метод, именуемый bind(), который вы можете использовать для хранения ваших Java-объектов в ApacheDS. Вы можете увидеть bind() в работе в Шаге 4 Листинга 1.

Установка параметров Context.bind()

Метод Context.bind() требует двух характеристик. Первая характеристика (cn=preferences,uid=alice,ou=users,ou=system) указывает именованный контекст, в котором вы хотите сохранить ваш Java-объект. Данный именованный контекст может быть разбит на две части: cn=preferences и uid=alice,ou=users,ou=system, через запятую.

Поскольку новая запись представляет рассмотренные параметры Alice, вы используете cn=preferences в качестве RDN. Обратите внимание, что последовательность uid=alice,ou=users,ou=system является такой же, как DN записи данных Alice, которую вы впервые встретили в "Создание RDN" пункте Части 1.

Основанное на всем этом DN новой записи cn=preferences,uid=alice,ou=users,ou=system, являющейся значением первой характеристики, которую вы переводите в метод bind().

Второй характеристикой метода перехода Context.bind() является объект MessagingPreferences из Шага 3. Метод bind() перехода не выдает ничего.


Запустите первое приложение!

Я соединил четыре вышеописанных шага в примере приложения, именующегося StoreAlicePreferences, показанного в Листинге 1. Вы также можете найти пример приложения в source code (код источника) для данного раздела.

Прежде, чем запустить приложение StoreAlicePreferences, вы должны иметь запись с DN равным uid=alice,ou=users,ou=system, хранящимся в ApacheDS. Вы должны были создать пользователя по имени Элис в "Создание RDN" пункте Части 1.

После запуска StoreAlicePreferences приложения, вы можете удостовериться в том, что параметры сообщения Alice были сохранены как объект Java, путем расширения записи Alice в вашем LDAP-браузере (в данном случае, JXplorer). Вы должны увидеть перспективный вид Alice, как изображено на Рисунке 1:

Рисунок 1. Параметры сообщения Alice были сохранены!
Параметры сообщения Alice были сохранены!

Примечания к приложению

Три атрибута включены наряду с объектом MessagingPreferences, изображенном на Рисунке 1. Я уже говорил об этих атрибутах -- javaClassName, javaClassNames и javaSerializedData -- в разделе "Хранение Java-объектов в ApacheDS" в Части 1.

Я не включил эти атрибуты в метод перехода bind() в приложении StoreAlicePreferences (в Шаге 4), следовательно, вы можете удивиться, как они завершились в ApacheDS. Ответ заключается в том, что метод bind() сам по себе применил эти атрибуты! Двухпараметрический Context.bind() Метод не требует никаких атрибутов. Как я уже объяснял в "Хранение Java-объектов в ApacheDS" разделе Части 1, тем не менее, LDAP нуждается в атрибутах javaClassName, javaClassNames и javaSerializedData. Следовательно, метод Context.bind() вводит эти атрибуты самостоятельно.

В следующем пункте представлен трехпараметрический метод bind(), требующий массива атрибутов и хранящий их вместе с Java-объектом.

Объект MessagingPreferences,изображенный на Рисунке 1, использует класс объекта javaContainer. Я рассматривал данный класс в "Хранение Java-объектов в ApacheDS" разделе Части 1. При желании вы можете ввести Java-объект в ApacheDS без использования класса javaContainer, как наглядно показано в следующем примере приложения.


Приложение 2. Сохранить объект Java с атрибутами

В данном примере вы научитесь добавлять атрибуты к вашему объекту Java, используя трехпараметрический метод bind(), который я упоминал ранее. Посмотрите на пример приложения, называемого StoreBobPreferences в Листинге 4. Данное приложение создает запись для пользователя по имени Боб, а также хранит параметры (т.е. атрибуты) Bob в записи, и выполняет оба этапа сразу.

Листинг 4. StoreBobPreferences
public class StoreBobPreferences{

    public StoreBobPreferences () 
    {
        try {

            //------------------------------------------         
            //Step1: Setting up JNDI properties for ApacheDS
            //------------------------------------------             
            InputStream inputStream = new FileInputStream( "apacheds.properties");
            Properties properties = new Properties();
            properties.load(inputStream); 
            properties.setProperty("java.naming.security.credentials", "secret");

            //----------------------------------------------
            //Step2: Fetching a DirContext object
            //----------------------------------------------             
            DirContext ctx = new InitialDirContext(properties);

            //----------------------------------------------
            //Step3A: Instantiate a Java Object
            //----------------------------------------------
            MessagingPreferences preferences = new MessagingPreferences();

            //----------------------------------------------
            //Step3B: Instantiate BasicAttribute object
            //----------------------------------------------
            Attribute objclass = new BasicAttribute("objectClass"); 

            //----------------------------------------------
            //Step3C: Supply value of attribute
            //----------------------------------------------
            objclass.add("person");

            //----------------------------------------------
            //Step3D: Put the attribute in attribute collection 
            //----------------------------------------------
            Attributes attrs = new BasicAttributes(true);
            attrs.put(objclass);

            //----------------------------------------------
            //Step4: Store the Java object in ApacheDS 
            //----------------------------------------------
            String bindContext = "uid=Bob,ou=users"; 
            ctx.bind( bindContext, preferences, attrs);
        } catch (Exception e) {
            System.out.println("Operation failed: " + e);
        }
    }

    public static void main(String[] args) {
       StoreBobPreferences storeBobPref = new StoreBobPreferences();
    }
}

В основном Листинг 4 состоит из тех же этапов, как и Листинг 1, за исключением дополнительного кода в Шаге 3, который я поясню ниже. (Обратите внимание, что Шаг 3A Листинга 4 является таким же, как и Шаг 3 Листинга 1, поэтому я начну с Шага 3B.)

Шаг 3B. Создать BasicAttribute

На первом этапе, который отличает приложение StoreBobPreferences от StoreAlicePreferences, вы создаете класс JNDI, именуемый BasicAttribute, который экспонирует JNDI интерфейс, называемый Attribute. Интерфейс Attribute может отображать один атрибут записи данных LDAP. Класс BasicAttribute обеспечивает основную реализацию (с ограниченными функциональными возможностями) интерфейса Attribute. Желательно наличие своей собственной реализации интерфейса Attribute (путем расширения BasicAttribute класса) у приложений и реализаций; тем не менее, класс BasicAttribute обеспечивает достаточно функциональных возможностей для наглядной демонстрации задач данного раздела.

Конструктор BasicAttribute принимает имя атрибута в качестве значения. Обратите внимание на то, что в Шаге 3B в Листинге 4 первый объект BasicAttribute который я сконструировал, принимает objectClass в качестве параметра. Это значит, что объект BasicAttribute представляет атрибут, именуемый objectClass.

Вы создаете одного представителя класса BasicAttribute для каждого из атрибутов, которые вы хотите добавить к записи данных для Боба.

Шаг 3C. Внести значения для каждого атрибута

В том случае, если у вас один объект BasicAttribute для каждого из атрибутов, которые вы хотите включить в Bob запись данных, вы вносите значения для каждого атрибута. Для внесения значения, вы вызываете add() (добавление) метод Attribute интерфейса. Метод add() принимает всего один параметр, который является последовательностью значений, которые вы хотите ввести.

Метод add() в действительности выбирает представителя Java-класса Object. Поскольку все Java-объекты являются расширениями класса Object, вы можете передать последовательность значений методами add(). Если некоторые из ваших атрибутов являются многозначными, вы можете вызывать метод add() для данного атрибута несколько раз для внесения всех необходимых значений.

Шаг 3D. Создать совокупность атрибутов

Теперь у вас имеются все атрибуты, и вы должны собрать их все вместе в совокупность объектов Attribute. JNDI обеспечивает интерфейс, именуемый Attributes, и его основную реализацию в классе, называемом BasicAttributes. Вы создаете объект BasicAttributes и вызываете его метод put() (введение) несколько раз для введения всех объектов Attribute объектов (по одному за раз) в совокупность.

Как показано в Шаге 4 Листинга 4, затем вы вызываете трехпараметрическую версию метода bind(). Трехпараметрический метод bind() схож с двухпараметрическим методом, использованном в Листинге 1, где третий параметр является совокупностью только что разработанных вами атрибутов.


Приложение 3. Сохранить сформированный Java-объект

В данном последнем упражнении по хранению Java-объектов, я покажу вам как хранить marshalled (сформированный) Java-объект, о чем я уже кратко упоминал в "Отображение сформированного Java-объекта" пункте Части 1.

Java-объект, чью скомпонованную форму вы хотите сохранить, должен быть сериализуемым (также как сериализуемый объект MessagingPreferences, созданный вами в Шаге 3 Листинга 1). Для того, чтобы наглядно это продемонстрировать, я возьму тот же объект MessagingPreferences и покажу вам, как формировать и хранить его в ApacheDS.

Сначала посмотрите на приложение StoreAlicePreferencesInMarshalledForm в Листинге 5, где показаны все этапы хранения сформированного Java-объекта в ApacheDS:

Листинг 5. StoreAlicePreferencesInMarshalledForm
public class StoreAlicePreferencesInMarshalledForm {
    public StoreAlicePreferencesInMarshalledForm () 
    {
        try {
            //------------------------------------------         
            //Step1: Setting up JNDI properties for ApacheDS
            //------------------------------------------             
            InputStream inputStream = new FileInputStream( "ApacheDS.properties");
            Properties properties = new Properties();
            properties.load(inputStream); 
            properties.setProperty("java.naming.security.credentials", "secret");

            //------------------------------------------         
            //Step2: Fetching a DirContext object
            //------------------------------------------             
            DirContext ctx = new InitialDirContext(properties);

            //---------------------------------------------
            //Step3: Instantiate a Java Object
            //---------------------------------------------
            MessagingPreferences preferences = new MessagingPreferences();
            MarshalledObject mObj= new MarshalledObject(preferences );

            //-------------------------------------------- 
            //Step4: Storing Java object in ApacheDS
            //-------------------------------------------- 
            String bindContext = "cn=marshalledPreferences,uid=alice,ou=users"; 
            ctx.bind( bindContext, mObj);
        } catch (Exception e) {
            System.out.println("Operation failed: " + e);
        }
    }

    public static void main(String[] args) {
        StoreAlicePreferencesInMarshalledForm storeAlicePref = 
            new StoreAlicePreferencesInMarshalledForm();
    }
}

J2SE осуществляет процесс формирования в скрытом режиме, поэтому приложение в Листинге 5 очень похоже на приложение StoreAlicePreferences, показанное в Листинге 1. Если вы сравните эти два приложения, то вы увидите, что там есть только одна линия дополнительного кода в Шаге 3 Листинга 5. После создания объекта MessagingPreferences, вы также создаете java.rmi.MarshalledObject, передавая объект preferences в конструктор java.rmi.MarshalledObject. Затем класс java.rmi.MarshalledObject осуществляет процесс формирования и содержит сформированную версию вашего объекта MessagingPreferences.

В Шаге 4, вы просто сохраняете (или связываете) сформированный объект вместо исходного объекта MessagingPreferences.

На этом я хочу завершить мое разъяснение хранения Java-объектов в ApacheDS. Теперь давайте рассмотрим несколько примеров, наглядно демонстрирующих поиск данных, сохраненных в ApacheDS.


Приложение 4. Поиск сохраненных данных

Я начну с простой поисковой операции в ApacheDS. Предположим, у вас имеется много пользователей, представленных в вашем представителе класса ApacheDS. Вы хотите найти все, что касается пользователя по имени Элис. Все, что вам известно об Элис, перечислено ниже:

  • Элис является пользователем, следовательно, вам нужно искать ее запись данных в рамках блока данных организации для пользователей. (Я представил понятие организационной единицы или "ou," в Части 1.)
  • Именем пользователя Элис является "alice" (без учета регистра).
  • Элис является человеком, следовательно, ее запись данных должна использовать класс объекта, который прямо или косвенно расширяет класс объекта person.

Теперь посмотрите на Листинг 6, где показан пример приложения, именуемого SearchForAlice. SearchForAlice наглядно показывает очень простой поисковый сценарий; позже я уточню данное приложение для того, чтобы охватить больше сценариев расширенного поиска.

Листинг 6. SearchForAlice
public class SearchForAlice {
    public SearchForAlice() {
        try 
        {
            //------------------------------------------         
            //Step1: Setting up JNDI properties for ApacheDS 
            //------------------------------------------             
            InputStream inputStream = new FileInputStream( "ApacheDS.properties");
            Properties properties = new Properties();
            properties.load(inputStream); 
            properties.setProperty("java.naming.security.credentials", "secret");

            //------------------------------------------         
            // Step2: Fetching a DirContext object
            //------------------------------------------             
            DirContext ctx = new InitialDirContext(properties);
    
            //---------------------------------------------
            //Step3: Setting search context
            //---------------------------------------------
            String searchContext = "ou=users";

            //-------------------------------------------- 
            //Step4: Creating search attributes for Alice
            //-------------------------------------------- 
            Attribute uid = new BasicAttribute("uid"); 
            Attribute objclass = new BasicAttribute("objectClass"); 

            //adding attribute values 
            uid.add("Alice");
            objclass.add("person");

            //Instantiate Attributes object and put search attributes in it.
            Attributes attrs = new BasicAttributes(true);
            attrs.put(uid);            
            attrs.put(objclass);

            //------------------------------------------ 
            //Step5: Executing search 
            //------------------------------------------ 
            NamingEnumeration ne = ctx.search(searchContext, attrs);
            
            if (ne != null)
            {
                //Step 6: Iterating through SearchResults
               while (ne.hasMore()) {
                    //Step 7: Getting individual SearchResult object
                    SearchResult sr = (SearchResult) ne.next();

                    //Step 8:
                    String entryRDN = sr.getName();
                    System.out.println("RDN of the Searched entry: "+entryRDN);

                    //Step 9:
                    Attributes srAttrs = sr.getAttributes();

                    if (srAttrs != null) {
                          //Step 10:
                          for (Enumeration e = attrs.getAll() ; e.hasMoreElements() ;) 
                          {
                              Attribute attr = (Attribute) e.nextElement();

                              //Step 11:
                              String attrID = attr.getID();
                              System.out.println("Attribute Name: "+attrID);
                              System.out.println("Attribute Value(s):");

                              NamingEnumeration e1 = attr.getAll();
                              while (e1.hasMore())
                                 System.out.println("\t\t"+e1.nextElement());
                          }//for()
                    }//if (srAttrs) 
               }  
            }//if (ne != null)
                 
        } catch (Exception e) {
            System.out.println("Operation failed: " + e);
        }
    }

    public static void main(String[] args) {
      SearchForAlice searchAlice = new SearchForAlice();
    }
 }

Приложение поиска в Листинге 6 состоит из 11 этапов. Вы помните первые два шага из Листинга 1, а именно загрузку JNDI параметров и создание объекта DirContext.

Припомните это во время рассмотрения параметра JNDI, именуемого java.naming.provider.url в Шаге 1 в Листинге 1, я упоминал, что обеспечение URL состоит из двух компонентов, одним из которых является контекст каталога, в котором вы хотите работать. Вы заметите, что значением java.naming.provider.url параметра в Листинге 4 является ou=system. Последовательность ou=system является контекстом каталога в котором вы хотите работать. Следовательно, поисковые операции автоматически выполняются в рамках данного контекста каталога.

Поскольку в данном примере вы выполняете операции поиска, вы можете назвать ou=system контекст каталога вашим search context (контекстом поиска). Теперь давайте посмотрим на остальные этапы приложения поиска:

  • Шаг 3. Сузьте ваш контекст поиска: Вы знаете, что Элис является пользователем, следовательно, вместо поиска Элис во всем ou=system контексте поиска, вы только ищите в организационной единицей для пользователей, которая является ou=users.
  • Шаг 4. Создайте атрибуты поиска: Та информация, которая вам известна об Элис, станет вашими атрибутами поиска. Поскольку вы знаете uid и класс объекта для Элис, вы создаете совокупность всего двух атрибутов: uid и objectClass. Вы можете видеть это в Шаге 4 Листинга 6. (Вы можете вспомнить мое разъяснение в Части 1, что uid является компонентом RDN, а не атрибутом. При указании параметров поиска, тем не менее, JNDI требование состоит в том, чтобы вы указали uid значение, как если бы это было значение атрибута.)
  • Шаг 5. Выполните поиск: Здесь вы вызываете метод search() (поиск) объекта DirContext, полученного вами в Шаге 2 Листинга 6. Метод search() требует двух параметров: первым параметром является контекст поиска, созданный вами в Шаге 3 данного упражнения, и вторым параметром является совокупность двух атрибутов в Шаге 4. Метод search() выдает объект NamingEnumeration, который содержит ваши результаты поиска.

Шаги от 1 до 5 устанавливают операцию поиска. Остальные шаги обрабатывают NamingEnumeration объект и извлекают результаты поиска.

  • Шаг 6. Извлечение результатов поиска: Объект NamingEnumeration в Шаге 6 Листинга 6 содержит совокупность результатов поиска. Каждый результат поиска в совокупности представлен объектом SearchResult. Для извлечения индивидуальных результатов поиска, вам всего лишь необходимо выполнить итерацию во всем объекте NamingEnumeration.
  • Шаг 7. Обработка индивидуальных результатов поиска: Обратите внимание, что каждый результат поиска содержит информацию об одной записи данных. Вы можете получить два сообщения (а именно, RDN и все его атрибуты) о записи данных из объекта SearchResult.
  • Шаг 8. Вызов getName(): Метод getName()SearchResult объекта извлекает RDN записи, которую вы ищете. RDN для Элис является uid=alice.
  • Шаг 9. Вызов getAttributes(): Метод getAttributes() объекта SearchResult вызывает объект Attributes, который содержит все значения атрибута, ассоциируемые с записью, которую вы ищете. Объект Attributes представляет совокупность атрибутов, похожую на совокупность атрибутов, созданную вами в Шаге 4 Листинга 6.
  • Шаг 10. Вызов getAll(): Метод getAll() объекта Attributes извлекает список, содержащий все данные атрибуты.
  • Шаг 11. Обработка атрибутов: В конце вы выбираете один атрибут из совокупности и вызываете его методы getID() и getAll(). Метод getID() извлекает имя атрибута в виде последовательности. Метод getAll() извлекает все значения атрибута в форме перечня.

Приложение 5. Поиск по имени

В предыдущем примере поиска вы увидели как искать пользователя, если вы знаете пользовательский uid. В данном примере вы научитесь модифицировать приложение для поиска Элис, используя ее имя вместо нее uid.

Припомните из Части 1, Рисунок 18, что пользовательское имя хранится как значение атрибута cn записи данных пользователя. Таким образом, в данном приложении, вы ищете атрибут cn, как показано в Листинге 7:

Листинг 7. SearchForAliceByCN
public class SearchForAliceByCN {
    public SearchForAliceByCN() {
        try 
        {
            //------------------------------------------         
            //Step1: Setting up JNDI properties for ApacheDS
            //------------------------------------------             
            InputStream inputStream = new FileInputStream( "ApacheDS.properties");
            Properties properties = new Properties();
            properties.load(inputStream); 
            properties.setProperty("java.naming.security.credentials", "secret");

            //------------------------------------------         
            // Step2: Fetching a DirContext object
            //------------------------------------------             
            DirContext ctx = new InitialDirContext(properties);
    
            //---------------------------------------------
            //Step3: Setting search context
            //---------------------------------------------
            String searchContext = "ou=users";

            //-------------------------------------------- 
            //Step4: Creating search attributes for Alice
            //-------------------------------------------- 
            Attribute cn = new BasicAttribute("cn"); 
            Attribute objclass = new BasicAttribute("objectClass"); 

            //putting attribute values 
            cn.add("Alice");
            objclass.add("person");

            //Instantiate an Attributes object and put search attributes in it 
            Attributes attrs = new BasicAttributes(true);
            attrs.put(cn);
            attrs.put(objclass);

            //------------------------------------------ 
            //Step5: Executing search
            //------------------------------------------ 
            NamingEnumeration ne = ctx.search(searchContext, attrs);
            
            if (ne != null)
            {
                //Step 6: Iterating through SearchResults
                while (ne.hasMore()) {
                    //Step 7: Getting individual SearchResult object
                    SearchResult sr = (SearchResult) ne.next();

                    //Step 8:
                    String entryRDN = sr.getName();

                    //Step 9:
                    Attributes srAttrs = sr.getAttributes();

                    if (srAttrs != null) {
                          //Step 10:
                         for (Enumeration e = attrs.getAll() ; e.hasMoreElements() ;) 
                         {
                              Attribute attr = (Attribute) e.nextElement();

                              //Step 11:
                              String attrID = attr.getID();  
                              System.out.println("Attribute Name: "+attrID);
                              System.out.println("Attribute Value(s):");

                              NamingEnumeration e1 = attr.getAll();
                                while (e1.hasMore())
                                   System.out.println("\t\t"+e1.nextElement());
                         }//for()
                    }//if (srAttrs) 
                } 
            }//if (ne != null)
                 
        } catch (Exception e) {
            System.out.println("Operation failed: " + e);
        }
    }

    public static void main(String[] args) {
        SearchForAliceByCN searchAlice = new SearchForAliceByCN();
    }
 }

Приложение SearchForAliceByCN показывает этапы поиска Элис по ее имени. Это приложение очень похоже на предыдущее приложение SearchForAlice, только с одним различием. В Шаге 4 Листинга 6, вы создали совокупность атрибутов uid и objectClass для поиска. В Шаге 4 данного приложения вы вместо этого создадите совокупность атрибутов cn и objectClass.

Что касается правил сочетаемости

Вы должны обратить внимание на один важный пункт, касающийся поиска cn. Вы видели в Части 1, Рисунок 13, что тип атрибута cn имеет поле, называемое SUBSTR, которое определяет правила сочетаемости для соответствия подпоследовательностей.

В случае атрибута cn, значением поля SUBSTR является caseIgnoreMatch, следовательно, когда вы ищите специфическое значение атрибута cn, подбор будет считаться успешным, даже если искомое имя совпадает с подпоследовательностью в значении атрибута cn. Более того, совпадение подпоследовательности не зависит от регистра.

Поэтому если вы ищете "аlice," то все пользователи с именем или фамилией "Alice" включаются в результаты поиска.


Приложение 6. Десериализовать Java-объект

Вы уже видели, как сохранить объект Java в ApacheDS и искать атрибуты, ассоциируемые с сохраненным объектом. Теперь вы узнаете, как искать и десериализовать Java-объект. Десериализация (преобразование из последовательной формы в параллельную) является противоположностью сериализации(преобразование из параллельной формы в последовательную), где вы создаете Java-объект из его сериализованной формы.

Приложение, показанное в Листинге 8, ищет и десериализует объект Alice MessagingPreferences. Вспомните, что вы сохранили объект MessagingPreferences в ApacheDS выше в Листинге 1.

Приложение FetchAliceMessagingPreferences является расширенной версией приложения SearchForAliceByCN, увиденного в Листинге 7. Фактически, Листинг 8 является таким же, как и Листинг 7 вплоть до Шага 8, где вы извлекаете RDN записи данных Alice. Вы начинаете искать объект Alice Preferences после Шага 8 в Листинге 8:

Листинг 8. FetchAliceMessagingPreferences
public class FetchAliceMessagingPreferences {
    public FetchAliceMessagingPreferences() {
        try 
        {

            //------------------------------------------         
            //Step1: Setting up JNDI properties for ApacheDS
            //------------------------------------------             
            InputStream inputStream = new FileInputStream( "ApacheDS.properties");
            Properties properties = new Properties();
            properties.load(inputStream); 
            properties.setProperty("java.naming.security.credentials", "secret");

            //------------------------------------------         
            // Step2: Fetching a DirContext object
            //------------------------------------------             
            DirContext ctx = new InitialDirContext(properties);
    
            //---------------------------------------------
            //Step3: Setting search context
            //---------------------------------------------
            String searchContext = "ou=users";

            //-------------------------------------------- 
            //Step4: Creating search attributes for Alice
            //-------------------------------------------- 
            Attribute cn = new BasicAttribute("cn"); 
            Attribute objclass = new BasicAttribute("objectClass"); 

            //putting attribute values 
            cn.add("Alice");
            objclass.add("person");

            //Instantiate an Attributes object and put search attributes in it 
            Attributes attrs = new BasicAttributes(true);
            attrs.put(cn);
            attrs.put(objclass);

            //------------------------------------------ 
            //Step5: Executing search
            //------------------------------------------ 
            NamingEnumeration ne = ctx.search(searchContext, attrs);
            
            if (ne != null)
            {
                //Step 6: Iterating through SearchResults
                while (ne.hasMore()) {
                    //Step 7: Getting individual SearchResult object
                    SearchResult sr = (SearchResult) ne.next();

                    //Step 8:
                    String entryRDN = sr.getName();
   
                    //---------------------------------------------
                    //Step9: Setting a new search context
                    //---------------------------------------------
                    searchContext = entryRDN + "," + searchContext;

                    //---------------------------------------------
                    //Step10: Creating search controls
                    //---------------------------------------------
                    SearchControls ctls = new SearchControls(); 
                    ctls.setReturningObjFlag(true);
                    ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);

                    //---------------------------------------------
                    //Step11: Creating filter
                    //---------------------------------------------
                    String filter = "(|(javaClassName=MessagingPreferences)
                                       (javaClassName=ShippingPreferences))";

                    //------------------------------------------ 
                    //Step12: Executing search
                    //------------------------------------------ 
                    NamingEnumeration ne1 = ctx.search(searchContext, filter, ctls);
            
                    if (ne != null)
                    {
                        //Step13: Iterating through SearchResults
                        while (ne1.hasMore()) {
                            //Step14: Getting individual SearchResult object
                            SearchResult sr1 = (SearchResult) ne1.next();
       
                            //Step15: Getting preferences object
                            Object obj = sr1.getObject();

                            if (obj != null) {
                               if (obj instanceof MessagingPreferences){
                                   MessagingPreferences pref = 
                                     (MessagingPreferences) obj;
                               }
                               else if (obj instanceof ShippingPreferences) {
                                  ShippingPreferences pref = (ShippingPreferences) obj;
                               }
                            }
                        }//while(   
                   }//if
                }//while
            }//if (ne != null)
        } catch (Exception e) {
            System.out.println("Operation failed: " + e);
        }
    }

    public static void main(String[] args) {
        FetchAliceMessagingPreferences searchAlicePreferences = 
             new FetchAliceMessagingPreferences();
    }
 }

Пересмотр контекста поиска

Прежде чем пройти все этапы поиска и десериализации объекта Alice MessagingPreferences, вы можете захотеть еще раз посмотреть на Рисунок 1, где показано, что объект Alice MessagingPreferences находится внутри записи данных Alice. Следовательно, вам необходимо найти MessagingPreferences объект внутри записи данных для Alice.

Как вам "заглянуть внутрь" записи данных? Именно для этих целей мы используем понятие search context (контекст поиска). В данном случае, вам понадобится сузить контекст поиска, который я представил в первом приложении поиска, SearchForAlice, показанном в Листинге 6.

Вы делаете это в Шаге 9 Листинга 8, где вы соединяете RDN Alice (uid=alice) с исходным контекстом поиска, который вы использовали для поиска записи данных Alice (ou=users,ou=system). Полученный контекст поиска (uid=alice,ou=users,ou=system) дает возможность заглянуть внутрь записи данных Alice.

Теперь давайте пройдем оставшиеся этапы приложения FetchAliceMessagingPreferences.

Построение и использование средств управления поиском

В Шаге 10 Листинга 8, вы создаете SearchControls объект, который вы затем можете использовать для построения средств управления поиском. Средства управления поиском используются для двух основных задач:

  • Для указания типа данных, содержащихся в результатах поиска. В данном случае вы хотите получить объект Java, следовательно вы вызываете метод setReturningObjFlag() объекта SearchControls. Данный метод устанавливает метку в средствах управления поиском для указания того, что поиск проводится для извлечения объекта.
  • Для указания области поиска. "Scope of search" (область поиска); означает то, что вы хотите искать среди определенных записей данных или же вы также хотите искать на одном и более уровнях, расположенных ниже записи. Вы устанавливаете область поиска путем вызова метода setSearchScope() объекта SearchControls.

Фильтрация результатов поиска

В Шаге 11 Листинга 8, вы создаете последовательность, именуемую "filter." (фильтр); Вы можете видеть, что значением последовательности фильтра является (|(javaClassName=MessagingPreferences)(javaClassName=ShippingPreferences)). Две пары атрибут-значение в скобках указывают различные значения для одного атрибута, именуемого javaClassName. Также обратите внимание на "OR" перед двумя парами атрибут-значение. Это значит, что вы ищете либо объект MessagingPreferencesлибоShippingPreferences.

Данная последовательность фильтра функционирует как фильтр для результатов поиска. Это значит, что результаты поиска, извлеченные после операции поиска, будут содержать только те результаты, которые соответствуют критериям, указанным в поисковом фильтре.

Вы используете данный тип поискового фильтра при поиске любого количества значений атрибута. Для получения более детальной информации по поисковым фильтрам, см. Resources пункт, где имеется ссылка на раздел, вводящий понятие поисковых фильтров в LDAP приложениях.

Извлечение и обработка результатов поиска

В Шаге 12 Листинга 8, вы вызываете метод search() (поиск), проходя контекст поиска, поисковый фильтр и средства управления поиском вместе с вызовом метода.

Обратите внимание на разницу между вызовом метода поиска, использованном в Шаге 5 Листинга 6 и тем, который использовался в Листинге 8: В Листинге 6, вы используете двухпараметрическую форму метода search(), а в Листинге 8 вы используете трехпараметрическую переопределенную форму того же метода.

Шаги 13 и 14 Листинга 8 являются такими же, как и Шаги 6 и 7 Листинга 7, соответственно. На данных этапах вы обрабатываете объекты NamingEnumeration и извлекаете индивидуальные результаты поиска в форме объекта SearchResult.

В завершение, в Шаге 15, вы вызываете метод getObject() объекта SearchResult. Метод getObject() получает Java-объект.

Вы не можете быть уверены в классе, чей представитель был извлечен методом getObject(), поскольку вы указываете два класса (MessagingPreferences или ShippingPreferences) в запросе поиска. Полученный результат может являться представителем любого из двух классов. В итоге вам необходимо сперва проверить, чьим представителем является объект и затем привести объект в соответствие. Как только вы проделали это, объект Java находится под вашим контролем, и вы можете вызывать его методы.


Приложение 7. Расформирование Java-объекта

В предыдущем приложении вы научились десериализовывать сериализованный Java-объект. Далее, в приложении FetchAliceMarshalledPreferences, вы научитесь расформировывать сформированный объект Java.

Листинг 9. FetchAliceMarshalledPreferences
public class FetchAliceMarshalledPreferences {
    public FetchAliceMarshalledPreferences() {
        try 
        {
            //------------------------------------------         
            //Step1: Setting up JNDI properties for ApacheDS  
            //------------------------------------------             
            InputStream inputStream = new FileInputStream( "ApacheDS.properties");
            Properties properties = new Properties();
            properties.load(inputStream); 
            properties.setProperty("java.naming.security.credentials", "secret");

            //------------------------------------------         
            // Step2: Fetching a DirContext object
            //------------------------------------------             
            DirContext ctx = new InitialDirContext(properties);
    
            //---------------------------------------------
            //Step3: Setting search context
            //---------------------------------------------
            String searchContext = "ou=users";

            //-------------------------------------------- 
            //Step4: Creating search attributes for Alice
            //-------------------------------------------- 
            Attribute cn = new BasicAttribute("cn"); 
            Attribute objclass = new BasicAttribute("objectClass"); 

            //putting attribute values 
            cn.add("Alice");
            objclass.add("person");

            //Instantiate an Attributes object and put search attributes in it 
            Attributes attrs = new BasicAttributes(true);
            attrs.put(cn);
            attrs.put(objclass);

            //------------------------------------------ 
            //Step5: Executing search
            //------------------------------------------ 
            NamingEnumeration ne = ctx.search(searchContext, attrs);
            
            if (ne != null)
            {
                //Step 6: Iterating through SearchResults
                while (ne.hasMore()) {
                    //Step 7: Getting individual SearchResult object
                    SearchResult sr = (SearchResult) ne.next();

                    //Step 8:
                    String entryRDN = sr.getName();
                    System.out.println("RDN of the Searched entry: "+entryRDN);
   
                    //---------------------------------------------
                    //Step9: Setting a new search context
                    //---------------------------------------------
                    searchContext = entryRDN + "," + searchContext;

                    System.out.println("new SearchContext: "+searchContext);

                    //---------------------------------------------
                    //Step10: Creating search controls
                    //---------------------------------------------
                    SearchControls ctls = new SearchControls(); 
                    ctls.setReturningObjFlag(true);
                    ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);

                    //---------------------------------------------
                    //Step11: Creating filter
                    //---------------------------------------------
                    String filter = "(javaClassName=java.rmi.MarshalledObject)";

                    //------------------------------------------ 
                    //Step12: Executing searchl
                    //------------------------------------------ 
                    NamingEnumeration ne1 = ctx.search(searchContext, filter, ctls);
            
                    if (ne != null)
                    {
                        //Step13: Iterating through SearchResults
                        while (ne1.hasMore()) {
                            //Step14: Getting individual SearchResult object
                            SearchResult sr1 = (SearchResult) ne1.next();

                            //Step15: Getting preferences object
                            Object obj = sr1.getObject();
        
                            if (obj instanceof MarshalledObject) {
                                MarshalledObject mObj= (MarshalledObject) obj;
                                Object obj1 = mObj.get();

                                if (obj1 instanceof MarshalledObject) {
                                    MessagingPreferences pref = 
                                      (MessagingPreferences) obj;
                                }
                                else if (obj1 instanceof ShippingPreferences) {
                                    ShippingPreferences pref = 
                                      (ShippingPreferences) obj;
                                }
                            }//if(obj)
                            
                       }//while
                   }//if
                  }//while
            }//if (ne != null)
                 
        } catch (Exception e) {
            System.out.println("Operation failed: " + e);
        }
    }

    public static void main(String[] args) {
        FetchAliceMarshalledPreferences fetchAlicePref = 
            new FetchAliceMarshalledPreferences();
    }
 }

Листинг 9 состоит из 15 этапов, первые 14 являются такими же, как и первые 14 шагов, отмеченных в Листинге 8. Единственное различие между двумя приложениями состоит в Шаге 15 Листинга 9, где метод SearchResult.getObject() извлекает сформированный объект. Вы можете убедиться в этом сами, сверив имя класса извлеченного объекта. После проверки, вы приводите объект к типу MarshalledObject.

Сериализованная форма вашего Java-объекта хранится внутри данного сформированного объекта, следовательно, теперь вы можете вызвать метод get() класса MarshalledObject для извлечения Java-объекта, который вы можете привести к типу после подтверждения его класса.


Приложение 8. Редактирование и обновление сохраненных объектов

В предыдущих примерах я в основном обращал внимание на хранение и поиск Java-объектов. В следующем приложении вам будет показано как редактировать и обновлять Java-объект, уже хранящийся в ApacheDS.

Приложение UpdateAlicePreferences в Листинге 10 обновляет Java-объект, который вы ранее совместили с классом объекта person в Листинге 4:

Листинг 10. UpdateAlicePreferences
public class UpdateAlicePreferences {
    public UpdateAlicePreferences() {
        try
        {
            //------------------------------------------         
            //Step1: Setting up JNDI properties for ApacheDS
            //------------------------------------------             
            InputStream inputStream = new FileInputStream( "ApacheDS.properties");
            Properties properties = new Properties();
            properties.load(inputStream); 
            properties.setProperty("java.naming.security.credentials", "secret");

            //------------------------------------------         
            // Step2: Fetching a DirContext object
            //------------------------------------------             
            DirContext ctx = new InitialDirContext(properties);
    
            //---------------------------------------------
            //Step3: Setting search context
            //---------------------------------------------
            String searchContext = "ou=users";

            //-------------------------------------------- 
            //Step4: Creating search attributes for Alice
            //-------------------------------------------- 
            Attribute cn = new BasicAttribute("cn"); 
            Attribute objclass = new BasicAttribute("objectClass"); 

            //putting attribute values 
            cn.add("Alice");
            objclass.add("person");

            //Instantiate an Attributes object and put search attributes in it 
            Attributes attrs = new BasicAttributes(true);
            attrs.put(cn);
            attrs.put(objclass);

            //------------------------------------------ 
            //Step5: Executing search
            //------------------------------------------ 
            NamingEnumeration ne = ctx.search(searchContext, attrs);
            
            if (ne != null)
            {
                //Step 6: Iterating through SearchResults
                while (ne.hasMore()) {
                    //Step 7: Getting individual SearchResult object
                    SearchResult sr = (SearchResult) ne.next();

                    //Step 8:
                    String entryRDN = sr.getName();
   
                    //---------------------------------------------
                    //Step9: Setting a new search context
                    //---------------------------------------------
                    searchContext = entryRDN + "," + searchContext;

                    //---------------------------------------------
                    //Step10: Creating search controls
                    //---------------------------------------------
                    SearchControls ctls = new SearchControls(); 
                    ctls.setReturningObjFlag(true);
                    ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);

                    //---------------------------------------------
                    //Step11: Creating filter
                    //---------------------------------------------
                    String filter = "(javaClassName=java.rmi.MarshalledObject)";

                    //------------------------------------------ 
                    //Step12: Executing search
                    //------------------------------------------ 
                    NamingEnumeration ne1 = ctx.search(searchContext, filter, ctls);
            
                    if (ne != null)
                    {
                        //Step13: Iterating through SearchResults
                        while (ne1.hasMore()) {

                            //Step14: Getting individual SearchResult object
                            SearchResult sr1 = (SearchResult) ne1.next();
       
                            //Step15: Getting preferences object
                            Object obj = sr1.getObject();
                            entryRDN = sr1.getName();

                            if (obj != null && obj instanceof MarshalledObject)
                            {
                                MarshalledObject mObj= (MarshalledObject) obj;
                                MessagingPreferences pref = 
                                  (MessagingPreferences) mObj.get();

                                //Step16: Updating your java object
                                pref.setStyles("http://www.mystyles.com/preferences");

                                //Step17: Setting a new context for data updation
                                String bindContext = entryRDN + "," + searchContext;

                                //Step18: Reading attributes in a new collection
                                Attributes attrs1 = sr1.getAttributes();

                                //Step19:Updating data
                                ctx.rebind(bindContext, pref, attrs);
                                System.out.println("Updating 
                                  the MessagingPreferences succeed");
                           } 
                        }//while
                    }//if  
                }//while
            }//if (ne != null)
                 
        } catch (Exception e) {
            System.out.println("Operation failed: " + e);
        }
    }

    public static void main(String[] args) {
        UpdateAlicePreferences updateAlicePreferences = 
            new UpdateAlicePreferences();
    }
 }

Первые 15 шагов Листинга 10 уже должны быть вам знакомы из предыдущих упражнений. На данных этапах вы просто ищете и извлекаете Java-объект, который вы хотите обновить. На следующих этапах вы обновляете и храните этот объект.

Обновление и хранение Java-объекта

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

  • Получить именованный контекст: Обновление существующей записи означает, что вы хотите внести ваши обновленные данные в тот же именованный контекст, в котором они раньше были записаны. Для этого вам необходимо знать именованный контекст обновляемой записи. Именованный контекст Java-объекта может быть сформирован путем конкатенирования контекста поиска (который вы использовали для поиска вашего объекта) с RDN объекта. Вы можете увидеть это в Шаге 17 Листинга 10.
  • Ввести все атрибуты в обновленный Java-объект: Когда вы вводите обновленный Java-объект обратно в ApacheDS, он вносится как новая запись данных того же именованного контекста. Все атрибуты, ассоциируемые с записью данных, теряются. Следовательно, вам необходимо ввести все атрибуты вашей записи данных обратно в обновленный Java-объект. В Шаге 18 Листинга 10, я осуществил это путем считывания всех атрибутов результата поиска и их упаковки в совокупность атрибутов.

В итоге в Шаге 19, вы вызываете метод DirContext.rebind(). Метод rebind() (развязывание) требует точно таких же параметров, что и трехпараметрический метод bind() в Шаге 4 Листинга 4, в приложении StoreBobPreferences.

Единственное различие между методами bind() и rebind() состоит в том, что метод rebind() хранит запись данных в именованном контексте уже занятом существующей записью данных, фактически обновляя существующую запись новыми данными.


Приложение 9. Взаимное/совместное координирование

В завершение данного раздела я приведу заключительное приложение, соединяющее все понятия, с которыми вы ознакомились, в простом объекте многократного пользования, который может хранить, искать, удалять и обновлять Java-объекты в ApacheDS. Класс LDAP4JavaObjects показан в Листинге 11:

Листинг 11. LDAP4JavaObjects
public class LDAP4JavaObjects {

    protected String commonName = null;
    protected String surName = null;
    protected String userID  = null;
    protected String javaObjectCN = null;
    protected String userName = null;
    protected String password = null;
    protected String initialContext = null;
    private String workingContext = null;    
    protected DirContext dirContext = null;
    protected Properties properties = null;
    protected Object javaObject = null;
    protected Attributes attributes = null;

    public LDAP4JavaObjects() {
        properties = new Properties();
        attributes = new BasicAttributes(true);

        workingContext = "ou=users";
        initialContext = workingContext;
        
        try { 
            InputStream inputStream = new FileInputStream( "ApacheDS.properties");
            properties.load(inputStream); 
        } catch (Exception e) {
            e.printStackTrace();
        }
    }//LDAPJavaObjects
    
    public void setUserName(String userName){
       properties.setProperty("java.naming.security.principal", userName); 
       this.userName = userName; 
    } 

    public void setPassword(String password) {
       properties.setProperty("java.naming.security.credentials", password); 
       this.password = password;
    }

    protected void connect() {
        try {
            // Fetch the directory context.
            dirContext= new InitialDirContext(properties);
        } catch (NamingException e) {
            System.out.println("Getting initial context operation failed: " + e);
        }
    }
    
    protected void close() {
        try {
            // Close the directory context.
            dirContext.close();
        } catch (NamingException e) {
            System.out.println("Closing initial context operation failed: " + e);
        }
    }
    
    public void setJavaObject(Object obj) throws java.io.NotSerializableException {
       if (obj instanceof java.io.Serializable)
           javaObject = obj;
    }

    protected boolean isObjectPresent(String uid, String cn, String workContext) {
        NamingEnumeration ne = search(uid, cn, workContext);
        if (ne != null)
           return true;
        else
           return false;
    }//isObjectPresent

    protected NamingEnumeration search(String user, String object, String workContext) {

        NamingEnumeration  ne = null;
        String filter =  new String();
        SearchControls ctls = new SearchControls(); 
        ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
        
        try {
            if (user != null && object == null) {
                addUserAttribute ("uid", user);
                addUserAttribute ("objectClass", "person"); 
                ne =  dirContext.search(workContext, attributes);
                if (ne != null && ne.hasMore())
                    return ne;
                else
                    return null;
            }else if (user == null &&  object != null)
            {
                filter = "(cn="+object+")";
                ctls.setReturningObjFlag(true);
                ne =  dirContext.search(workContext, filter, ctls);

                if (ne != null && ne.hasMore())
                   return ne;
                else
                    return null;
            }
        } catch (NamingException e) {
            e.printStackTrace();
        }
        return null;
    }//search
    
    protected void update(String uid, String cn, String workContext) {
        if (uid != null && cn == null)
        {
           String[] objClassValues = {"top","person"};
           addUserAttribute ("uid", uid);

           if (commonName != null)
               addUserAttribute ("cn", commonName);

           addUserAttributes ("objectClass", objClassValues);

           try  {
               dirContext.rebind(workContext, null, attributes);
           } catch (NamingException e) {
               System.out.println("Storing  operation failed: " + e);
           }
        } else if (uid == null && cn != null)  {
            addUserAttribute ("cn", cn);
            
            try  {
                dirContext.rebind(workContext, javaObject, attributes);
            } catch (NamingException e) {
                e.printStackTrace();
            }
        }
    }//update
    
    
    protected void store(String uid, String cn, String workContext) {
        if (uid != null && cn == null)
        {
            String[] objClassValues = {"top","person"};
            addUserAttribute ("uid", uid);

            if (commonName != null)
                addUserAttribute ("cn", commonName);

            addUserAttributes ("objectClass", objClassValues);
            try  
            {
               dirContext.bind(workContext, null, attributes);
            } catch (NamingException e) {
               e.printStackTrace();
            }
        } else if (uid == null && cn != null) {
            addUserAttribute ("cn", cn);    
            try  {
                dirContext.bind(workContext, javaObject, attributes);
            } catch (NamingException e) {
                e.printStackTrace();
            }
        }
    }//store

    
    public void addUserAttribute (String name, String value){
        Attribute attr = new BasicAttribute(name); 
        attr.add(value);
        attributes.put(attr);
    }//addUserAttribute


    public void addUserAttributes(String name, String[] value) {
        Attribute attr = new BasicAttribute (name);
        if (value != null) {
            for (int i =0; i < value.length; i++)
                attr.add(value[i]);
        }
        attributes.put(attr);
    }//addUserAttribute
    
    public void writeToServer () {
        connect();

        if (userID == null && commonName != null)
            userID = commonName;

        //Check if the cn of the Java object is set.
        //If not, use commonName as object cn.
        if (javaObject != null)
        {
            if (javaObjectCN == null && commonName != null)    
                javaObjectCN = commonName;
        }

       if (!(isObjectPresent(userID, null, initialContext))) 
       {
          workingContext = "uid="+userID+","+initialContext;
          store(userID, null, workingContext);
          workingContext = "cn="+javaObjectCN +",uid="+userID+","+initialContext;
          store( null, javaObjectCN, workingContext);
       } else if (isObjectPresent(null, javaObjectCN, 
         "uid="+userID+","+initialContext)) { 
          workingContext = "cn="+javaObjectCN +",uid="+userID +","+ initialContext;
          update(null, javaObjectCN, workingContext);
       } else {
          workingContext = "cn="+javaObjectCN +",uid="+userID +","+ initialContext;
          store(null, javaObjectCN, workingContext);
       }
       close();
    }//writeToServer()


    public void searchFromServer(String user, String object) {
        connect();
        NamingEnumeration ne = search(user, object, initialContext);

        if (ne != null)
            processSearchResult(ne);
        else
            System.out.println ("searchFromServer... failed");
        close();
    }//searchFromServer

    private void processSearchResult (NamingEnumeration ne){
        try {
            if (ne!=null)
            {      
                while (ne.hasMore()) {
                    SearchResult sr = (SearchResult) ne.next();
                    Object obj = sr.getObject();

                    if (obj != null) {
                        javaObject = obj;
                        javaObjectCN = sr.getName();
                    } else {
                        userID = sr.getName();
                        processAttributes(sr.getAttributes());
                    }    
                }//while
            }//if (ne != null)
        } catch (javax.naming.NamingException e) {
            e.printStackTrace();
        }
    }//processSearchResult

    private void processAttributes(Attributes attrs){
        try {
            for (Enumeration e = attrs.getAll() ; e.hasMoreElements() ;) {
                Attribute attr = (Attribute) e.nextElement();
                if (attr.getID().equals("cn"))
                    commonName = (String)attr.get();
                else if (attr.getID().equals("sn"))
                    surName = (String)attr.get();
            }
        } catch (javax.naming.NamingException ne){
            ne.printStackTrace();
        }
    }//processAttributes

    public void setUserID(String userID) {
        this.userID = userID;
    }

    public String getUserID() {
        return userID;
    }

    public void setJavaObjectCN(String objectCN) {
        this.javaObjectCN = objectCN;
    }

    public String getJavaObjectCN() {
        return javaObjectCN;
    }

    public void setCommonName (String cn) {
       commonName = cn;
    }

    public void setSurName (String sn) {
       surName = sn;
    }

    public String getCommonName() {
        return commonName;
    }

    public String getSurName() {
        return surName;
    }
    
    public static void main(String[] args) {
        LDAP4JavaObjects javaLdapClient = new LDAP4JavaObjects();
        javaLdapClient.setPassword("secret");
        javaLdapClient.setUserID("Alice");

        //Instantiating a Java object.
        MessagingPreferences msgPreferences =
            new MessagingPreferences ();
      
        //Setting a Java object.
        try {
            javaLdapClient.setJavaObject (msgPreferences);
        } catch (Exception e) {
            e.printStackTrace();
        }
                    
        javaLdapClient.setJavaObjectCN ("preferences");
        javaLdapClient.writeToServer();
        System.out.println ("Entry stored in ApacheDS..");        
    }//main
 }

Примечания к приложению

Первое, что вы заметите относительно класса LDAP4Java, это то, что он содержит методы управления записью данных для пользователя, которые принадлежит к классу объекта person. Методы setUserName() и setPassword() устанавливают имя пользователя и пароль для приложения.

После установки адреса сервера вы можете использовать такие методы настройки как setCommonName() и setSurName(). Данные методы настройки устанавливают значения атрибута пользователя. В дополнение к методам setCommonName() и setSurName(), LDAP4JavaObjects также содержит метод addUserAttribute(). Вы можете использовать метод addUserAttribute() для указания дополнительных атрибутов пользователя.

После установки значений атрибута вы можете вызвать метод setJavaObject() для установки вашего Java-объекта в LDAP4JavaObjects. Вы можете использовать метод setContext() для установки контекста сервера, в котором вы хотите вводить или искать объект Java.

После введения всех требуемых данных в LDAP4JavaObjects, вы можете вызвать метод writeToServer(). Данный метод является интеллектуальным. Сначала он выясняет, присутствует ли запись пользователя в удаленном сервере или нет. Если он не может найти запись, то он вводит новую. Если же запись уже существует в сервере, то он ее обновляет.

LDAP4JavaObjects также имеет метод, именуемый searchFromServer(), который вы можете использовать для поиска данных пользователя или Java-объектов в ApacheDS. Метод searchFromServer() обновляет LDAP4JavaObjects элементы данных после операции поиска.

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

Вы можете запустить метод main() в Листинге 11, если вы хотите увидеть наглядную демонстрацию использования всех упомянутых методов LDAP4JavaObjects.


Выводы к Части 2

В данном разделе, состоящем из двух частей, вы узнали, как ApacheDS обеспечивает серверные функциональные возможности LDAP. В дополнение к основному рассмотрению структуры журнала каталога ApacheDS и того, как он функционирует в качестве LDAP-сервера, я привел девять примеров приложений, наглядно демонстрирующих как хранить, искать, извлекать и обновлять объекты Java в ApacheDS.

В complete source code (полный исходный код) данного раздела включены примеры JNDI кода, который вы можете использовать для работы с вашими объектами Java в ApacheDS, а также класс многократного использования, содержащий все рассмотренные функциональные возможности. Вы можете использовать данный класс для получения практических навыков по обработке объектов Java в ApacheDS, а также вы можете расширить его в ваших собственных Java-приложениях.

См. Resources (Ресурсы) для получения дополнительной информации об ApacheDS и сопутствующих технологиях, описанных в данном руководстве.


Загрузка

ОписаниеИмяРазмер
Source codej-apachedssource.zip40KB

Ресурсы

Научиться

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

Обсудить

Комментарии

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=Технология Java, Open source
ArticleID=194169
ArticleTitle=Хранение Java-объектов в Apache Directory Server, Часть 2
publish-date=02092007