В сегодняшнем посте мы рассмотрим не только спецификацию 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 .