Строп-фильтр для упаковки тела запроса

Пример использования: Мы разрабатываем сайт закрытой группы пользователей AEM, где пользователи должны будут отправлять формы, запускающие рабочие процессы. Поскольку пользователи аутентифицированы, часть полезной нагрузки рабочего процесса должна включать пользователя, инициировавшего форму.

Я рассматриваю возможность использования для этого форм AEM, которые сохраняются на узлах под /content/usergenerated/content/forms/af/my-site, но пользователь не упоминается в полезной нагрузке (только пользователь службы). В этом случае есть два пользователя службы: служба рабочего процесса, выполняющая рабочий процесс, и служба fd, выполняющая обработку формы и первоначальное сохранение. НАПРИМЕР. следующий код вызывается из отчетов о шагах рабочего процесса 'fd-service'

workItem.getWorkflowData().getMetaDataMap().get("userId", String.class);

Чтобы обойти это ограничение,

Workflow initiated from publish AEM instance: All workflow instances are created using a service user when adaptive forms, interactive communications, or letters are submitted from AEM publish instance. In these cases, the user name of the logged-in user is not captured in the workflow instance data.

Я добавляю сервлет фильтра, чтобы перехватить отправку начальной формы перед сервлетом AEM Forms, используя оболочку запроса, чтобы изменить тело запроса, добавив исходный идентификатор пользователя.

Что касается форм, рабочих процессов и средств запуска ... Это в основном то, что у меня есть https://helpx.adobe.com/aem-forms/6/aem-workflows-submit-process-form.html

Я просмотрел следующие ресурсы:

Вот код моей обертки

import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.wrappers.SlingHttpServletRequestWrapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.ServletInputStream;
import java.io.*;

public class FormSubmitRequestWrapper extends SlingHttpServletRequestWrapper {
String requestPayload;
private static final Logger log = LoggerFactory.getLogger(FormSubmitRequestWrapper.class);

public FormSubmitRequestWrapper(SlingHttpServletRequest slingRequest) {
    super(slingRequest);

    // read the original payload into the requestPayload variable
    StringBuilder stringBuilder = new StringBuilder();
    BufferedReader bufferedReader = null;
    try {
        // read the payload into the StringBuilder
        InputStream inputStream = slingRequest.getInputStream();
        if (inputStream != null) {
            bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
            char[] charBuffer = new char[128];
            int bytesRead = -1;
            while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
                stringBuilder.append(charBuffer, 0, bytesRead);
            }
        } else {
            // make an empty string since there is no payload
            stringBuilder.append("");
        }
    } catch (IOException ex) {
        log.error("Error reading the request payload", ex);
    } finally {
        if (bufferedReader != null) {
            try {
                bufferedReader.close();
            } catch (IOException iox) {
                log.error("Error closing bufferedReader", iox);
            }
        }
    }
    requestPayload = stringBuilder.toString();
}

/**
 * Override of the getInputStream() method which returns an InputStream that reads from the
 * stored requestPayload string instead of from the request's actual InputStream.
 */
@Override
public ServletInputStream getInputStream ()
        throws IOException {

    final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(requestPayload.getBytes());
    ServletInputStream inputStream = new ServletInputStream() {
        public int read ()
                throws IOException {
            return byteArrayInputStream.read();
        }
    };
    return inputStream;
}
}

Вот мой фильтр

import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.engine.EngineConstants;
import org.osgi.framework.Constants;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


import javax.jcr.Session;
import javax.servlet.*;
import java.io.IOException;


@Component(service = Filter.class,
        immediate = true,

        property = {
                Constants.SERVICE_DESCRIPTION + "=Add the CUG userID to any UGC posts",
                EngineConstants.SLING_FILTER_SCOPE + " = " + EngineConstants.FILTER_SCOPE_REQUEST,
                Constants.SERVICE_RANKING + ":Integer=3000",
                EngineConstants.SLING_FILTER_PATTERN + "=/content/forms/af/my-site.*"
        })


public class DecorateUserGeneratedFilter implements Filter {

    private static final Logger log = LoggerFactory.getLogger(DecorateUserGeneratedFilter.class);

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        final SlingHttpServletResponse slingResponse = (SlingHttpServletResponse ) response;
        final SlingHttpServletRequest slingRequest= (SlingHttpServletRequest) request;

        FormSubmitRequestWrapper wrappedRequest = new FormSubmitRequestWrapper(slingRequest);

        log.info("starting ConfirmAlumniStatus workflow");
        log.info(getCurrentUserId(slingRequest));

        chain.doFilter(wrappedRequest, slingResponse);
    }

    @Override
    public void destroy() {

    }

    public String getCurrentUserId(SlingHttpServletRequest request) {
        ResourceResolver resolver = request.getResourceResolver();
        Session session = resolver.adaptTo(Session.class);
        String userId = session.getUserID();

        return userId;

    }

}

Когда отправка POST обрабатывается этим фильтром, я получаю сообщение об ошибке ниже, в котором говорится, что тело запроса уже прочитано. Таким образом, кажется, что рейтинг фильтра может быть недостаточно высоким.

25.06.2018 13:11:13.200 ERROR [0:0:0:0:0:0:0:1 [1529946669719] POST /content/forms/af/my-site/request-access/jcr:content/guideContainer.af.internalsubmit.jsp HTTP/1.1] org.apache.sling.engine.impl.SlingRequestProcessorImpl service: Uncaught Throwable java.lang.IllegalStateException: Request Data has already been read at org.apache.sling.engine.impl.request.RequestData.getInputStream(RequestData.java:669) at org.apache.sling.engine.impl.SlingHttpServletRequestImpl.getInputStream(SlingHttpServletRequestImpl.java:292) at javax.servlet.ServletRequestWrapper.getInputStream(ServletRequestWrapper.java:136) at my.site.servlets.FormSubmitRequestWrapper.(FormSubmitRequestWrapper.java:26) at my.site.servlets.DecorateUserGeneratedFilter.doFilter(DecorateUserGeneratedFilter.java:75) at org.apache.sling.engine.impl.filter.AbstractSlingFilterChain.doFilter(AbstractSlingFilterChain.java:68) at org.apache.sling.engine.impl.filter.AbstractSlingFilterChain.doFilter(AbstractSlingFilterChain.java:73) at org.apache.sling.engine.impl.filter.AbstractSlingFilterChain.doFilter(AbstractSlingFilterChain.java:73) at com.cognifide.cq.includefilter.DynamicIncludeFilter.doFilter(DynamicIncludeFilter.java:82) at org.apache.sling.engine.impl.filter.AbstractSlingFilterChain.doFilter(AbstractSlingFilterChain.java:68) at org.apache.sling.engine.impl.debug.RequestProgressTrackerLogFilter.doFilter(RequestProgressTrackerLogFilter.java:10

Я не думаю, что рейтинг услуг работает. Когда я смотрю http: // локальный: 4502 / система / консоль / статус-slingfilter мой фильтр отображается, как показано. Судя по другим перечисленным фильтрам, я думаю, что крайнее левое число - это рейтинг фильтра. По какой-то причине мой фильтр имеет рейтинг 0, хотя я установил его как service.ranking = 700

0 : class my.site.servlets.DecorateUserGeneratedFilter (id: 8402, property: service.ranking=700); called: 0; time: 0ms; time/call: -1µs

Обновление: мне удалось исправить ранг фильтра, сделав его 700 по-прежнему выдающим исключение IllegalStateException. Установка 3000 решила эту проблему. Но когда из моей оболочки вызывается request.getInputStream (). Он возвращает ноль.

getInputStream, вероятно, был вызван до вашего фильтра. Я бы посоветовал сохранить идентификатор пользователя в атрибуте запроса, а затем получить и вернуть его в вашей оболочке. Надеюсь, это имеет смысл. Кроме того, должен ли userId быть в теле запроса для начала?

Ahmed Musallam 26.06.2018 09:04

Я думаю, вы правы, что getInputStream уже вызывается. Обычно я соглашусь с тем, что использование атрибута было бы хорошим вариантом, но в этом случае я не контролирую сервлет. Итак, я полагаю, что если мы добавим его в тело запроса, то, возможно, сервлет обработает наши данные вместе с другими отправленными данными формы.

Cris Rockwell 26.06.2018 19:14

Я вижу, в таком случае кажется, что userId действительно не документирован? и может измениться в будущем имп. Я бы порекомендовал пойти другим путем с сервлетом, который запускает ваши рабочие процессы. (Я скоро отправлю ответ)

Ahmed Musallam 26.06.2018 19:43

Верный. Это довольно неожиданная проблема, поскольку я ожидал, что идентификатор пользователя уже будет частью полезной нагрузки или метаданных формы. В нашем случае пользователь аутентифицирован, а идентификатор пользователя известен с помощью метода, который я включил в фильтр getCurrentUserId. Кажется, должен быть способ сохранить это для использования в рабочих процессах.

Cris Rockwell 26.06.2018 19:54

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

Ahmed Musallam 26.06.2018 20:03
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
5
1 953
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

Верная идея, неправильное место.

Короткий ответ заключается в том, что когда вы реализуете SlingHttpServletRequestWrapper, он обеспечивает обработку вызовов методов по умолчанию для исходного SlingHttpServletRequest, если вы добавляете параметр на лету, что вы хотите сделать, это убедиться, что методы, которые взаимодействуют с параметрами переопределены, так что вы можете быть уверены, что ваш добавлен. Поэтому при инициализации вызовите исходную карту параметров, скопируйте эти элементы в новую карту, которая включает ваши собственные значения.

Затем отключите любые методы, которые будут запрашивать эти значения

getParameter(String)
getParameterMap()
getParameterNames()
getParameterValues(String)

Не трогайте InputStream, он уже был обработан для получения переданных параметров.

Кроме того, это один из двух способов справиться с этим типом варианта использования, другой вариант - использовать SlingPOSTProcessors, как описано в документации. https://sling.apache.org/documentation/bundles/manipulating-content-the-slingpostservlet-servlets-post.html

который позволяет вам определять, что записывается в репозиторий, и изменять данные, включая, как и в вашем случае, дополнительное поле.

То, что вы пытаетесь сделать, может быть легким путем, но может не соответствовать требованиям будущего для новых выпусков AEM.

Вам нужен полный контроль над тем, как запускается ваш рабочий процесс !:

  1. В ваших формах должно быть поле, содержащее путь к рабочему процессу (и, возможно, другую информацию, необходимую для этого рабочего процесса).
  2. Создайте собственный сервлет, в который будут публиковаться ваши формы.
  3. В этом сервлете обрабатываются все значения, отправленные пользователем (из формы). Но особенно запомните предполагаемый путь рабочего процесса и активируйте его с помощью API рабочего процесса.

Таким образом, вам не придется возиться с пусковыми установками, и рабочие процессы запускаются вашими пользователями с использованием их идентификатора пользователя.

Надеюсь это поможет.

Большое спасибо за ваш вклад! Я вообще не думаю, что другой маршрут является «легким». Мне ваше предложение легче реализовать. На этом этапе я принимаю ваше предложение, поскольку оно устраняет некоторую неопределенность уравнения. Однако я не собираюсь принимать это как ответ, потому что мой исходный пост посвящен тому, как выполнять перенос запросов в Sling. Я все еще хочу знать, как это делается.

Cris Rockwell 27.06.2018 20:44

если вы ищете пример кода:

      @SlingFilter(order = 1)
    public class MyFilter implements Filter {

@Override
public void init(FilterConfig filterConfig) throws ServletException {
    return;
}

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
        FilterChain filterChain) throws IOException, ServletException {

    ServletRequest request = servletRequest;

    if (servletRequest instanceof SlingHttpServletRequest) {
        final SlingHttpServletRequest slingRequest = (SlingHttpServletRequest) servletRequest;
            request = new SlingHttpServletRequestWrapper(slingRequest) {
              String userId = getCurrentUserId(slingRequest);
            };

    }

    filterChain.doFilter(request, servletResponse);
}

@Override
public void destroy() {
    return;
}

Другие вопросы по теме