All Downloads are FREE. Search and download functionalities are using the official Maven repository.

fi.evolver.basics.spring.http.HttpInterceptor Maven / Gradle / Ivy

There is a newer version: 6.5.1
Show newest version
package fi.evolver.basics.spring.http;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import fi.evolver.basics.spring.auth.AuthorizationService;
import fi.evolver.basics.spring.job.ResultState;
import fi.evolver.basics.spring.job.entity.TaskStatus.TaskState;
import fi.evolver.basics.spring.log.LogPolicy;
import fi.evolver.basics.spring.log.LogPolicy.Policy;
import fi.evolver.basics.spring.log.entity.MessageLogMetadata;
import fi.evolver.utils.ContextUtils;
import fi.evolver.utils.attribute.ContextAttribute;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

@Component
public class HttpInterceptor implements HandlerInterceptor {
	private static final Logger LOG = LoggerFactory.getLogger(HttpInterceptor.class);

	public static final ContextAttribute CONTEXT_SOURCE_SYSTEM = new ContextAttribute<>(HttpLoggerFilter.class.getName() + ".SourceSystem", String.class);
	public static final ContextAttribute CONTEXT_BEAN_TYPE_NAME = new ContextAttribute<>(HttpLoggerFilter.class.getName() + ".BeanTypeName", String.class);
	public static final ContextAttribute CONTEXT_LOG_META_DATA = new ContextAttribute<>(HttpLoggerFilter.class.getName() + ".LogMetadata", MetadataHolder.class);
	public static final ContextAttribute CONTEXT_LOG_POLICY = new ContextAttribute<>(HttpLoggerFilter.class.getName() + ".LogPolicy", Policy.class);
	public static final ContextAttribute CONTEXT_MESSAGE_TYPE = new ContextAttribute<>(HttpLoggerFilter.class.getName() + ".MessageType", String.class);
	public static final ContextAttribute CONTEXT_RESULT_STATE = new ContextAttribute<>(HttpLoggerFilter.class.getName() + ".ResultState", ResultState.class);
	public static final ContextAttribute CONTEXT_AUTH_USER = new ContextAttribute<>(HttpLoggerFilter.class.getName() + ".AuthUser", String.class);

	public static final String ATTRIBUTE_AUTH_USER = CONTEXT_AUTH_USER.name();
	public static final String ATTRIBUTE_SOURCE_SYSTEM = CONTEXT_SOURCE_SYSTEM.name();

	private static final ResultState UNAUTHORIZED_ACCESS = ResultState.failed("Unauthorized access");


	@Autowired
	private AuthorizationService authorizationService;


	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
		if (!ContextUtils.withinContext())
			return false;

		String user = (String)request.getAttribute(ATTRIBUTE_AUTH_USER);
		if (user != null)
			CONTEXT_AUTH_USER.set(user);

		if (handler instanceof HandlerMethod method) {
			CONTEXT_BEAN_TYPE_NAME.set(method.getBeanType().getName());
			authorizationService.authenticate(request.getHeader("Authorization"));
			String permission = String.format("%s::%s", method.getMethod().getDeclaringClass().getSimpleName(), method.getMethod().getName());

			if (!authorizationService.hasPermission(permission)) {
				response.sendError(403);
				CONTEXT_RESULT_STATE.set(UNAUTHORIZED_ACCESS);
				return false;
			}
		}

		return true;
	}


	@Override
	public void afterCompletion(
			HttpServletRequest request,
			HttpServletResponse response,
			Object handler,
			Exception ex) {

		if (ContextUtils.withinContext()) {
			CONTEXT_AUTH_USER.get().ifPresent(u -> addMetadata("AuthUser", u));
			if (handler instanceof HandlerMethod method) {
				CONTEXT_LOG_POLICY.computeIfAbsent(() -> getLogPolicy(method));
				CONTEXT_MESSAGE_TYPE.computeIfAbsent(() -> getMessageType(method));
				CONTEXT_RESULT_STATE.set(inferEndState(response, ex));
			}
		}
	}


	private static ResultState inferEndState(HttpServletResponse response, Exception ex) {
		Optional resultState = CONTEXT_RESULT_STATE.get();

		if (ex != null && resultState.map(ResultState::getState).map(TaskState::isSuccess).orElse(true))
			return ResultState.failed("%s", ex);

		if (resultState.isPresent())
			return resultState.get();

		HttpStatus status = HttpStatus.resolve(response.getStatus());
		String reason = status != null ? status.getReasonPhrase() : "?";

		if (response.getStatus() >= 400)
			return ResultState.failed("%s", reason);

		return ResultState.ok("%s", reason);
	}


	private static Policy getLogPolicy(HandlerMethod method) {
		return Optional.ofNullable(method.getMethodAnnotation(LogPolicy.class))
				.map(LogPolicy::value)
				.orElse(Policy.FULL);
	}

	private static String getMessageType(HandlerMethod method) {
		return Optional.ofNullable(method.getMethodAnnotation(MessageType.class))
				.map(MessageType::value)
				.orElse("?");
	}


	/**
	 * Sets the message type of the incoming HTTP request for logging purposes.
	 *
	 * @see MessageType for a simple declarative approach.
	 *
	 * @param messageType The type of the incoming message.
	 */
	public static void setMessageType(String messageType) {
		CONTEXT_MESSAGE_TYPE.set(messageType);
	}


	/**
	 * Sets the log policy of the incoming HTTP request.
	 *
	 * @see LogPolicy for a simple declarative approach.
	 *
	 * @param policy At what level should the message be logged if at all.
	 */
	public static void setLogPolicy(Policy policy) {
		CONTEXT_LOG_POLICY.set(policy);
	}


	/**
	 * Sets the result state of the incoming HTTP request for logging purposes.
	 *
	 * @param resultState The result state.
	 */
	public static void setResultState(ResultState resultState) {
		CONTEXT_RESULT_STATE.set(resultState);
	}


	/**
	 * Add message log metadata for the incoming HTTP request.
	 * If either the key or the value is null, no metadata is added.
	 *
	 * @param key Key for the metadata.
	 * @param value Value for the metadata.
	 */
	public static void addMetadata(String key, Object value) {
		if (key == null || value == null)
			return;

		try {
			CONTEXT_LOG_META_DATA.computeIfAbsent(MetadataHolder::new).metadata().put(key, value.toString());
		}
		catch (RuntimeException e) {
			LOG.warn("Failed adding metadata {}='{}'", key, value, e);
		}
	}


	record MetadataHolder(Map metadata) {
		MetadataHolder() {
			this(new LinkedHashMap<>());
		}

		List toMessageLogMetadata() {
			List results = new ArrayList<>(metadata.size());
			metadata.forEach((k, v) -> results.add(new MessageLogMetadata(k, v)));
			return results;
		}
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy