Рубрики
Без рубрики

REST Язык запросов – Реализация или операция

Узнайте, как расширить возможности API, реализуя операцию ор-запроса

Автор оригинала: baeldung.

1. Обзор

В этой быстрой статье мы расширим расширенные поисковые операции, которые мы реализовали в предыдущая статья и включают Or-основанные критерии поиска в нашем языке запроса REST API .

2. Подход к осуществлению

Раньше все критерии в поиск параметр запроса формируется предикатами, сгруппированными только оператором AND. Давайте изменим это.

Мы должны быть в состоянии реализовать эту функцию либо как простое, быстрое изменение существующего подхода, либо как новое с нуля.

При простом подходе мы помеем критерии, указывающие на то, что он должен быть объединен с помощью оператора OR.

Например, вот URL для проверки API на ” первоеимя ИЛИ последнееимя”:

http://localhost:8080/users?search=firstName:john,'lastName:doe

Обратите внимание, что мы отметили критерии последнееимя с одной цитатой, чтобы дифференцировать его. Мы будем захватывать этот предикат для оператора OR в нашем объекте значения критериев – SpecSearchКритерия:

public SpecSearchCriteria(
  String orPredicate, String key, SearchOperation operation, Object value) {
    super();
    
    this.orPredicate 
      = orPredicate != null
      && orPredicate.equals(SearchOperation.OR_PREDICATE_FLAG);
    
    this.key = key;
    this.operation = operation;
    this.value = value;
}

3. Улучшение userSpecificationBuilder

Теперь давайте изменим наш спецификации строитель, UserSpecificationBuilder, учитывать критерии OR при строительстве Спецификация :

public Specification build() {
    if (params.size() == 0) {
        return null;
    }
    Specification result = new UserSpecification(params.get(0));

    for (int i = 1; i < params.size(); i++) {
        result = params.get(i).isOrPredicate()
          ? Specification.where(result).or(new UserSpecification(params.get(i))) 
          : Specification.where(result).and(new UserSpecification(params.get(i)));
    }
    return result;
 }

4. Улучшение управления пользователями

Наконец, давайте навеем новую конечную точку REST в нашем контроллере, чтобы использовать эту функциональность поиска с оператором OR. Улучшенная логика разбора извлекает специальный флаг, который помогает в определении критериев с оператором OR:

@GetMapping("/users/espec")
@ResponseBody
public List findAllByOrPredicate(@RequestParam String search) {
    Specification spec = resolveSpecification(search);
    return dao.findAll(spec);
}

protected Specification resolveSpecification(String searchParameters) {
    UserSpecificationsBuilder builder = new UserSpecificationsBuilder();
    String operationSetExper = Joiner.on("|")
      .join(SearchOperation.SIMPLE_OPERATION_SET);
    Pattern pattern = Pattern.compile(
      "(\\p{Punct}?)(\\w+?)("
      + operationSetExper 
      + ")(\\p{Punct}?)(\\w+?)(\\p{Punct}?),");
    Matcher matcher = pattern.matcher(searchParameters + ",");
    while (matcher.find()) {
        builder.with(matcher.group(1), matcher.group(2), matcher.group(3), 
        matcher.group(5), matcher.group(4), matcher.group(6));
    }
    
    return builder.build();
}

5. Тест в реальном 2000 году с условием OR

В этом примере живого теста, с новой конечной точкой API, мы будем искать пользователей по имени “Джон” ИЛИ фамилия “doe”. Обратите внимание, что последнееимя имеет одну цитату, которая квалифицирует его как “OR предикат”:

private String EURL_PREFIX
  = "http://localhost:8082/spring-rest-full/auth/users/espec?search=";

@Test
public void givenFirstOrLastName_whenGettingListOfUsers_thenCorrect() {
    Response response = givenAuth().get(EURL_PREFIX + "firstName:john,'lastName:doe");
    String result = response.body().asString();

    assertTrue(result.contains(userJohn.getEmail()));
    assertTrue(result.contains(userTom.getEmail()));
}

6. Испытание настойчивости с условием ИЛИ

Теперь давайте выполнить тот же тест, который мы сделали выше, на уровне настойчивости для пользователей с именем “Джон” ИЛИ фамилия “doe” :

@Test
public void givenFirstOrLastName_whenGettingListOfUsers_thenCorrect() {
    UserSpecificationsBuilder builder = new UserSpecificationsBuilder();

    SpecSearchCriteria spec 
      = new SpecSearchCriteria("firstName", SearchOperation.EQUALITY, "john");
    SpecSearchCriteria spec1 
      = new SpecSearchCriteria("'","lastName", SearchOperation.EQUALITY, "doe");

    List results = repository
      .findAll(builder.with(spec).with(spec1).build());

    assertThat(results, hasSize(2));
    assertThat(userJohn, isIn(results));
    assertThat(userTom, isIn(results));
}

7. Альтернативный подход

В альтернативном подходе мы могли бы предоставить поисковый запрос, больше похожий на полную ГДЕ оговорка запроса S’L.

Например, вот URL для более сложного поиска по первоеимя и возраст:

http://localhost:8080/users?search=( firstName:john OR firstName:tom ) AND age>22

Обратите внимание, что мы разделили отдельные критерии, операторы и группировки скобки с пространством для формирования действительного выражения infix.

Давайте разобрать выражение infix с помощью КритерииПарсер . Наша КритерииПарсер делит данное выражение infix на токены (критерии, скобки, и операторы OR) и создает выражение postfix для того же:

public Deque parse(String searchParam) {

    Deque output = new LinkedList<>();
    Deque stack = new LinkedList<>();

    Arrays.stream(searchParam.split("\\s+")).forEach(token -> {
        if (ops.containsKey(token)) {
            while (!stack.isEmpty() && isHigerPrecedenceOperator(token, stack.peek())) {
                output.push(stack.pop().equalsIgnoreCase(SearchOperation.OR_OPERATOR)
                  ? SearchOperation.OR_OPERATOR : SearchOperation.AND_OPERATOR);
            }
            stack.push(token.equalsIgnoreCase(SearchOperation.OR_OPERATOR) 
              ? SearchOperation.OR_OPERATOR : SearchOperation.AND_OPERATOR);

        } else if (token.equals(SearchOperation.LEFT_PARANTHESIS)) {
            stack.push(SearchOperation.LEFT_PARANTHESIS);
        } else if (token.equals(SearchOperation.RIGHT_PARANTHESIS)) {
            while (!stack.peek().equals(SearchOperation.LEFT_PARANTHESIS)) { 
                output.push(stack.pop());
            }
            stack.pop();
        } else {
            Matcher matcher = SpecCriteraRegex.matcher(token);
            while (matcher.find()) {
                output.push(new SpecSearchCriteria(
                  matcher.group(1), 
                  matcher.group(2), 
                  matcher.group(3), 
                  matcher.group(4), 
                  matcher.group(5)));
            }
        }
    });

    while (!stack.isEmpty()) {
        output.push(stack.pop());
    }
  
    return output;
}

Давайте добавим новый метод в нашу спецификацию строитель, GenericSpecificationBuilder, для построения поискового Спецификация от выражения postfix:

    public Specification build(Deque postFixedExprStack, 
        Function> converter) {

        Deque> specStack = new LinkedList<>();

        while (!postFixedExprStack.isEmpty()) {
            Object mayBeOperand = postFixedExprStack.pollLast();

            if (!(mayBeOperand instanceof String)) {
                specStack.push(converter.apply((SpecSearchCriteria) mayBeOperand));
            } else {
                Specification operand1 = specStack.pop();
                Specification operand2 = specStack.pop();
                if (mayBeOperand.equals(SearchOperation.AND_OPERATOR)) {
                    specStack.push(Specification.where(operand1)
                      .and(operand2));
                }
                else if (mayBeOperand.equals(SearchOperation.OR_OPERATOR)) {
                    specStack.push(Specification.where(operand1)
                      .or(operand2));
                }
            }
        }
        return specStack.pop();

Наконец, давайте добавим еще одну конечную точку REST в нашем ПользовательКонтроллер разобрать сложное выражение с помощью нового КритерииПарсер :

@GetMapping("/users/spec/adv")
@ResponseBody
public List findAllByAdvPredicate(@RequestParam String search) {
    Specification spec = resolveSpecificationFromInfixExpr(search);
    return dao.findAll(spec);
}

protected Specification resolveSpecificationFromInfixExpr(String searchParameters) {
    CriteriaParser parser = new CriteriaParser();
    GenericSpecificationsBuilder specBuilder = new GenericSpecificationsBuilder<>();
    return specBuilder.build(parser.parse(searchParameters), UserSpecification::new);
}

8. Заключение

В этом учебнике мы улучшили наш язык запросов REST с возможностью поиска с оператором OR.

Полную реализацию этой статьи можно найти в проект GitHub . Это maven основе проекта, поэтому она должна быть легко импортировать и работать, как она есть.