2003 г

"Подчистите" свою схему для SOAP

Автор: Шейн Куркуру (Shane Curcuru)
Перевод: Intersoft Lab
Авторские права: market@iso.ru

Корректирование XML-схем: получение схем, удобных для SOAP

Во все большем числе проектов используются XM-схемы для определения структуры данных. По мере роста репозитория схем, становится очевидной потребность в инструментальных средствах, предназначенных для манипулирования и управления схемами. Модель Eclipse XSD Schema Infoset обладает широкими возможностями построения запросов и редактирования. Автор статьи Шейн Куркуру рассказывает о том, как можно модернизировать схему для ее использования с SOAP с помощью автоматического преобразования определений используемых атрибутов в определения элементов.

Предполагается, что читатель знаком с XML-схемами и понимаете, как функционирует SOAP. Код примеров, содержащийся в zip-файле, может работать как автономно, так и в инструментальном средстве Eclipse.

Введение: пример очистки XML-документа

Если вы создали библиотеку схем, возможно, вам захотите воспользоваться ею в новых проектов. Например, если вы уже применяете модель данных для внутренней формы заказа на поставку, то при переходе к использованию Web-сервисов может появиться необходимость модернизировать ее для работы с SOAP. SOAP позволяет передавать XML-сообщение по сети; с помощью схемы на xml body можно накладывать ограничения. Однако, для своего элемента xml body SOAP, как правило, использует данные элементов, а не атрибутов. Рассмотрим программу, которая может автоматически скорректировать существующий документ схемы, преобразов любое объявление атрибута в приблизительно "эквивалентные" объявления элементов.





Рис.1. Преобразование атрибутов в элементы



Краткое изложение применяемого подхода

Если вы вспомните о сложной структуре XML-схем, вы вряд ли захотите воспользоваться Notepad, чтобы редактировать xsd-файлы. Любой хороший XML-редактор не на много лучше - несмотря на то, что он, возможно, отлично организовывает элементы и атрибуты, он не может показывать многочисленные абстрактные отношения Infoset, которые определены в спецификации Schema. Именно здесь на выручку приходит Модель Schema Infoset: она выражает и конкретное DOM-представление набора документов схемы, и полную абстрактную Infoset-модель схемы. Оба эти представления демонстрируются с помощью программного API Модели, а также встроенного редактора схем.

Визуальное редактирование схемы

Если вы установили Модель XSD Schema Infoset и дополнения к Оболочке моделирования Eclipse (EMF) в Eclipse, вы можете узнать, как работает этот редактор в инструментальном средстве. (Примечание: данная статья не подразумевает обязательного изучения этого редактора). Просто щелкните правой кнопкой мышки по файлу schema.xsd в меню Navigator и выберите Open With... а затем Sample XML Schema Editor. Вы откроете стандартный редактор Eclipse, который показывает обычное окно Source - это конкретное DOM-представление xsd-файла, который вы открыли.
В нижней части редактора находятся еще две закладки: Semantics и Syntax. Это графические представления, демонстрирующие различные абстрактные отношения Infoset между компонентами схемы. Например, в окне Semantics можно увидеть высокоуровневый элемент для Types - это все типы (простые и сложные), объявленные где угодно в самой схеме, а не только на верхнем уровне и не просто в этом документе (эта функциональность становится более очевидной, если открытый вами документ схемы использует элементы include и import).

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

В первую очередь отметим, что невозможно преобразовать атрибут в элемент, если уже существует элемент с таким именем в единицах (particle) complexType. Следовательно, сначала установим случаи конфликта имен и не станем эти атрибуты. Чтобы упростить пример, объявим некоторые произвольные условия, чтобы схема была несовместима с этой программой. Не будем изменять схемы, которые содержат групповые символы, поскольку в этом случае потребовалось бы выполнять сложную проверку имен, чтобы убедиться, что ни один из измененных атрибутов не конфликтует с элементами. Также не будем модифицировать группы, которые в качестве компоновщика используют #all или #choice, так как это непредсказуемым способом могло бы изменить значение группы.

Изучение схемы на наличие несовместимости

Использование возможностей абстрактных отношений Infoset

В первую очередь необходимо обнаружить все объявления complexType в схеме, поскольку они являются единственным местом, где действительно могут использоваться атрибуты. Необязательно искать все объявления атрибутов, так как позже всегда можно посмотреть в каждом complexType, какие атрибуты в нем используются. Обращение с запросом к схеме - очень простой процесс: пройдитесь по содержанию и отыщите сложные типы. Заметьте, что для изучения содержания схемы имеется множество методов формирования запросов, и это только один из них.



Листинг 1. Обнаружение complexType
// Find type definitions: for our purposes, the simplest 
//  way to get all complexTypes is to drop down to the 
//  underlying EMF model of a schema to iterate through 
//  all concrete components contained within this schema
// (Поиск определения типа: в нашем примере самый простой 
// способ получить все complexTypes опуститься к 
// базовой EMF-модели схемы, чтобы пройтись по всем
// конкретным компонентам, находящимся в ней)
List complexTypeDefinitions = new ArrayList();
for (Iterator iter = schema.eAllContents(); iter.hasNext(); )
{
    XSDConcreteComponent concreteComponent = (XSDConcreteComponent)iter.next();
    if (concreteComponent instanceof XSDComplexTypeDefinition)
    {
        complexTypeDefinitions.add(concreteComponent);
    }
}
// An alternate method would be to use the abstract Infoset 
//  relationship of schema.getTypeDefinitions(), which would 
//  get all globally-visible typedefs (simple and complex) 
//  within the whole schema, however that would miss any 
//  types that were nested inside of other components
// (Альтернативный способ - воспользоваться абстрактным 
// Infoset-отношением schema.getTypeDefinitions(), который 
// нашел все глобально видимые определения типов (простых и сложных)
// во всей схеме, но тогда все вложенные в другие компоненты типы
// остались бы незамеченными)

Теперь, когда у нас есть список всех complexType, которые необходимо скорректировать, давайте исключим любые типы, которые несовместимы с рассматриваемой программой. Поскольку мы просто запрашиваем информацию о различных компонентах схемы, можно воспользоваться многими абстрактными отношениями Infoset и методами, которые предлагает Модель. Эти абстрактные методы автоматически учитывают такие понятия, как базовые и производные типы, ссылки на объявления, находящиеся где-нибудь в другом месте, а также действие импортированных, включенных или переопределенных документов схемы.



Листинг 2. Поиск случаев несовместимости

// Detect name collisions between top-level elems and attrs
// (Обнаруживает конфликт имен между высокоуровневыми 
// элементами и атрибутами)
List elementNames = getElementNames(complexType);
List attributeNames = getAttributeNames(complexType);
attributeNames.retainAll(elementNames);
if (!attributeNames.isEmpty()) {
    // Report the name collision and return...
    // (Сообщает о конфликте имен и возвращает...)	
}

// Now check for any attribute wildcards, which we 
//  can't really change into elements
// (Проверяет групповые символы, которые нельзя
// превратить в элементы)
XSDWildcard attributeWildcard = complexType.getAttributeWildcard();
if (null != attributeWildcard) {
    // Report an incompatible wildcard and return...
    // (Сообщает о несовместимых групповых символах имен и возвращает...)
}

// Check the content for other incompatible conditions like 
//  groups with choice or all or a simpleType
// (Проверяет содержание на другие несовместимые условия, как
// группы с выбором или все или simpleType)
XSDComplexTypeContent complexTypeContent = complexType.getContent();
if (complexTypeContent instanceof XSDSimpleTypeDefinition) {
    // Report a simple type as incompatible and return...
    // (Сообщает о несовместимых простых типах и возвращает...)	
}
else if (null != complexTypeContent)
{
    XSDTerm particleTerm = ((XSDParticle)complexTypeContent).getTerm();

    if (particleTerm instanceof XSDModelGroup)
    {
        XSDCompositor compositor = ((XSDModelGroup)particleTerm).getCompositor();
        if ((XSDCompositor.ALL_LITERAL == compositor)
                || (XSDCompositor.CHOICE_LITERAL == compositor)) {
            // Report an incompatible group type and return...
            // (Сообщает о несовместимых типах групп и возвращает...)			
        }
    }
    // more checks for wildcards, etc.
    // (еще проверки групповых символов и т.д.)	
}

Примечание. В этом примере приведен не весь код, используемый для обнаружения случаев несовместимости; пожалуйста, скачайте zip-файл с примерами (см. Ресурсы), чтобы увидеть его целиком. Программа MakeSoapCompatible.java тщательно спроектирована, в ней приводятся подробные комментарии, указывающие, как манипулировать схемами с помощью этой Модели. Их изучение является необходимым условием, если вы хотите углубиться свои знания.

Создание объявлений элементов

Конкретизация при добавлении компонентов и манипулировании ими

После того, как вы обнаружили некоторые complexType, которые требуется скорректировать, необходимо провести конкретизацию. Для каждого complexType следует пройтись по списку getAttributeContents(), который показывает, какие конкретно атрибуты использует этот тип. Для каждого случая использования сначала убедитесь, что вы указываете на фактическое объявление атрибута - даже если это ссылка на объявление, находящиеся где-то в другом месте. В этом случае важно создать elementDeclaration, который имеет те же имя и тип, что и в каждом случае использования атрибута - это довольно простой процесс. Кроме того, необходимо поместить elementDeclaration внутрь getContents() новой единицы, поскольку эта единица позже будет добавлена в complexType.



Листинг 3. Замена атрибутов элементами
if (attrDecl.isAttributeDeclarationReference())
    attrDecl = attrDecl.getResolvedAttributeDeclaration();

// Create a blank element and simply copy over the 
//  pertinent data about the attribute
// (Создает пустой элемент и просто копирует
// соответствующие данные о атрибуте
XSDElementDeclaration elemDecl = XSDFactory.eINSTANCE.createXSDElementDeclaration();
elemDecl.setName(attrDecl.getName());
elemDecl.setTypeDefinition(attrType);

// Note that since an annotation's elements are only modeled 
//  in the concrete tree that we must explicitly ask to clone them
// (Внимание: т.к. элементы аннотации моделируются только в
// конкретном дереве, необходимо явно потребовать клонировать их)
if (null != attrDecl.getAnnotation()) {
    cloneAnnotation(attrDecl, elemDecl);
}
// Wrap this element in a particle
// (Обернуть этот элемент в едницу)
XSDParticle particle = XSDFactory.eINSTANCE.createXSDParticle();
particle.setContent(elemDecl);

Это именно та область, которая четко показывает различие между конкретной и абстрактной моделями. Возможно, вам станет любопытно, что же это за единица, если при просмотра файлов schemaDocument.xsd, вы не видите никаких элементов xsd:particle. Для этого прочтите спецификацию для единиц (specification for a particle), хотя она довольно обширна. Единица, в сущности, это абстрактный контейнер объявления имен, группы моделей или чего бы то ни было (группового символа); единица - это то, что определяет свои ограничения min/maxOccurs в отдельном месте в схеме. Поскольку Модель может выражать и конкретные, и абстрактные представления схемы, несложно работать с любым видом представления.

Аннотации - это единственный вид компонента схемы, которые моделируются только в конкретном представлении модели, и поэтому они требуют несколько особенной обработки. В этом примере кода любые аннотации копируются из объявления атрибута в новое, только что созданное, объявление элемента. В действительности, чтобы клонировать или копировать содержимое компонента аннотации, необходимо использовать метод DOM cloneNode(), а затем добавить саму аннотацию в новое объявление элемента.



Листинг 4. Клонирование конкретных аннотаций

XSDAnnotation oldAnnotation = attrDecl.getAnnotation();
XSDAnnotation newAnnotation = XSDFactory.eINSTANCE.createXSDAnnotation();
try {
    Element oldAnnElem = oldAnnotation.getElement();
    // Use the DOM method to do a deep clone of the element
    Element newAnnElem = (Element)oldAnnElem.cloneNode(true);
    newAnnotation.setElement(newAnnElem);
    elemDecl.setAnnotation(newAnnotation);
} 
catch (Exception e) {
    // Report the error and return
    // (Сообщает об ошибке и возвращается)
}

Замена атрибутов элементами

Теперь, когда мы получили новое объявление элементов, которое должно заменить используемые атрибуты, необходимо поменять их местами в компоненте complexType. Поскольку мы воспользовались в цикле конкретным отношением включения complexType.getAttributeContents(), можно просто добавить новое elementDeclaration, а затем вызвать attrContentsIter.remove(), чтобы удалить фактически используемый атрибут из типа.



Листинг 5. Использование конкретных списков для удаления атрибутов

// Use this concrete relationship, since we're going to 
//  actually remove the attributes from this type
// (Использует это конкретное отношение, т.к. из этого
// типа будут удаляться атрибуты) 
for (ListIterator iter = 
        complexType.getAttributeContents().listIterator(); 
        iter.hasNext(); /* no-op */ ) {

    if (changeAttributeIntoElement(complexType, 
            (XSDAttributeGroupContent)iter.next(), changedAttrs)) {
        // Note that list manipulation calls like remove() 
        //  will only work properly on concrete lists; 
        //  attempting to manipulate 'abstract' lists will 
        //  either throw an exception or will silently fail
        //  (Внимание: вызовы манипуляции списком, как remove()
        //  будет работать корректно только на конкретных
        //  списках; попытка манипулировать "абстрактными"
        //  списками, либо сгенерирует исключение, либо приведет к сбою)  
        iter.remove();
    }
    else {
        // Report the error and continue...
        // (Сообщает об ошибке и продолжает...)
    }
}

Переписывание схемы

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

Предполагается, что по крайней мере, некоторые объявления атрибутов были преобразованы в эквивалентные объявления элементов. В этом случае важно сохранить эту схему для дальнейшего использования с SOAP-приложением. Оболочка EMF, на которой построена эта Модель, обеспечивает сервисы обработки ресурсов, которые различны способами загружают и сохраняют документы схемы. Код примера демонстрирует очень простой способ сериализации непосредственно в универсальный идентификатор ресурса (URI), в этом случае выходной файл на диске называется по имени оригинального входного файла.



Листинг 6. Запись схемы в файл
File outFile = new File(newLocation);
FileOutputStream fos = new FileOutputStream(outFile);
// Ensure that the abstract model is synchronized with the 
//  concrete tree: this will ensure that the Model has 
//  updated the concrete Element in the schema document 
//  with any changes that may have been made in the 
//  abstract model
// (Убедитесь, что абстрактная модель синхронизирована с
// конкретным деревом: это гарантирует, что Модель обновила
// конкретный элемент в документе схемы с учетом всех изменений,
// внесенных в абстрактную модель)
schema.updateElement();

// Simply ask the XSDResourceImpl to serialize the schema to 
//  a document for us; this is just one way we can easily use 
//  the XSD/EMF framework to manage resources for us
//  (Просто запрашивает XSDResourceImpl сериализовать схему
// в документ; это просто способ  использования оболочки 
// XSD/EMF для управления ресурсами)
XSDResourceImpl.serialize(fos, schema.getElement());
fos.close();

Заключение

Как было показано выше, выполнение умозрительно простой операции редактирования документовхем (преобразования атрибутов в элементы) может повлечь за собой достаточно много работы. Однако, эта задача становится управляемой благодаря возможности представления Моделью Schema Infoset и абстрактного Infoset схемы, и ее конкретного представления документов схемы. Эта Модель также содержит простые инструментальные средства для загрузки и сохранения документов схемы в различные источники, позволяя программно управлять репозиторием схем.

Некоторые пользователи могут задаться вопросом: "А почему бы не воспользоваться XSLT или другим XML-приложением для редактирования документов схемы?" Несмотря на то, что XSLT может легко обрабатывать конкретную модель набора документов схем, эта технология просто не может увидеть любое абстрактное отношение во всей схеме, которую эти отношения представляют. Предположим, например, что необходимо обновить какие-нибудь перечисляемые simpleType, чтобы добавить новую перечисляемую величину UNK, которая неизвестно что значит. Разумеется, вы просто хотите скорректировать перечисления, которые соответствуют формату, при котором используются строки длиной три символа, и вам не нужно исправлять числовые или иные перечисления.

Несмотря на то, что технология XSLT могла бы найти все объявления simpleType, она не может понять отношения между типами и базовыми типами или просто вычислить значения фасетов в этих типах. Модельное представление абстрактных отношений Infoset в схеме включает нечто, как simpleType.getEffectiveEnumerationFacets(), что учитывает базовые типы, ссылки и другие отношения в схеме. Этот метод возвращает полный список перечислений в этом simpleType, к которому можно обращаться с запросом и который, если это необходимо, можно скорректировать новыми величинами. Модель также позволяет включать поддержку управления пространствами имен и разрешать другие типы в любой точке схемы, что было бы сложно сделать при помощи прочих инструментальных средств.

Код примера

Пример кода, рассматриваемый в этой статье, демонстрируется с помощью программы MakeSoapCompatible.java, интересующиеся могут изучить комментарии, содержащиеся в полном коде. К программе прилагается документ простой схемы MakeSoapCompatible.xsd, который показывает базовую форму заказа на покупку, в которой необходимо заменить атрибуты на элементы. Указанную программу также можно применять для работы с другими документами схем. Чтобы программа могла работать автономно необходимо наличие Модели XSD Schema Infoset и оболочки моделирования Eclipse

Эту программу и программы-утилиты можно скачать в виде одного zip-файла (см. Ресурсы).

Копии двух других java-файлов утилит, обычно поставляемых вместе с Моделью XSD Schema Infoset (версии 1.0.1 и выше), содержатся в коде с комментариями. Эти утилиты позволяют реализовать некоторые другие полезные технологии, а именно:

XSDSchemaQueryTools.java демонстрирует несколько других способов выполнения сложных запросов к компонентам схемы.

XSDSchemaBuildingTools.java содержит удобные методы программного построения схем.

Ресурсы

Об авторе

Шейн Куркуру - разработчик и инженер качества компаний Lotus и IBM с двенадцатилетним стажем, член Apache Software Foundation. Он работал над таким проектами, как Lotus 1-2-3, Lotus eSuite, XSLT-процессор Xalan-J компании Apache, и всевозможными инструментальными средствами для XML схем. Шейн Куркуру доступен по адресу: shane_curcuru@us.ibm.com.