IBM®
Перейти к тексту
    в России и странах СНГ [изменить]    Условия использования
 
 
   
    Главная страница    Продукты    Услуги и решения    Поддержка и загрузка    Мой профиль    
Перейти к тексту

developerWorks Россия  >  Lotus | SOA и Web-сервисы  >

Практическое использование Web-сервисов в IBM Lotus Domino 7: Создание сложных Web-сервисов

developerWorks
Опции документа

Опции документа, требующие включения JavaScript, не отображаются

Обсудить

Исходные тексты примера


Выскажите мнение об этой странице

Помогите нам улучшить содержание


Уровень сложности: сложный

Джулиан Робиччо, разработчик, консультант

26.11.2007

В третьей и последней статье серии о Web-сервисах IBM Lotus Domino мы обсудим более продвинутые средства использования Web-сервисов Domino, такие как сложные типы данных, перечисления, прикрепленные файлы и пользовательские отказы

В первой статье этой серии, "Практическое использование Web-сервисов в IBM Lotus Domino 7: Что такое Web-сервисы и почему они важны?", освещаются основы Web-сервисов и терминологии SOA, а во второй статье, "Практическое использование Web-сервисов в IBM Lotus Domino 7: Создание и тестирование простых Web-сервисов", больше внимания уделено деталям создания и тестирования простых Web-сервисов в Lotus Domino V7. В третьей и последней статье в этой серии мы обсудим средства для написания более сложных (и более полезных) Web-сервисов в Lotus Domino V7.

Примеры, обсуждаемые в данной статье, находятся в базе данных Notes, которую вы можете загрузить (см. раздел загрузка в конце статьи). Как и в предыдущих частях, все примеры кода в статье реализованы на языке LotusScript. Однако в базе данных, которая сопровождает статью, те же примеры реализованы и на Java. База данных также содержит несколько примеров агентов, которые можно использовать для обращения к Web-сервисам и их тестирования.

О сложных типах данных

Ранее мы имели дело только с простыми типами параметров и возвращаемых значений: строками, целыми числами, простыми массивами и тому подобным. Было даже показано, как возвращать множество значений с помощью параметров ввода/вывода (InOut). Однако при разработке более сложного Web-сервиса очень скоро вы можете оказаться в такой ситуации, когда вам нужно будет отправлять и получать целые структуры данных. Для этого часто используют XML документы, например:


Листинг 1. Пример XML структуры book
                
<book>
    <author>Barry Allen</author>
    <title>Life in the Fast Lane</title>
    <booktype>Biography</booktype>
</book>

Используя подобную структуру, вы легко сможете пересылать объекты <book> целиком в качестве параметров или ответов сервера. Следующим образом можно создавать даже вложенные структуры:


Листинг 2. Пример структуры XML bookshelf
                
<bookshelf>
    <shelfnumber>1</shelfnumber>
    <location>JLA Main Office</location>
    <book>
        <author>Barry Allen</author>
        <title>Life in the Fast Lane</title>
        <booktype>Biography</booktype>
    </book>
    <book>
        <author>Bruce Wayne</author>
        <title>Dark Times</title>
        <booktype>Reference</booktype>
    </book>
</bookshelf>

В LotusScript для этих целей используются специальные (custom) типы или классы. В Web-сервисах используются объекты, называемые сложными типами данных (complex data type).

Убедитесь, что свойства сложного типа даных перечислены в алфавитном порядке
Убедитесь, что свойства в ваших сложных типах данных LotusScript в алфавитном порядке. Мы не уверены, но возможно с некоторыми клиентами (с Apache Axis и Apache SOAP, например) могут быть проблемы, если свойства сложного типа данных LotusScript расположены не в алфавитном порядке. В результате некоторые элементы данных сложного типа могут теряться как в запросе клиента, так и в ответе сервера.

Сложный тип данных в Web-сервисе на LotusScript представляет собой просто public-класс с одним или несколькими public-свойствами. Каждое public-свойство ведет себя как элемент сложного типа данных. Например, для того, чтобы описать структуру <book> классом в LotusScript, можно использовать следующий код:


Листинг 3. Пример LotusScript-класса book.
                
Class Book
	Public author As String
	Public booktype As String
	Public title As String
End Class

В WSDL-файле это будет выглядеть как структура следующего вида:


Листинг 4. Пример WSDL-определения сложного типа данных для LotusScript-класса book
                
<complexType name="BOOK">
    <sequence>
        <element name="AUTHOR" type="xsd:string"/>
        <element name="BOOKTYPE" type="xsd:string"/>
        <element name="TITLE" type="xsd:string"/>
    </sequence>
</complexType>

Обратите внимание на то, что в WSDL-файле имена типов данных и свойств переводятся в верхний регистр. Lotus Domino так преобразует Web-сервисы, написанные на LotusScript, поскольку Web-сервисы чувствительны к регистру в отличие от LotusScript.

Используя сложные типы данных в LotusScript Web-сервисах, можно создавать методы с применением сигнатур подобного рода:


Листинг 5. Пример LotusScript-методов, использующих класс book
                
Public Function findTitle (searchString As String) As Book
Public Sub uploadNewBook (newBook As Book)

Эти методы гораздо удобнее в использовании, нежели методы с большим числом передаваемых и возвращаемых параметров.



В начало


Web-сервис Bookstore: пример базы данных

К статье прилагается пример базы данных книжного магазина (см. ссылку в разделе загрузка). С помощью Web-сервисов база данных позволяет искать, скачивать и загружать на сервер файлы с оглавлениями книг. База данных заполнена небольшим количеством общедоступных файлов eText с сайта Проект Гутенберг.

На рисунке 1 показан пример документа из базы данных.


Рисунок 1. Документ из учебной базы данных Bookstore (Книжный магазин)
Документ из учебной базы данных Bookstore

Как вы видите, у каждой книги есть название, автор, жанр, описание и прикрепленный файл.

В базе данных два LotusScript Web-сервиса:

  • BookSearch. Позволяет осуществлять поиск книг, возвращает название, автора, жанр и описание книги.
  • BookDownloadUpload. Позволяет получать информацию о книге, включая прикрепленный файл. Также позволяет загружать новые файлы с книгами.

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



В начало


Как работает Web-сервис BookSearch

Web-сервис BookSearch позволяет искать книги и получать простую информацию о них, такую как название, автор, жанр и описание. Для методов, которые всегда возвращают информацию об одной книге, в качестве типа возвращаемого значения используется сложный тип данных BookInfo. Для методов, которые могут возвращать информацию об одной или нескольких книгах, используется сложный тип данных BookInfoArray, содержащий массив объектов BookInfo и элемент, показывающий количество элементов в массиве.

Общая структура класса BookInfo в LotusScript такова:


Листинг 6. LotusScript-класс BookInfo
                
Class BookInfo
	Public author As String
	Public description As String
	Public fileName As String
	Public noteID As String
	Public title As String
	Public typeOfBook As String
End Class

У нас есть некоторое количество элеменов данных, которые отображаются в поля базы данных (или в информацию о документе book в базе данных). Для упрощения доступа клиента к элементу title (название книги) в полученном массиве BookInfoArray, который содержит несколько таких элементов, мы возвращаем NoteID документа.

Вспомогательный метод внутри класса BookInfo:


Листинг 7. Метод getDocContents внутри LotusScript-класса BookInfo
                
Public Function getDocContents (doc As NotesDocument) As Integer
	title = doc.GetItemValue(TITLE_FIELD)(0)
	description = doc.GetItemValue(DESCRIPTION_FIELD)(0)
	author = doc.GetItemValue(AUTHOR_FIELD)(0)
	typeOfBook = doc.GetItemValue(BOOK_TYPE_FIELD)(0)
	noteID = doc.NoteID
	'** firstAttachmentFileName является пользовательской функцией
	'** для получения имени прикрепленного к документу файла
	fileName = firstAttachmentFileName(doc, ATTACHMENT_FIELD)
	getDocContents = True
End Function

Данная функция нужна для извлечения объекта NotesDocument из базы данных и для записи информации о нем в public-свойства объекта BookInfo. Заметьте: несмотря на то, что этот метод является public-методом, он не работает в сложном типе данных повсеместно. Сложному типу данных доступны только public-свойства.

Общая структура объекта BookInfoArray:


Листинг 8. LotusScript-класс BookInfoArray
                
Class BookInfoArray
	Public bookArray() As BookInfo
	Public count As Integer
End Class

Свойство bookArray представляет собой массив объектов BookInfo, а свойство count говорит о том, сколько элементов должно содержаться внутри bookArray. Включать свойство count не обязательно, однако, как мы думаем, очень полезно, поскольку с ним клиентам Web-сервисов будет проще определить пуст массив или нет. Они могут просто проверить, равен ли count нулю (массив пуст) или положителен (массив не пуст). С другой стороны, можно запутаться, является ли пустой массив нулевым (null) объектом, массивом без элементов или массивом из единственного элемента, содержащего нулевые (null) или пустые значения.

Класс BookInfoArray также имеет вспомогательный метод:


Листинг 9. Метод setArrayFromCollection внутри LotusScript-класса BookInfoArray
                
Public Sub setArrayFromCollection (dc As NotesDocumentCollection)
	count = dc.Count
	If (count = 0) Then
		Redim bookArray(0)
	Else
		Redim bookArray(count - 1)
		
		Dim doc As NotesDocument
		Dim dcCount As Integer
		Set doc = dc.GetFirstDocument
		Do Until (doc Is Nothing)
			Dim book As New BookInfo
			Call book.getDocContents(doc)
			Set bookArray(dcCount) = book
			Set doc = dc.GetNextDocument(doc)
			dcCount = dcCount + 1
		Loop
	End If
	
End Sub

Почти как и метод getDocContents в классе BookInfo, этот метод берет коллекцию NotesDocumentCollection, состоящую из документов Book, хранящихся в базе данных, и добавляет каждый NotesDocument из коллекции во внутреннее свойство bookArray. А также он устанавливает свойство count в соответствие с количеством документов в коллекции.



В начало


Пересылка и обработка ошибок

Прежде чем мы рассмотрим класс BookSearch, давайте немного прервемся на обсуждение концепции отказов в Web-сервисах LotusScript.

В терминологии Web-сервисов отказ (fault) - это ошибка (error). Если в LotusScript Web-сервисе нет генерации отказов или обработки ошибок и при вызове метода или сервиса происходит ошибка, вы получите следующий ответ от SOAP:


Листинг 10. Стандартный отказ LotusScript SOAP
                
   <soapenv:Body>
      <soapenv:Fault>
         <faultcode>soapenv:Server.generalException</faultcode>
         <faultstring>LotusScript did not run to completion.</faultstring>
         <detail/>
      </soapenv:Fault>
   </soapenv:Body>

Фактическая же ошибка появляется в серверной консоли и в лог-файле log.nsf, но, к сожалению, сообщение об ошибке, которое возвращается пользователю, слишком стандартна и не очень полезно.

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

  1. Убедитесь, что в секции Web-сервиса Options присутствует строчка %INCLUDE "lsxsd.lss".
  2. Добавьте последним в ваш метод параметр WS_FAULT (он должен быть последним параметром).
  3. При возникновении ошибки или если вы хотите вернуть отказ пользователю, установите объект WS_FAULT и выйдите из метода при помощи функции Exit или процедуры Exit.

Пример:


Листинг 11. Пример пересылки клиенту явного отказа LotusScript
                
Public Function stringToNumber (s As String, returnFault As WS_FAULT) As Integer
	On Error Goto processError
	stringToNumber = Cint(s)
	Exit Function
	
processError:
	Call returnFault.setFault(True)
	Call returnFault.setFaultString(Error$)
	Messagebox "Logging to console: " & Error$
	Exit Function
End Function

Когда клиент вызывает метод stringToNumber, он видит только один строковый объект, который будет передан методу. Параметр ошибки скрыт. Однако, если возникает ошибка, вы можете задать сообщение о ней для всех необходимых случаев. В этом примере, если клиент передаст методу фиктивное значение (такое, как foo - "нечто"), он получит следующий ответ:


Листинг 12. Отказ SOAP, возвращаемый нашим явно сгенерированным отказом
                
<soapenv:Fault>
    <faultcode>soapenv:Server.generalException</faultcode>
    <faultstring>Type mismatch</faultstring>
    <detail/>
</soapenv:Fault>

Совет: Используйте метод генерации отказов
В классах BookSearch и BookDownloadUpload мы вызываем специальные методы всякий раз, когда нам нужно сгенерировать отказ. Это позволяет легко осуществлять глобальное регистрирование ошибок или создавать уведомление при возникновении отказов в Web-сервисе.

В данном случае элемент <faultstring> гораздо нагляднее, чем стандартное сообщение "LotusScript did not run to completion".

Стоит также отметить еще два момента касательно блока processError в методе stringToNumber:

  • Необходимо явно устанавливать объект fault в True, используя setFault.
  • Конструкция Messagebox не обязательна, но с ее помощью можно выводить ошибки (или другую информацию) в серверную консоль и файл log.nsf в любой момент работы Web-сервиса. Это можно использовать для простого журналирования и отладки.


В начало


Анатомия класса BookSearch

Вернемся к нашему примеру Web-сервиса BookSearch. Класс BookSearch, представляющий собой интерфейс этого Web-сервиса, имеет три public-метода:

  • getFirstTitleMatch. Возвращает единичный объект BookInfo с первой книгой с названием, отвечающим заданной строке поиска.
  • getAllTitleMatches. Возвращает объект BookInfoArray со всеми книгами с названиями, отвечающими заданной строке поиска.
  • getDocByNoteID. Возвращает единичный объект BookInfo с документом book с заданным NoteID.

И два вспомогательных private-метода, которые недоступны клиентам Web-сервиса:

  • throwFault. Вызывается методами при необходимости сгенерировать отказ, обеспечить общий подход к генерации отказов, записать в журнал любые возвращаемые отказы.
  • findDocsByTitle. Возвращает коллекцию NotesDocumentCollection, содержащую документы book из базы данных, которые соответствуют заданной строке поиска.

Хотя метод findDocsByTitle является основной функцией поиска NotesView, стоит взглянуть на метод throwFault. Вот его код:


Листинг 13. Метод throwFault внутри LotusScript-класса BookSearch
                
Private Sub throwFault (fault As WS_FAULT, faultText As String)
	Call fault.setFault(True)
	Call fault.setFaultString(faultText)
	'** здесь можно добавить любые действия,
	'** которые нужно выполнить при возникновении ошибки...
End Sub

То же самое мы делали в примере генерации отказа выше, только вместо установки свойств fault для каждого отдельного метода Web-сервиса у нас каждый метод вызывает эту функцию. Преимущество такого подхода отмечено в комментарии в конце функции: "здесь можно добавить любые действия, которые нужно выполнить при возникновении ошибки."

При генерации отказов с использованием подобного механизма легче организовать одно центральное место для управления и регистрации отказов, возникающих в Web-сервисе. Можно вывести сообщение в серверную консоль, используя конструкцию Messagebox, можно добавить запись в основной NotesLog-журнал или даже использовать специальную базу данных для регистрации ошибок, такую как OpenLog. Опять же, использование отдельного метода для обработки отказов не обязательно, но является хорошим тоном.

Что касается public-методов нашего класса BookSearch, их код выглядит следующим образом:


Листинг 14. LotusScript-класс BookSearch, который используется как реализация Web-сервиса
                
Public Function getFirstTitleMatch (searchString As String, _
returnFault As WS_FAULT) As BookInfo
	On Error Goto processError
	Dim dc As NotesDocumentCollection
	Set dc = findDocsByTitle(searchString)
	
	If (dc.Count = 0) Then
		Call throwFault(returnFault, "No matches found for search string: " & searchString)
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error:  The previous line is longer than the max of 90 characters ---------|
		Exit Function
	End If
	
	Set getFirstTitleMatch = New BookInfo
	Call getFirstTitleMatch.getDocContents(dc.GetFirstDocument)
	Exit Function
	
processError:
	Call throwFault(returnFault, "Error searching documents: " & Error)
	Exit Function
	
End Function

Public Function getAllTitleMatches (searchString As String, _
returnFault As WS_FAULT) As BookInfoArray
	On Error Goto processError
	Dim dc As NotesDocumentCollection
	Dim doc As NotesDocument
	Set dc = findDocsByTitle(searchString)
	
	Set getAllTitleMatches = New BookInfoArray
	Call getAllTitleMatches.setArrayFromCollection(dc)
	
	Exit Function
	
processError:
	Call throwFault(returnFault, "Error searching documents: " & Error)
	Exit Function
	
End Function

Public Function getDocByNoteID (noteID As String, _
returnFault As WS_FAULT) As BookInfo
	On Error Resume Next
	Dim session As New NotesSession
	Dim db As NotesDatabase
	Dim doc As NotesDocument
	
	Set db = session.CurrentDatabase
	Set doc = db.GetDocumentByID(noteID)
	If (doc Is Nothing) Then
		Call throwFault(returnFault, "No doc found for Note ID " & noteID)
		Exit Function
	End If
	
	Set getDocByNoteID = New BookInfo
	Call getDocByNoteID.getDocContents(doc)
End Function

Код этих методов достаточно короткий и понятный, но мы укажем на несколько моментов:

  • Обратите внимание, что методы getFirstTitleMatch и getDocByNoteID генерируют отказ, если строка поиска пустая, а метод getAllTitleMatches генерирует отказ, если возникает ошибка LotusScript. Это показывает, что мы можем возвращать объект fault даже если не происходит фактической ошибки выполнения кода. В данном случае мы также можем вернуть пустой объект BookInfo.
  • Краткость кода методов показывает полезность методов BookInfo.getDocContents и BookInfoArray.setArrayFromCollection. Добавление этих вспомогательных методов в классы сложных типов данных не только укорачивает код нашего Web-сервиса, но и позволяет добавлять и изменять эту общую функциональность в одном месте.

Если вы хотите протестировать методы Web-сервиса, вы можете использовать либо тестовые агенты из базы данных Web-сервиса Bookstore, либо использовать один из способов, описанных в предыдущей статье, "Практическое использование Web-сервисов в IBM Lotus Domino 7: создание и тестирование простых Web-сервисов."



В начало


Использование перечислений

Прежде чем мы поговорим о коде Web-сервиса BookDownloadUpload, нам нужно обсудить одну структуру данных. Эта структура называется перечисление.

Перечисления можно использовать только в Lotus Domino версии V7.0.1 и более поздних
Не следует использовать перечисления, если только вы не работаете с Lotus Domino версии V7.0.1 или более поздними. В первом релизе Lotus Domino V7.0 были проблемы с поддержкой перечислений: типы данных, которые ожидались и возвращались для перечислений, были некорректными. Проблема была разрешена в релизе 7.0.1.

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

  • Fiction (художественная литература)
  • Non-fiction (научная литература)
  • Reference (справочная литература)
  • Comic Book (комиксы)

В форме Domino или на Web-странице мы можем контролировать ввод такого рода, используя список, так что пользователи не смогут добавить произвольные данные в такое поле. В Web-сервисе используются перечисления. В документе WSDL перечисления выглядят примерно следующим образом:


Листинг 15. Пример того, как выглядят перечисления в WSDL-документе
                
<simpleType name="BOOKTYPE">
    <restriction base="xsd:string">
        <enumeration value="Fiction"/>
        <enumeration value="Non-Fiction"/>
        <enumeration value="Reference"/>
        <enumeration value="Comic Book"/>
    </restriction>
</simpleType>

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

В LotusScript вам придется сделать несколько очень специфичных вещей для создания перечисления. В качестве примера приведен код перечисления BookType для Web-сервиса BookDownloadUpload:


Листинг 16. Код перечисления BookType на языке LotusScript
                
'** Имя этих констант ДОЛЖНО начинаться со слова "BookType_":
Const BookType_Fiction = "Fiction"  
Const BookType_Nonfiction = "Non-Fiction"
Const BookType_Reference = "Reference"
Const BookType_Comic = "Comic Book"


'** Имя этих глобальных переменных ДОЛЖНО заканчиваться словом "_BookType":
Dim Fiction_BookType As BookType
Dim NonFiction_BookType As BookType
Dim Reference_BookType As BookType
Dim Comic_BookType As BookType


'** Список возможных элементов BookType ДОЛЖНО называться "Enum_BookType":
Dim Enum_BookType List As BookType 


'** Фактический класс перечисления BookType:
Class BookType
	'** ДОЛЖНО присутствовать Public-свойство, называющееся "Value",
	'** того же типа что и вышеопределенные константы
	Public Value As String
	
	Public Sub Initialize (typeString As String)
		Value = typeString
		Set Enum_BookType(Cstr(Value)) = Me
	End Sub
End Class

'** Этот класс инициализирует все глобальные объекты _BookType.
'** Он должен быть вызван при инициализации класса BookDownloadUpload.
Class BookTypeInitializer
	Public Sub New ()
		Set Fiction_BookType = New BookType
		Call Fiction_BookType.Initialize(BookType_Fiction)
		
		Set NonFiction_BookType = New BookType
		Call NonFiction_BookType.Initialize(BookType_NonFiction)
		
		Set Reference_BookType = New BookType
		Call Reference_BookType.Initialize(BookType_Reference)
		
		Set Comic_BookType = New BookType
		Call Comic_BookType.Initialize(BookType_Comic)
	End Sub
End Class

Что нужно сделать для создания перечисления в LotusScript:

  1. Придумайте имя для перечисления (в нашем случае BookType).
  2. Для каждого элемента перечисления создайте глобальную константу. Название каждой константы должно начинаться с имени перечисления, за которым должен следовать знак подчеркивания (в нашем случае BookType_xxx).
  3. Для каждого элемента перечисления создайте глобальный объект с типом данных, соответствующим имени перечисления. Имя каждого объекта должно заканчиваться на имя перечисления с подчеркиванием (в нашем случае xxx_BookType).
  4. Создайте глобальный список с типом данных, соответствующм имени перечисления. Имя должно состоять из строки Enum_ с именем перечисления (в нашем случае Enum_BookType).
  5. Создайте специальный класс с тем же именем, что и у перечисления. У этого класса должно быть единственное public-свойство, называющееся Value, с тем же типом данных, что и константы, которые были созданы для перечисления в шаге 2.
  6. При вызове Web-сервиса все глобальных объекты перечислений должны быть инициализированы надлежащими значениями (в нашем случае посредством создания экземпляра класса BookTypeInitializer).

После этого специальный класс перечисление, созданный в шаге 5, можно использовать везде, где бы вы ни захотели. В WSDL-файле он отображается как элемент перечисления, и выполняются ограничения, заданные списком Enum в шаге 4.

С одной стороны, для создания ограниченного списка элементов, которые может использовать клиент, требуется слишком много специального кода. Тем не менее, это правильный способ реализации подобных списков в Web-сервисах.

Для упрощения процесса создания кода можно использовать страницу Domino Enumeration Code Generator. Вам нужно ввести имя перечисления, тип данных и все значения, которые вы хотели бы использовать, и страница сгенерирует для вас LotusScript- и Java- код. Это поможет вам гораздо быстрее начать использовать перечисления в Web-сервисах Lotus Domino.



В начало


Как работает Web-сервис BookDownloadUpload

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

  • Перечисление BookType, которое мы уже обсудили.
  • Сложный тип данных BookInfoAndFile, который содержит информацию о книге и файл с данными.
  • Сложный тип данных BookInfoAndFileArray, который представляет собой массив объектов BookInfoAndFile (также как и тип BookInfoArray в сервисе BookSearch).
  • Класс BookDownloadUpload, который является интерфейсом для нашего Web-сервиса.

В предыдущем разделе мы рассмотрели перечисление BookType. Класс BookInfoAndFileArray по сути идентичен классу BookInfoArray сервиса BookSearch. Классы BookInfoAndFile и BookDownloadUpload мы обсудим более подробно позже.



В начало


Работа с прикрепленными файлами

Основное отличие сервиса BookDownloadUpload от сервиса BookSearch состоит в том, что он позволяет скачавать и загружать файлы. Для этого используется класс XSD_BASE64BINARY, определенный в файле lsxsd.lss.

Для начала взгляните на код класса BookInfoAndFile:


Листинг 17. LotusScript-класс BookInfoAndFile
                
Class BookInfoAndFile
	Public author As String
	Public base64file As XSD_BASE64BINARY
	Public description As String
	Public fileName As String
	Public noteID As String
	Public title As String
	Public typeOfBook As BookType
	
	'** инициализация объекта base64file при создании
	Public Sub New ()
		Set base64file = New XSD_BASE64BINARY
	End Sub
	
	'** упрощаем заполнение содержимого полей объекта base64file
	'** с помощью NotesStream
	Public Sub setFile (stream As NotesStream)
		Call base64file.SetValueFromNotesStream(stream)
	End Sub
	
	'** упрощаем взятие содержимого полей объекта base64file
	'** с помощью  NotesStream
	Public Function getFile () As NotesStream
		Set getFile = base64file.GetValueAsNotesStream()
	End Function
	
	'** упрощаем заполнение содержимого полей объекта typeOfBook
	Public Sub setBookType (bookTypeString As String)
		Set typeOfBook = Enum_BookType(bookTypeString)
	End Sub
	
	'** упрощаем взятие содержимого полей объекта typeOfBook
	Public Function getBookType () As String
		getBookType = typeOfBook.toString()
	End Function
	
	'** берем NotesDocument из базы данных и заполняем значения
	'** Public-свойство соответствено
	Public Function getDocContents (doc As NotesDocument) As Integer
		title = doc.GetItemValue(TITLE_FIELD)(0)
		description = doc.GetItemValue(DESCRIPTION_FIELD)(0)
		author = doc.GetItemValue(AUTHOR_FIELD)(0)
		
		Call setBookType(doc.GetItemValue(BOOK_TYPE_FIELD)(0))
		
		'** функции пользователя для преобразования прикрепленного файла к NotesStream
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error:  The previous line is longer than the max of 90 characters ---------|
		fileName = firstAttachmentFileName(doc, ATTACHMENT_FIELD)
		Call setFile(firstAttachmentToStream(doc, ATTACHMENT_FIELD))
		
		noteID = doc.NoteID
		getDocContents = True
	End Function
	
	'** создаем новый документ в базе данных в соответствие с
	'** содержимым Public свойств этого объекта
	Public Function createNewDoc () As Integer
		Dim session As New NotesSession
		Dim doc As NotesDocument
		
		Set doc = New NotesDocument(session.CurrentDatabase)
		doc.Form = FORM_NAME
		Call doc.ReplaceItemValue(TITLE_FIELD, title)
		Call doc.ReplaceItemValue(DESCRIPTION_FIELD, description)
		Call doc.ReplaceItemValue(AUTHOR_FIELD, author)
		Call doc.ReplaceItemValue(BOOK_TYPE_FIELD, getBookType())
		
		Dim rtitem As New NotesRichTextItem(doc, ATTACHMENT_FIELD)
		Dim tempFileName As String
		'** функции пользователя для записи NotesStream во временный файл
		tempFileName = createTempFile(fileName, Me.getFile())
		Call rtitem.EmbedObject(EMBED_ATTACHMENT, "", _
		tempFileName, fileName)
		
		Call doc.Save(True, False)
		noteID = doc.NoteID
		
		Kill tempFileName
		createNewDoc = True
	End Function
End Class

Base64file является public-свойством, содержащим прикрепленные файлы, поэтому мы объявляем его как тип данных XSD_BASE64BINARY (в WSDL-файле он отображается как тип данных xsd:base64Binary). Этот тип данных языка LotusScript определен в файле lsxsd.lss. У него есть два метода для получения и заполнения содержимого файла: SetValueFromNotesStream и GetValueAsNotesStream.

Java Base64 кодирование/декодирование гораздо быстрее чем в языке LotusScript
Если вы используете кодирование и декодирование в base64 для файлов более 20 KB, подумайте о том, чтобы использовать Java (либо Java Web-сервис, либо LS2J). Алгоритмы LotusScript base64, особенно декодирующие, работают очень медленно со всем, кроме маленьких файлов.

BookDownloadUpload Web-сервис в экземпляре базы данных содержит пример использования LS2J для поддержки функций base64, также в базе данных есть Web-сервис BookDownloadUploadJava который показывает, как все реализовать на Java.

При передаче NotesStream объекту (с помощью SetValueFromNotesStream) можно передавать как бинарный, так и ASCII поток данных, а класс кодирует данные в base64. Точно также можно извлечь файловые данные, хранимые в объекте (с помощью GetValueAsNotesStream), как бинарный NotesStream, а base64-декодирование обработает данные за вас.

Однако есть некоторые хитрости в работе с NotesStreams. Если вы считываете или записываете файлы в локальную файловую систему, преобразовать файл в поток очень просто (примеры можно найти в Lotus Domino Designer Help). Для преобразования прикрепленного файла элемента NotesDocument или NotesRichTextItem в поток NotesStream вам нужно сначала открепить файл, а потом записать его в поток. Точно также, чтобы прикрепить NotesStream к документу в качестве прикрепленного файла, нужно записать поток во временный файл, а потом прикрепить этот временный файл к документу. Специальные функции firstAttachmentToStream и createTempFile нашего Web-сервиса поддерживают такую функциональность (чтобы узнать, как они работают, смотрите код функций в примере базы данных).

Base64-кодирование и декодирование не выполняется на клиентской стороне, когда пользователь отправляет или получает прикрепленный файл. Некоторые клиенты, такие как Apache Axis, делают это, а некоторые, такие как MSSOAP, требуют сделать это вручную.



В начало


Анатомия класса BookDownloadUpload

Класс BookDownloadUpload очень похож на класс BookSearch, который мы рассматривали ранее. У него есть пять public-методов:

  • getFirstTitleMatch. Возвращает единственный объект BookInfoAndFile с первой найденной книгой с названием, соответствующим заданной строке поиска.
  • getAllTitleMatches. Возращает объект BookInfoAndFileArray со всеми книгами с названиями, соответствующими заданной строке поиска.
  • getDocByNoteID. Возвращает единственный объект BookInfoAndFile, соответствующий документу book с определенным NoteID.
  • addNewFileComplex. Позволяет клиенту создавать новые документы book посредством загрузки объекта BookInfoAndFile.
  • addNewFile. Поволяет клиенту создавать новые документы book посредством передачи всех отдельных элементов книги в качестве различных параметров.

Точно так жеЮ как и в классе BookSearch, класс BookDownloadUpload имеет два вспомогательных private-метода (throwFault и findDocsByTitle), которые недоступны клиентам Web-сервиса.

Ниже приведен код класса BookDownloadUpload:


Листинг 18. LotusScript-класс BookDownloadUpload, использующийся в качестве реализации Web-сервиса
                
Class BookDownloadUpload
	
	'** инициализация всех глобальных объектов BookType перед началом работы
	Public Sub New ()
		Dim bookInit As New BookTypeInitializer
	End Sub
	
	Public Function getFirstTitleMatch (searchString As String, _
	returnFault As WS_FAULT) As BookInfoAndFile
		On Error Goto processError
		Dim dc As NotesDocumentCollection
		Set dc = findDocsByTitle(searchString)
		
		If (dc Is Nothing) Then
			Call throwFault(returnFault, "No matches found for search string: " & searchString)
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error:  The previous line is longer than the max of 90 characters ---------|
			Exit Function
		Elseif (dc.Count = 0) Then
			Call throwFault(returnFault, "No matches found for search string: " & searchString)
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error:  The previous line is longer than the max of 90 characters ---------|
			Exit Function
		End If
		
		Set getFirstTitleMatch = New BookInfoAndFile
		Call getFirstTitleMatch.getDocContents(dc.GetFirstDocument)
		Exit Function
		
processError:
		Call throwFault(returnFault, "Error searching documents: " & Error)
		Exit Function
		
	End Function
	
	Public Function getAllTitleMatches (searchString As String, _
	returnFault As WS_FAULT) As BookInfoAndFileArray
		On Error Goto processError
		Dim dc As NotesDocumentCollection
		Dim doc As NotesDocument
		Set dc = findDocsByTitle(searchString)
		
		Set getAllTitleMatches = New BookInfoAndFileArray
		Call getAllTitleMatches.setArrayFromCollection(dc)
		
		Exit Function
		
processError:
		Call throwFault(returnFault, "Error searching documents: " & Error)
		Exit Function
		
	End Function
	
	Public Function getDocByNoteID (noteID As String, _
	returnFault As WS_FAULT) As BookInfoAndFile
		On Error Resume Next
		Dim session As New NotesSession
		Dim db As NotesDatabase
		Dim doc As NotesDocument
		
		Set db = session.CurrentDatabase
		Set doc = db.GetDocumentByID(noteID)
		If (doc Is Nothing) Then
			Call throwFault(returnFault, "No doc found for Note ID " & noteID)
			Exit Function
		End If
		
		Set getDocByNoteID = New BookInfoAndFile
		Call getDocByNoteID.getDocContents(doc)
	End Function
	
	Private Sub throwFault (fault As WS_FAULT, faultText As String)
		Call fault.setFault(True)
		Call fault.setFaultString(faultText)
		
		'** другие действия по журналированию ошибки...
		If (Err > 0) Then
			'** Messagebox запишет в файл log.nsf также как и агент backend
			Messagebox LogError()
		End If
	End Sub
	
	Private Function findDocsByTitle (searchString As String) As NotesDocumentCollection
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error:  The previous line is longer than the max of 90 characters ---------|
		Dim session As New NotesSession
		Dim db As NotesDatabase
		Dim view As NotesView
		
		Set db = session.CurrentDatabase
		Set view = db.GetView(TITLE_LOOKUP_VIEW)
		Set findDocsByTitle = view.GetAllDocumentsByKey(searchString, False)
	End Function
	
	Public Function addNewFileComplex (book As BookInfoAndFile, _
	returnFault As WS_FAULT) As String
		On Error Goto processError
		Call book.createNewDoc()
		addNewFileComplex = book.noteID
		Exit Function
		
processError:
		Call throwFault(returnFault, "Error creating new doc: " & Error)
		Exit Function
		
	End Function
	
	Public Function addNewFile (title As String, author As String, description As String, _
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error:  The previous line is longer than the max of 90 characters ---------|
	typeOfBook As BookType, fileName As String, base64file As XSD_BASE64BINARY, _
	returnFault As WS_FAULT) As String
		Dim book As New BookInfoAndFile
		book.title = title
		book.author = author
		Set book.typeOfBook = typeOfBook
		book.description = description
		book.fileName = fileName
		Set book.base64file = base64file
		addNewFile = addNewFileComplex(book, returnFault)
	End Function
	
End Class

Как вы видите, код методов getFirstTitleMatch, getAllTitleMatches и getDocByNoteID по сути идентичен коду тех же функций класса BookSearch, за исключением того, что вместо объектов BookInfo мы используем объекты BookInfoAndFile. Также в процедуру New мы добавили строчку кода для инициализации перечислений класса с помощью класса BookTypeInitializer. Еще метод throwFault показывает использование Messagebox для вывода сообщений об ошибках в серверную консоль. Во всем остальном класс BookDownloadUpload очень похож на класс BookSearch.

Что касается методов addNewFile и addNewFileComplex, их код очень короткий, потому как логика создания новых документов в базе данных на основе загруженного документа описана в классе BookInfoAndFile.



В начало


Тестирование примера базы данных

Для тестирования какого-либо сервиса из примера базы данных Bookstore вам могут быть полезны два примера агентов, которыми мы снабдили базу данных. Они вызывают все методы Web-сервисов (в том числе и скачивание, загрузку файлов). Агенты написаны на Java и используют программные заглушки Apache Axis, разработанные в проекте с открытым кодом Stubby database, они доступны на OpenNTF.org.

Также мы предоставили несколько примеров использования MSSOAP для выполнения простого поиска по книгам. Учтите, что, если у вас установлена библиотека MSSOAP версии ниже 3.0, примеры с MSSOAP будут работать, только если вы установите Web-сервисы как RPC/Encoded, кроме того они не будут работать с сервисом BookDownloadUpload, потому что MSSOAP 1.x не сможет интерпретировать перечисление BookType, которое мы используем в этом Web-сервисе. Узнать больше об ограничениях использования MSSOAP можно из предыдущей статьи серии "Практическое использование Web-сервисов в IBM Lotus Domino 7: создание и тестирование простых Web-сервисов".

Для тестирования можно также использовать другие средства, указанные в предыдущей статье из этой серии.



В начало


Предостережения и устранение неисправностей

Приведем несколько советов, которые помогут избежать появления неполадок и устранить их при возникновении в коде вашего Web-сервиса.

При тестировании сервисов локально, убедитесь что запущен сервис Notes HTTP

Если для тестирования вы сделали локальную копию базы данных, сначала неоходимо убедиться в том, что сервис Notes HTTP запущен фоновым процессом. Проще всего это сделать так:

  1. Откройте базу данных в Domino Designer.
  2. Выберите форму или представление в базе данных.
  3. Выберите Design - Preview in Web browser - Default Browser и подождите, пока форма или представление не появится в браузере

После того как вы увидите форму или представление как Web-страницу, закройте окно браузера и тестируйте Web-сервис, используя localhost в качестве имени сервера. HTTP-сервис будет запущен в качестве фонового процесса, пока вы полностью не закроете клиент Notes.

ПРИМЕЧАНИЕ. Некоторые персональные брандмауэры блокируют работу локального HTTP-сервиса, поэтому вам, возможно, будет нужно поменять настройки брандмауэра.

Если пользователь не имеет доступа к Web-сервису, он получит ошибку 401 Access Denied (доступ запрещен)

Убедитесь в правильности настроек требований безопасности для доступа к Web-сервису. Если анонимный пользователь не имеет прав доступа к базе данных и Web-сервису даже на чтение, он получит ошибку 401 Access Denied (доступ запрещен) при попытке чтения WSDL-файла или вызова метода Web-сервиса. В первой версии Lotus Domino 7.0 в данной ситуации генерировалась менее понятная ошибка 404 Not Found (страница не найдена).

Используйте Messagebox или System.out.println для вывода информации в серверную консоль

Просто выводить сообщения в серверную консоль (и log.nsf file) - не очень хороший метод журналирования ошибок, но если вам нужно быстро временно добавить журналирование, такой способ самый простой. В Web-сервисах Domino сообщения можно вывести в серверную консоль с помощью Messagebox (LotusScript) или System.out.println (Java).

Лучше, конечно, использовать более общие средства, такие как NotesLog или базу данных OpenLog. Если вы используете общий метод генерации отказов (как мы делали в примерах Web-сервисов в этой статье), вы сможете легко добавить вызовы системы журналирования, с тем чтобы, например, отслеживать ваши сообщения и ошибки.

Настройки безопасности для агентов сервера, обращающихся к Web-сервису и сервису, осуществляющих чтение из файла/запись в файл

Для Web-сервисов, которые читают/записывают файлы, обращаются к сети, а также для серверных агентов, которые обращаются к Web-сервисам, убедитесь, что следующие условия выполнены:

  • Уровень безопасности агента/Web-сервиса по крайней мере "2. Allow Restricted Operations" (его можно установить в блоке свойств (Properties), который доступен при редактировании элемента агента или Web-сервиса).
  • Подписчик агента/Web-сервиса имеет права "Run unrestricted methods and operations" на серверные документы.
  • Подписчик агента/Web-сервиса имеет права "Run restricted LotusScript/Java agents" на серверные документы.

Если эти права выданы неверно, при запуске агента или Web-сервиса вы увидите ошибку безопасности (security error; обычно в серверной консоли и в файле log.nsf).

Если Web-сервис возвращает ошибку 500, вам нужно перекомпилировать LotusScript

Если вы получили ошибку HTTP 500 с сообщением "Unknown LotusScript Error" (Неизвестная ошибка LotusScript) при попытке доступа к WSDL-файлу или при работе метода вашего Web-сервиса, вам нужно перекомпилировать весь код LotusScript в базе данных. Такая ошибка часто случается, когда Web-сервис использует функции из скриптовой библиотеки, а библиотека более новая, чем Web-сервис. Это похоже на ситуацию, когда клиент Lotus Notes показывает сообщение "Error Loading USE or USELSX".

Преобразуйте динамические массивы в статические

Если ваш LotusScript Web-сервис возвращает массив, убедитесь что он статический, а не динамический. Например, следующие типы вызовов LotusScript генерируют динамические массивы:

  • varval = doc.SomeFieldName
  • varval = Split("I am superman", " ")
  • varval = Evaluate(|@DbColumn("";"":"somedatabase";"some view";2)|)

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


Листинг 19. Код для преобразования динамического массива в статический
                
Dim returnArray() As String
varval = doc.MultiValueCategoryField
Redim returnArray(Ubound(varval))
For i = 0 To Ubound(varval)
	returnArray(i) = varval(i)
Next

ПРИМЕЧАНИЕ. Нужно возвращать массивы одного из типа данных ARRAY_HOLDER, определенных в файле lsxsd.lss file (ситуация более детально описана в предыдущей статье).

Используйте Lotus Domino версии V7.0.1 или более поздней, особенно, если если вы работаете с перечислениями

Немного хитроумная, но важная задача была решена в Lotus Domino V7.0.1. Главная проблема в первой версии Lotus Domino V7.0 была в том, что перечисления не работали должным образом. Убедитесь, что вы используете версию Lotus Domino V7.0.1 или выше и посмотрите общедоступные страницы список исправлений для Notes/Domino и База Знаний Lotus для поиска возможных ошибок.

Даже если время ожидания ответа у клиента Web-сервиса истекло, он может продолжать работать

Если клиент Web-сервиса вызвал метод, для выполнения которого требуется много времени, время ожидания ответа у клиента может истечь прежде, чем сервис закончит что-либо делать (стандартное значение ожидания ответа по умолчанию для клиента – 60 секунд).

Заметьте, что если у клиента истечет время ожидания ответа, Web-сервис будет продолжать обрабатывать запрос, пока не закончит или пока не истечет таймаут агента, установленный в документе Domino Server. При загрузке большого файла или сложной транзакции в базе данных клиент Web-сервера может получить сообщение об истечении времени ожидания и думать, что процесс загрузки не был осуществлен, даже если на самом деле это не так (возможно загрузка была выполнена через несколько минут).

К слову, не забывайте, что в Lotus Domino V7 можно использовать новое средство определения профиля агентов для генерации записи о том, что запускаются различные долгообрабатывающиеся части вашего Web-сервиса. Для получения дополнительной информации о том, как подключать и использовать определение профиля агентов смотрите в файле справки к Lotus Domino Designer.

Убедитесь, что public-свойства классов в LotusScript, использующих сложные типы данных, расположены в алфавитном порядке

Эта проблема возможно будет устранена более поздних версиях Lotus Domino, но что касается версии 7.0.1, в ней могут быть проблемы, если public-свойства классов сложных типов данных расположены не в алфавитном порядке. Вот этот, например, код работает:


Листинг 20. Свойства сложных типов данных LotusScript расположены в алфавитном порядке (ПРАВИЛЬНО)
                
Class Book
	Public author As String
	Public booktype As String
	Public title As String
End Class

А вот с этим кодом могут быть проблемы:


Листинг 21. Свойства сложных типов данных LotusScript расположены в алфавитном порядке (НЕПРАВИЛЬНО)
                
Class Book
	Public title As String
	Public author As String
	Public booktype As String
End Class

Признаком этой проблемы является то, что в объекте сложного типа данных, посланном в качестве параметра или полученном в качестве ответа, какие-то элементы могут отсутствовать. Проблема может оказаться достаточно сложной для диагностирования, но она часто решается, если отсортировать public-свойства в алфавитном порядке.

Тем не менее заметьте, что простое изменение порядка свойств в исходном коде не поможет, потому что WSDL не обновляется, пока список свойств не изменится (даже если меняется их порядок). Нужно сделать следующее:

  1. Изменить порядок свойств.
  2. Добавить фиктивное свойство.
  3. Сохранить и закрыть Web-сервис.
  4. Открыть Web-сервис заново.
  5. Удалить фиктивное свойство.
  6. Сохранить и закрыть Web-сервис.

Мы точно не знаем, с какими клиентами Web-сервиса проявляется эта проблема, но мы замечали ее при использовании как Apache Axis, так и Apache SOAP.

Аккуратно записывайте длинные строки (> 32 KB), полученные от Web-сервиса, в обычное текстовое поле документа NotesDocument

Использовать Web-сервиса, который получает параметр или элемент сложного типа данных и записывает его значение в документ NotesDocument, - обычная практика. Если вы так делаете, помните о том, что обычное текстовое поле документа в Notes ограничено 64 KB (около 32,000 символов). Если вы попытаетесь записать в текстовое поле значение больше, чем это, возникнут непредвиденные ошибки.

Если данные, которые вы записываете в поле формата Plain Text документа NotesDocument, могут превысить 64 KB, лучше записывать их в поле формата Rich Text.

Подумайте о том, что использование LS2J и Java может быть эффективнее, чем LotusScript при base64 кодировании/декодировании

При выполнении операций base64-кодирования или декодирования с файлами, размер которых больше, чем 20 KB, лучше использовать для этих операций Java, нежели родное LotusScript-кодирование/декодирование из класса XSD_BASE64BINARY. Как правило, LotusScript работает с длинными строками относительно медленно, тогда как Java работает быстрее.

Из примеров базы данных Web Services Bookstore (ее можно найти в разделе загрузка) вы можете узнать о том, как использовать LS2J и Java для обеспечения подобной функциональности. Методы getFile и setFile класса BookInfoAndFile в Web-сервисе BookDownloadUpload Web обращаются к скриптовой библиотеке LS2J, которая называется Base64 LS2J, и использует специальный Java-класс из библиотеки Base64Java.

При тестировании с файлом размером около 150 KB base64-декодирование выполнилось методами LS2J в 100 раз быстрее, чем встроенными методами LS2J (за одну-две секунды против почти двух минут на нашей рабочей станции).



В начало


Заключение

Эта серия из трех статей о Web-сервисах в Lotus Domino V7.0 поможет вам понять принципы работы с Web-сервисами Lotus Domino, начиная с основных базовых концепций и заканчивая созданием сложных сервисов. Мы обсудили использование простых типов данных, массивов, сложных типов данных, перечислений, прикрепленных файлов и генерацию сообщений об ошибках в Web-сервисах LotusScript на практических примерах кода для каждой их этих концепций и примере базы данных, которая пригодна для использования. В пример базы данных также включены Java Web-сервисы, которые дублируют сервисы LotusScript, с тем чтобы вы могли посмотреть, как методы LotusScript преобразуются в Java-методы. Мы даже рассмотрели несколько особых методов для тестирования Web-сервисов с использованием различных свободно распространяемых средств. Пример базы данных, который вы можете скачать, содержит несколько агентов, которые обращаются к Web-сервисам напрямую от клиентов Lotus Notes V7.0.

Теперь, имея в распоряжении эти мощные средства, вы сможете включить базы данных Domino в корпоративную архитектуру SOA. Они откроют для ваших приложений и вашей общей организации многие двери, как с функциональной точки зрения, так и с точки зрения архитектуры. Это еще одно направление, в котором можно использовать Lotus Notes/Domino, которое остается полезной и эффективной платформой для разработки приложений.




В начало


Загрузка

ОписаниеИмяРазмерМетод загрузки
Пример базы данных для статьиWSBookstore.nsf1.82 MBHTTP
Информация о методах загрузки


Ресурсы

Научиться

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

Обсудить


Об авторе

Джулиан Робиччо (Julian Robichaux) работает разработчиком ПО, он профессиональный программист, специализирующийся в области IBM Lotus Notes и разработчик Java. Его можно нанять для работы как в небольших, так и в крупных проектах в области разработки, проектирования и обучения. В свободное время он пишет свой личный сайт http://www.nsftools.com. Его семья не понимает, зачем он везде таскает за собой ноутбук. Он, впрочем, тоже.




Выскажите мнение об этой странице


Пожалуйста, найдите минутку и заполните форму, чтобы повысить уровень сервиса.



 


 


 


Поделиться этой статьей:

забобрить забобрить memori сохранить в memori




В начало


IBM обладает всеми авторскими правами касательно информации, расположенной на developerWorks. Использование информации приведенной на этом ресурсе без явного письменного разрешения от IBM или первоначального автора запрещены. Если Вы желаете использовать информацию с developerWorks, пожалуйста воспользуйтесь регистрационной формой для того, чтобы связаться с нами запрос на использование материалов developerWorks Россия.
    IBM в России Конфиденциальность Контакты