В сегодняшнем посте мы рассмотрим не только спецификацию JAX-RS 2.0, но и полезные расширения, которые Apache CXF , одна из популярных реализаций JAX-RS 2.0 , предлагает разработчикам сервисов и API-интерфейсов REST . В частности, мы поговорим о расширении поиска с использованием подмножества фильтров запросов OData 2.0 .
В двух словах, расширение поиска просто отображает какое-то выражение фильтра на набор совпадающих типизированных объектов (экземпляров классов Java). В OData 2.0 фильтры запросов могут быть очень сложными , однако на данный момент Apache CXF поддерживает только подмножество из них:
| оператор | Описание | пример |
|---|---|---|
| уравнение | равных | городской экв ‘Редмонд’ |
| Небраска | Не равный | город н ‘Лондон’ |
| GT | Лучше чем | цена GT 20 |
| GE | Больше или равно | цена ge 10 |
| л | Меньше, чем | цена 20 лт |
| ле | Меньше или равно | цена ле 100 |
| и | Логический и | цена ле 200 и цена гт 3,5 |
| или же | Логический или | цена ле 3,5 или цена гт 200 |
По сути, для настройки и активации расширения поиска для ваших служб JAX-RS достаточно определить два свойства: search.query.parameter.name и search.parser , а также один дополнительный поставщик SearchContextProvider :
@Configuration
public class AppConfig {
@Bean( destroyMethod = "shutdown" )
public SpringBus cxf() {
return new SpringBus();
}
@Bean @DependsOn( "cxf" )
public Server jaxRsServer() {
final Map< String, Object > properties = new HashMap< String, Object >();
properties.put( "search.query.parameter.name", "$filter" );
properties.put( "search.parser", new ODataParser< Person >( Person.class ) );
final JAXRSServerFactoryBean factory =
RuntimeDelegate.getInstance().createEndpoint(
jaxRsApiApplication(),
JAXRSServerFactoryBean.class
);
factory.setProvider( new SearchContextProvider() );
factory.setProvider( new JacksonJsonProvider() );
factory.setServiceBeans( Arrays.< Object >asList( peopleRestService() ) );
factory.setAddress( factory.getAddress() );
factory.setProperties( properties );
return factory.create();
}
@Bean
public JaxRsApiApplication jaxRsApiApplication() {
return new JaxRsApiApplication();
}
@Bean
public PeopleRestService peopleRestService() {
return new PeopleRestService();
}
}
В search.query.parameter.name определяет то , что было бы имя запрос параметра строки , используемым в качестве фильтра (мы устанавливаем его , чтобы быть $ фильтра ), в то время как search.parser определяет анализатор будет использоваться для разбора выражения фильтра (мы устанавливаем это быть ODataParser параметризованным с классом Person ). ODataParser построен на вершине превосходного Apache Olingo проекта , который в настоящее время реализует OData 2.0 протокола (поддержку для OData 4.0 находится на пути).
Как только конфигурация выполнена, любая служба JAX-RS 2.0 может воспользоваться возможностями поиска, введя контекстный параметр SearchContext . Давайте посмотрим на это в действии, определив службу REST для управления людьми, представленными следующим классом Person :
public class Person {
private String firstName;
private String lastName;
private int age;
// Setters and getters here
}
В PeopleRestService просто позволит создать новое лицо с помощью HTTP POST и выполнить поиск с помощью HTTP GET , перечисленный в / поиске конечной точки:
package com.example.rs;
import java.util.ArrayList;
import java.util.Collection;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import org.apache.cxf.jaxrs.ext.search.SearchCondition;
import org.apache.cxf.jaxrs.ext.search.SearchContext;
import com.example.model.Person;
@Path( "/people" )
public class PeopleRestService {
private final Collection< Person > people = new ArrayList<>();
@Produces( { MediaType.APPLICATION_JSON } )
@POST
public Response addPerson( @Context final UriInfo uriInfo,
@FormParam( "firstName" ) final String firstName,
@FormParam( "lastName" ) final String lastName,
@FormParam( "age" ) final int age ) {
final Person person = new Person( firstName, lastName, age );
people.add( person );
return Response
.created( uriInfo.getRequestUriBuilder().path( "/search" )
.queryParam( "$filter=firstName eq '{firstName}' and lastName eq '{lastName}' and age eq {age}" )
.build( firstName, lastName, age ) )
.entity( person ).build();
}
@GET
@Path("/search")
@Produces( { MediaType.APPLICATION_JSON } )
public Collection< Person > findPeople( @Context SearchContext searchContext ) {
final SearchCondition< Person > filter = searchContext.getCondition( Person.class );
return filter.findAll( people );
}
}
Метод findPeople — это тот, который мы ищем. Благодаря всем сложностям, которые делает Apache CXF , метод выглядит очень простым: SearchContext внедряется, а выражение фильтра автоматически выбирается из параметра строки запроса $ filter . Последняя часть заключается в применении фильтра к данным, который в нашем случае является просто набором людей . Очень чистый и понятный.
Давайте построим проект и запустим его:
mvn clean package java -jar target/cxf-search-extension-0.0.1-SNAPSHOT.jar
Используя удивительный инструмент curl , давайте выполним пару запросов HTTP POST, чтобы сгенерировать некоторые данные для выполнения запросов фильтра:
> curl http://localhost:8080/rest/api/people -X POST -d "firstName=Tom&lastName=Knocker&age=16"
{
"firstName": "Tom",
"lastName": "Knocker",
"age": 16
}
> curl http://localhost:8080/rest/api/people -X POST -d "firstName=Bob&lastName=Bobber&age=23"
{
"firstName": "Bob",
"lastName": "Bobber",
"age": 23
}
> curl http://localhost:8080/rest/api/people -X POST -d "firstName=Tim&lastName=Smith&age=50"
{
"firstName": "Tim",
"lastName": "Smith",
"age": 50
}
Имея образцы данных, давайте продолжим и предложим пару различных критериев поиска, достаточно сложных, чтобы продемонстрировать мощь фильтров запросов OData 2.0 :
- найти все лицо , чьи первое имя является Боб ($ = фильтр «ПгвЬЫат эк„Bob“» )
> curl -G -X GET http://localhost:8080/rest/api/people/search --data-urlencode
$filter="firstName eq 'Bob'"
[
{
"firstName": "Bob",
"lastName": "Bobber",
"age": 23
}
]
> curl -G -X GET http://localhost:8080/rest/api/people/search --data-urlencode
$filter="lastName eq 'Bobber' or (lastName eq 'Smith' and firstName ne 'Bob')"
[
{
"firstName": "Bob",
"lastName": "Bobber",
"age": 23
},
{
"firstName": "Tim",
"lastName": "Smith",
"age": 50
}
]
> curl -G -X GET http://localhost:8080/rest/api/people/search --data-urlencode
$filter="firstName eq 'T*' and age ge 16"
[
{
"firstName": "Tom",
"lastName": "Knocker",
"age": 16
},
{
"firstName": "Tim",
"lastName": "Smith",
"age": 50
}
]
Примечание : если вы запускаете эти команды в Linux-подобной среде, вам может потребоваться экранировать знак $, используя вместо этого \ $ , например: curl -X GET -G http: // localhost: 8080 / rest / api / people / search —data-urlencode \ $ filter = «firstName eq ‘Bob’»
На данный момент Apache CXF предлагает только базовую поддержку фильтров запросов OData 2.0 со многими мощными выражениями в стороне. Тем не менее, существует обязательство продвигать его вперед, как только сообщество проявит достаточный интерес к использованию этой функции.
Стоит отметить, что фильтры запросов OData 2.0 — не единственная доступная опция. Расширение поиска также поддерживает FIQL (язык запросов элементов фида ), и эта замечательная статья от одного из основных разработчиков Apache CXF является прекрасным введением в него.
Я думаю, что эта весьма полезная функция Apache CXF может сэкономить много вашего времени и усилий, предоставляя простые (и не такие простые) возможности поиска для ваших служб JAX-RS 2.0 . Пожалуйста, попробуйте, если он соответствует потребностям вашего приложения.
Полный исходный код проекта доступен на Github .