Skip to content

Listen events

Fabrice Daugan edited this page Nov 1, 2025 · 1 revision

Introduction

Sometime you want to create a plugin aware of events in the applications. Some events:

  • Application event
  • API calls
  • Error management

Application events

Application events are Spring related events such as: start and stop of the context.

To listner these event, implements the SpringApplicationRunListener class.

As exemple of this listenener, the plugin listener

API events

This section cover the API event listener dirrectly with CXF.

Create a class like this, and each API call (succeed of failed) will trigger the filter(...) method.

import jakarta.servlet.ServletConfig;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.container.ContainerResponseContext;
import jakarta.ws.rs.container.ContainerResponseFilter;
import jakarta.ws.rs.core.SecurityContext;
import jakarta.ws.rs.core.UriInfo;
import jakarta.ws.rs.ext.Provider;
import lombok.extern.slf4j.Slf4j;
import org.apache.cxf.jaxrs.impl.AbstractPropertiesImpl;
import org.ligoj.bootstrap.core.resource.AbstractMapper;
import org.ligoj.bootstrap.resource.system.configuration.ConfigurationResource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;

import java.io.InputStream;
import java.util.Arrays;
import java.util.List;

/**
 * A response filter able to transform Null results to 404 HTTP response when
 */
@Provider
@Slf4j
@Component
public class OnEventFilter extends AbstractMapper implements ContainerResponseFilter {

	/**
	 * Ignored class from payload.
	 */
	private static final Class<?>[] IGNORED_CLASSES = {UriInfo.class, SecurityContext.class, ServletConfig.class,
			ServletRequest.class, ServletResponse.class, InputStream.class, ApplicationContext.class};

	@Autowired
	private ConfigurationResource configurationResource;

	/**
	 * Convert complex or Servlet like technical object to their class name only.
	 */
	private Object convertForPayload(Object parameter) {
		if (Arrays.stream(IGNORED_CLASSES).anyMatch(c -> parameter != null && c.isAssignableFrom(parameter.getClass()))) {
			// This parameter is dropped
			return "<" + parameter.getClass().getSimpleName() + ">";
		}
		return parameter;
	}

	@Override
	public void filter(final ContainerRequestContext requestContext, final ContainerResponseContext responseContext) {
		try {
			var responseEntity = responseContext.getEntity();
			var principal = requestContext.getSecurityContext().getUserPrincipal().getName();
			var response = responseEntity == null ? null : responseEntity.toString();
			var exchange = ((AbstractPropertiesImpl) requestContext).getMessage().getExchange();
			@SuppressWarnings("unchecked") final var params = exchange.getInMessage().getContent(List.class).stream()
					.map(this::convertForPayload).toList();
			log.info("API event {} /{}, params={}, response={}, principal={}, status={}",
					requestContext.getMethod(), requestContext.getUriInfo().getPath(), params, response, principal,
					responseContext.getStatus());
		} catch (final Exception e) {
			// Log only errors without interrupting the main flow
			log.warn("API event handling failed", e);
		}
	}
}

If you want to trigger a long running task, you should use a deffered execution

void execute(final Runnable runnable) {
	CompletableFuture.delayedExecutor(1, TimeUnit.SECONDS).execute(runnable);
}

As exemple of this usage, the hook management

Clone this wiki locally