В сегодняшнем посте мы рассмотрим не только спецификацию 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 :
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
@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 определяет, каким будет имя параметра строки запроса, используемого в качестве фильтра (мы устанавливаем его равным $ filter ), тогда как search.parser определяет анализатор, который будет использоваться для анализа выражения фильтра (мы устанавливаем это быть ODataParser параметризованным с классом Person ). ODataParser построен на основе превосходного проекта Apache Olingo, который в настоящее время реализует протокол OData 2.0 (поддержка OData 4.0 уже на подходе).
Как только конфигурация выполнена, любая служба JAX-RS 2.0 может воспользоваться возможностями поиска, введя контекстный параметр SearchContext . Давайте посмотрим на это в действии, определив службу REST для управления людьми, представленными следующим классом Person :
1
2
3
4
5
6
7
|
public class Person { private String firstName; private String lastName; private int age; // Setters and getters here } |
PeopleRestService просто позволит создавать новых людей с помощью HTTP POST и выполнять поиск с использованием HTTP GET , перечисленных в / search endpoint:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
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 . Последняя часть заключается в применении фильтра к данным, который в нашем случае является просто набором людей . Очень чистый и понятный.
Давайте построим проект и запустим его:
1
2
|
mvn clean package java -jar target/cxf-search-extension- 0.0 . 1 -SNAPSHOT.jar |
Используя удивительный инструмент curl , давайте выполним пару запросов HTTP POST, чтобы сгенерировать некоторые данные для выполнения запросов фильтра:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
|
> 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 :
- найти всех людей, чье имя Боб ($ filter = «firstName eq ‘Bob’» )
1
2
3
4
5
6
7
8
9
|
> curl -G -X GET http: //localhost:8080/rest/api/people/search --data-urlencode $filter= "firstName eq 'Bob'" [ { "firstName" : "Bob" , "lastName" : "Bobber" , "age" : 23 } ] |
- найти всех людей, чья фамилия Боббер или фамилия Смит, а firstName не Боб ($ filter = «lastName eq« Bobber »или (lastName eq« Smith »и firstName ne« Bob »)» )
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
> 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 } ] |
- найти всех людей, чье имя начинается с буквы T, и которые старше 16 лет ($ filter = «firstName eq ‘T *’ и age ge 16» )
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
> 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 .
Ссылка: | Помимо спецификации JAX-RS: поисковое расширение Apache CXF от нашего партнера по JCG Андрея Редько в блоге Андрея Редько (devmind) . |