Получение информации о сервисе из онтологии
Теперь, когда мы знаем, с каким сервисом желает работать пользователь, необходимо извлечь информацию об этом сервисе из онтологии. Для этого необходимо добавить API к методу doPost().
Добавление информации о сервисе к онтологии
Когда мы создавали онтологию в четвертой части, мы включали базовую информацию о сервисе, а теперь нам нужны более подробные данные. В частности, нам нужно добавить дополнительную информацию о сервисе, такую как данные о выражениях записей и шаблоне, определенных в текущем классе Service, а также о способе передачи сервису параметров, например, типа поиска или идентификатора разработчика (см. листинг 13):
Листинг 13. Дополнение определения сервиса
...
<owl:Class rdf:ID="Service">
<rdfs:label>Web Service</rdfs:label>
<rdfs:subClassOf>
<owl:Restriction>
<owl:onProperty rdf:resource="#endpoint" />
<owl:minCardinality rdf:datatype=
"&xsd;nonNegativeInteger">1</owl:minCardinality>
</owl:Restriction>
</rdfs:subClassOf>
<rdfs:subClassOf>
<owl:Restriction>
<owl:onProperty rdf:resource="#template" />
<owl:minCardinality rdf:datatype=
"&xsd;nonNegativeInteger">1</owl:minCardinality>
</owl:Restriction>
</rdfs:subClassOf>
</owl:Class>
<owl:DatatypeProperty rdf:ID="endpoint">
<rdfs:domain rdf:resource="#Service"/>
<rdfs:range rdf:resource=
"http://www.w3.org/2001/XMLSchema#anyURI"/>
</owl:DatatypeProperty>
<owl:ObjectProperty rdf:ID="inputparameter">
<rdfs:domain rdf:resource="#Service"/>
<rdfs:range rdf:resource="#ServiceParameterMap"/>
</owl:ObjectProperty>
<owl:DatatypeProperty rdf:ID="rootoutputnode">
<rdfs:domain rdf:resource="#Service"/>
<rdfs:range rdf:resource="&xsd;string"/>
</owl:DatatypeProperty>
<owl:DatatypeProperty rdf:ID="xsltTransformationString">
<rdfs:domain rdf:resource="#Service"/>
<rdfs:range rdf:resource="&xsd;string"/>
</owl:DatatypeProperty>
<owl:DatatypeProperty rdf:ID="queryParameter">
<rdfs:domain rdf:resource="#Service"/>
<rdfs:range rdf:resource="&xsd;string"/>
</owl:DatatypeProperty>
<owl:DatatypeProperty rdf:ID="template">
<rdfs:domain rdf:resource="#Service"/>
<rdfs:range rdf:resource="&xsd;string"/>
</owl:DatatypeProperty>
<owl:DatatypeProperty rdf:ID="elementValue">
<rdfs:domain rdf:resource="#Service"/>
<rdfs:range rdf:resource="&xsd;string"/>
</owl:DatatypeProperty>
<owl:DatatypeProperty rdf:ID="attributeValue">
<rdfs:domain rdf:resource="#Service"/>
<rdfs:range rdf:resource="&xsd;string"/>
</owl:DatatypeProperty>
<owl:Class rdf:ID="ServiceParameterMap">
<rdfs:label>Web Service Parameter Map</rdfs:label>
<rdfs:subClassOf>
<owl:Restriction>
<owl:onProperty rdf:resource="#paramname" />
<owl:cardinality
rdf:datatype="&xsd;nonNegativeInteger">1</owl:cardinality>
</owl:Restriction>
</rdfs:subClassOf>
<rdfs:subClassOf>
<owl:Restriction>
<owl:onProperty rdf:resource="#paramvalue" />
<owl:cardinality
rdf:datatype="&xsd;nonNegativeInteger">1</owl:cardinality>
</owl:Restriction>
</rdfs:subClassOf>
</owl:Class>
<owl:DatatypeProperty rdf:ID="paramname">
<rdfs:domain rdf:resource="#ServiceParameterMap"/>
<rdfs:range rdf:resource="&xsd;string"/>
</owl:DatatypeProperty>
<owl:DatatypeProperty rdf:ID="paramvalue">
<rdfs:domain rdf:resource="#ServiceParameterMap"/>
<rdfs:range rdf:resource="&xsd;string"/>
</owl:DatatypeProperty>
...
|
Вся информация, например, шаблон выражения записей (называемый здесь корневым узлом), очень проста. Мы можем просто добавить ее к классу в виде свойства. В некоторых случаях ситуация не будет такой простой. Например, у входных параметров есть название и значение, поэтому для хранения этой информации необходимо разработать отдельную структуру.
В этом случае структура называется ServiceParameterMap.
ServiceParameterMap указывает на ресурсы paramname и paramvalue. После того как все эти свойства созданы, мы можем создать непосредственно индивидные концепты.
Отдельный сервис в рамках онтологии
Индивидные концепты сервисов создаются очень просто; достаточно следовать структуре класса (см. листинг 14):
Листинг 14. Создание индивидного концепта Bookstore
<Bookstore rdf:ID="Amazon.com">
<endpoint>http://webservices.amazon.com/onca/xml</endpoint>
<rootoutputnode>/ItemSearchResponse/Items/Item</rootoutputnode>
<inputparameter rdf:resource="#amazonInput1"/>
<inputparameter rdf:resource="#amazonInput2"/>
<inputparameter rdf:resource="#amazonInput3"/>
<inputparameter rdf:resource="#amazonInput4"/>
<inputparameter rdf:resource="#amazonInput5"/>
<xsltTransformationString>
<![CDATA[
TBD
]]>
</xsltTransformationString>
<template>
<![CDATA[
<p><a value='href'><value/></a> by
<value/></p>
]]>
</template>
<elementValue>ItemAttributes/Title!ItemAttributes/Author</elementValue>
<attributeValue>DetailPageURL</attributeValue>
<queryParameter>Title</queryParameter>
</Bookstore>
<ServiceParameterMap rdf:ID="amazonInput1">
<paramname>Service</paramname>
<paramvalue>AWSECommerceService</paramvalue>
</ServiceParameterMap>
<ServiceParameterMap rdf:ID="amazonInput2">
<paramname>AWSAccessKeyId</paramname>
<paramvalue>000000000000000</paramvalue>
</ServiceParameterMap>
<ServiceParameterMap rdf:ID="amazonInput3">
<paramname>SearchIndex</paramname>
<paramvalue>Books</paramvalue>
</ServiceParameterMap>
<ServiceParameterMap rdf:ID="amazonInput5">
<paramname>ResponseGroup</paramname>
<paramvalue>Large</paramvalue>
</ServiceParameterMap>
<ServiceParameterMap rdf:ID="amazonInput4">
<paramname>Operation</paramname>
<paramvalue>ItemSearch</paramvalue>
</ServiceParameterMap>
|
Часть свойств, например, template и elementValue, знакомы вам по классу Service. Другие, например, ресурсы inputparameter, новые, созданные в виде отдельных экземпляров класса
ServiceParameterMap, на которые впоследствии основное тело концепта сервиса ссылается как на свойство inputparameter.
Кроме того, хотя может показаться разумным включить каждое значение элемента
elementValue и значение атрибута attributeValue в отдельное свойство, это вызовет проблемы в дальнейшем, так как вы не можете предсказать порядок, в котором они будут извлекаться, но вам нужно соблюсти порядок их вывода. чтобы решить эту проблему, включите все значения по порядку, разделяя их восклицательным знаком (!). Маловероятно, что восклицательный знак появится в каком-либо из выражений XPath, поэтому мы можем использовать его в качестве разделителя.
Обратите внимание, что вы можете просто добавить параметры к концу строки, но следование вышеприведенным рекомендациям даст несколько преимуществ. Во-первых, это позволит вам динамически изменять такие параметры, как, например, ключ разработчика. Во-вторых, это делает структуру более общей, тем самым облегчая адаптацию приложений как для сервисов SOAP, так и для сервисов REST.
Теперь давайте рассмотрим извлечение информации.
Извлечение индивидного концепта
Первый шаг состоит в получении ссылки на ресурс, представляющий индивидный концепт в онтологии. Поскольку значение, возвращаемое полем выбора, является URI ресурса, этот выбор осуществляется очень просто (см. листинг 15):
Листинг 15. Получение индивидного концепта
...
public class MashupOntologyReader {
private OntModel bookStoreOntModel;
public MashupOntologyReader(){
...
}
public Individual[] getIndividuals(String individualType){
...
return individuals;
}
public void obtainServiceInfo(String serviceURI){
Individual bookstore =
bookStoreOntModel.getIndividual(serviceURI);
}
}
public static void main(String[] args) {
MashupOntologyReader reader = new MashupOntologyReader();
Individual[] individuals = reader.getIndividuals("Bookstore");
reader.obtainServiceInfo(
"http://example.com/store#Amazon.com");
}
}
|
Создаем новый метод obtainServiceInfo() в классе
MashupOntologyGreater и с помощью метода getIndividual() получаем ссылку на нужный ресурс.
Теперь мы можем извлечь информацию о сервисе.
Получение параметров сервиса
Параметры сервиса хранятся как свойства ресурса, который представляет сервис, поэтому для получения информации нам нужно считать эти свойства (см. листинг 16):
Листинг 16. Получение параметров сервиса
...
public class MashupOntologyReader {
private OntModel bookStoreOntModel;
public MashupOntologyReader(){
...
}
public Individual[] getIndividuals(String individualType){
...
return individuals;
}
public void obtainServiceInfo(String serviceURI){
Individual bookstore =
bookStoreOntModel.getIndividual(serviceURI);
for (StmtIterator i = bookstore.listProperties( );
i.hasNext(); ) {
Statement s = i.nextStatement();
Property p = s.getPredicate();
if (p.canAs(OntProperty.class)){
Resource r;
Literal l;
if (p.getLocalName().equals("endpoint")){
l = s.getLiteral();
System.out.println( p.getLocalName() + " ==> "
+ l.getString() );
}
else if (p.getLocalName().equals("rootoutputnode")){
l = s.getLiteral();
System.out.println( p.getLocalName() + " ==> "
+ l.getString() );
}
else if (p.getLocalName().equals("inputparameter")){
r = s.getResource();
System.out.println( getParameterName(r.getURI())
+ " ==> " + getParameterValue(r.getURI()));
}
else if (p.getLocalName()
.equals("xsltTransformationString")){
l = s.getLiteral();
System.out.println( p.getLocalName() + " ==> "
+ l.getString() );
}
else if (p.getLocalName().equals("template")){
l = s.getLiteral();
System.out.println( p.getLocalName() + " ==> "
+ l.getString() );
}
else if (p.getLocalName().equals("elementValue")){
l = s.getLiteral();
System.out.println( p.getLocalName() + " ==> "
+ l.getString() );
}
else if (p.getLocalName().equals("attributeValue")){
l = s.getLiteral();
System.out.println( p.getLocalName() + " ==> "
+ l.getString() );
}
else if (p.getLocalName().equals("queryParameter")){
l = s.getLiteral();
System.out.println( p.getLocalName() + " ==> "
+ l.getString() );
}
}
}
}
public String getParameterName(String uri){
// this assumes the uri is to a ServiceParameterMap
OntResource or = bookStoreOntModel.getOntResource(uri);
String toReturn=null;
for (StmtIterator i = or.listProperties( ); i.hasNext(); ) {
Statement s = i.nextStatement();
Property p = s.getPredicate();
if (p.canAs(OntProperty.class)){
Resource r;
Literal l;
if (p.getLocalName().equals("paramname")){
l = s.getLiteral();
toReturn=l.getString();
}
}
}
return toReturn;
}
public String getParameterValue(String uri){
// this assumes the uri is to a ServiceParameterMap
OntResource or = bookStoreOntModel.getOntResource(uri);
String toReturn=null;
for (StmtIterator i = or.listProperties( ); i.hasNext(); ) {
Statement s = i.nextStatement();
Property p = s.getPredicate();
if (p.canAs(OntProperty.class)){
Resource r;
Literal l;
if (p.getLocalName().equals("paramvalue")){
l = s.getLiteral();
toReturn=l.getString();
}
}
}
return toReturn;
}
public static void main(String[] args) {
MashupOntologyReader reader = new MashupOntologyReader();
Individual[] individuals = reader.getIndividuals("Bookstore");
reader.obtainServiceInfo(
"http://example.com/store#Amazon.com");
}
}
|
К сожалению, мы не можем просто считать нужное свойство. Вместо этого нам придется организовать цикл по всем свойствам с помощью метода listProperties()
, который снова возвращает Iterator.
Вероятно, вы помните, что в RDF свойства представлены предложениями (или операторами), такими, например, как "The Amazon.com service has an endpoint of http://webservices.amazon.com/onca/xml" (конечная точка сервиса Amazon.com - http://webservices.amazon.com/onca/xml). С точки зрения грамматики фраза "has an endpoint of http://webservices.amazon.com/onca/xml" представляет собой сказуемое предложения. Поэтому первым шагом в извлечении свойства является чтение предложения и извлечение из него сказуемого.
Теперь, когда у нас есть сказуемое, мы должны убедиться в том, что оно на самом деле является свойством онтологии. В конце концов это может быть просто свойство RDF или еще что-либо аномальное. Теперь, когда вы знаете, что это действительно свойство онтологии, мы можем вызвать метод localName и сравнить его с ожидаемым параметром.
Теперь, когда мы знаем, с каким свойством имеем дело, действия с ним зависят от его типа. Некоторые свойства, например, template, являются простыми строковыми литералами, и мы можем извлечь их напрямую. Однако другие свойства, например inputparameter, являются ресурсами, и мы не можем просто извлечь из них литеральное значение. Вместо этого нам придется извлекать их свойства. Например, inputparameter
состоит из объекта со свойствами
paramname и paramvalue. Чтобы найти эти свойства, мы можем создать методы getParameterName() и getParameterValue()
, которые организуют цикл по свойствам указанного ресурса.
При запуске приложения мы получаем результат, похожий на рисунок 2.
Рисунок 2. Информация о сервисе
Обновленный класс сервиса
От всей этой информации не будет никакой пользы, пока мы не сохраним ее так, чтобы можно было иметь к ней доступ. На текущий момент приложение настроено на анализ и выполнение массива объектов Service, поэтому лучшим решением будет изменить класс
Service таким образом, чтобы в него можно было записать данные (см. листинг 17):
Листинг 17. Обновленный класс Service
import java.util.Vector;
public class Service {
String name = "";
String baseURL = "";
String template = "";
Vector inputParameters = new Vector();
String queryParameter = "";
String recordExp = "";
String xsltTransformationString = "";
Vector elementValues = new Vector();
Vector attributeValues =
new Vector();
String filterExp = "";
Service subSvc = null;
}
|
Изначально целью класса Service было предоставление контейнера для данных, создание произвольных объектов и их возвращения. Теперь нам это не нужно - все данные поступают из онтологии. Все, что нам нужно сделать - убедиться, что у нас есть переменные для каждого элемента данных, которые мы желаем сохранить, например, inputparameters и
xsltTransformationString.
Кроме того, Чтобы сделать класс более гибким, изменим elementValues и
attributeValues на Vector, вместо массивов фиксированного размера, какими они были раньше.
Теперь мы можем наполнить Service.
Наполнение сервиса
Мы уже проделали всю самую трудную работу по извлечению информации из онтологии. Все, что нужно сделать сейчас - создать новый объект Service и добавить к нему информацию (см. листинг 18):
Листинг 18. Наполнение Service
...
public Service obtainServiceInfo(String serviceURI){
Individual bookstore =
bookStoreOntModel.getIndividual(serviceURI);
Service svc = new Service();
for (StmtIterator i = bookstore.listProperties( );
i.hasNext(); ) {
Statement s = i.nextStatement();
Property p = s.getPredicate();
if (p.canAs(OntProperty.class)){
Resource r;
Literal l;
if (p.getLocalName().equals("endpoint")){
l = s.getLiteral();
svc.baseURL=l.getString();
System.out.println( p.getLocalName() + " ==> "
+ l.getString() );
}
else if (p.getLocalName().equals("rootoutputnode")){
l = s.getLiteral();
svc.recordExp=l.getString();
System.out.println( p.getLocalName() + " ==> "
+ l.getString() );
}
else if (p.getLocalName().equals("inputparameter")){
r = s.getResource();
svc.inputParameters.add(r.getURI());
System.out.println( getParameterName(r.getURI())
+ " ==> " + getParameterValue(r.getURI()));
}
else if (p.getLocalName()
.equals("xsltTransformationString")){
l = s.getLiteral();
svc.xsltTransformationString=l.getString();
System.out.println( p.getLocalName() + " ==> "
+ l.getString() );
}
else if (p.getLocalName().equals("template")){
l = s.getLiteral();
svc.template=l.getString();
System.out.println( p.getLocalName() + " ==> "
+ l.getString() );
}
else if (p.getLocalName().equals("elementValue")){
l = s.getLiteral();
String[] elementValues = l.getString().split("!");
for (int j = elementValues.length; j>0; j--){
svc.elementValues.add(elementValues[(j-1)]);
}
System.out.println( p.getLocalName() + " ==> "
+ l.getString() );
}
else if (p.getLocalName().equals("attributeValue")){
l = s.getLiteral();
String[] attributeValues = l.getString().split("!");
for (int j = attributeValues.length; j>0; j--){
svc.attributeValues.add(attributeValues[(j-1)]);
}
System.out.println( p.getLocalName() + " ==> "
+ l.getString() );
}
else if (p.getLocalName().equals("queryParameter")){
l = s.getLiteral();
svc.queryParameter = l.getString();
System.out.println( p.getLocalName() + " ==> "
+ l.getString() );
}
}
}
return svc;
}
...
|
Обратите внимание, что в этом методе мы обрабатываем отличие в названиях между классом Service и онтологией. Пусть, например, мы установили соответствие между endpoint и baseURL.
Также стоит отметить, что строковые литералы (например, шаблон) добавляются напрямую в класс, а свойства, представляющие ресурсы, например inputparameter, добавляются не как литералы, а как URI ресурса. (Вы можете принять решение, что для приложения будет лучше хранить всю информацию непосредственно в классе. В этом случае вам придется выполнить здесь дополнительные действия.) Кроме того, мы разделяем значения свойств
elementValue и attributeValue в массив, элементы которого будем использовать для наполнения Service.
Следующим шагом будет формирование запроса REST.
Формирование запроса
Сейчас у нас есть вся информация о сервисе, но у нас нет запроса, который мы могли бы ему послать, поскольку все входные параметры разбиты по отдельным ресурсам. Создание такого запроса зависит от сервиса и не входит в онтологию и сервлет, поэтому мы включим его в класс Service (см. листинг 19):
Листинг 19. Формирование запроса
import java.util.Vector;
public class Service {
String name = "";
...
public String getRESTRequest(MashupOntologyReader ont){
String restQuery = this.baseURL;
for (int i=0;i<this.inputParameters.size();i++){
String serviceParameterMapURI =
(String) this.inputParameters.elementAt(i);
if (restQuery.equals(this.baseURL))
restQuery = restQuery + "?";
else
restQuery = restQuery + "&";
restQuery = restQuery +
ont.getParameterName(serviceParameterMapURI) + "=" +
ont.getParameterValue(serviceParameterMapURI);
}
restQuery = restQuery + "&" + this.queryParameter + "=";
return restQuery;
}
}
|
В методе getRESTRequest() выполняем в цикле обработку каждого из inputParameters, добавляя их к baseURL.
И, в заключение, добавляем к концу ссылки параметр запроса и знак равенства (=).
Таким образом, когда мы добавляем ключевое слово, по которому будет осуществляться поиск, к концу запроса, мы получаем полную ссылку запроса, например:
http://webservices.amazon.com/onca/xml?ResponseGroup=Large&Service=
AWSECommerceService&AWSAccess KeyId=000000000000000&SearchIndex=
Books&Operation=ItemSearch&Title=Star+Wars
|
Примечание: Приведенный выше код обычно записывается в одну строку. В целях форматирования здесь он показан в несколько строк.
Выполнение запроса
Фактический запрос подается почти точно так же, как и ранее (см. листинг 20):
Листинг 20. Выполнение запроса
...
protected void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
MashupOntologyReader ont = new MashupOntologyReader();
String query = request.getParameter("query").toString();
String serviceURI =
request.getParameter("serviceURI").toString();
Service service = ont.obtainServiceInfo(serviceURI);
try {
DocumentBuilder builder =
DocumentBuilderFactory.newInstance()
.newDocumentBuilder();
Service[] svcs = {service};
Document hostDoc = builder.parse(
new InputSource(new StringReader("<results/>")));
Node hostRoot = hostDoc.getDocumentElement();
for (int k=0; k < svcs.length; k++){
Service svc = svcs[k];
Node renderedService =
renderService(svc.getRESTRequest(ont)+query,
hostDoc);
if (renderedService != null){
Element nameElement = hostDoc.createElement("h1");
nameElement.appendChild(
hostDoc.createTextNode(svc.name));
...
|
Сначала нам нужно загрузить онтологию - она нужна и для заполнения информации о сервисе, и для создания запроса REST. После того как информация о сервисе заполнена, с ее помощью можно заменить элементы массива services
одним элементом. Дальше мы действуем так же, как и раньше, за исключением того, что при вызове метода renderService()
нужно создать запрос.
Теперь можно запустить приложение и получить результат, похожий на тот, что получался в более простой версии приложения.
|