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

com.sap.cds.services.ServiceException Maven / Gradle / Ivy

/**************************************************************************
 * (C) 2019-2024 SAP SE or an SAP affiliate company. All rights reserved. *
 **************************************************************************/
package com.sap.cds.services;

import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.function.Function;

import org.slf4j.helpers.MessageFormatter;

import com.sap.cds.ql.StructuredType;
import com.sap.cds.ql.cqn.Path;
import com.sap.cds.reflect.CdsElement;
import com.sap.cds.services.handler.Handler;
import com.sap.cds.services.messages.MessageTarget;

/**
 * {@link ServiceException} is the central unchecked exception thrown by the framework and {@link Handler} when an error occurs during event processing via {@link Service#emit(EventContext)}
 * It extends {@link RuntimeException} with an {@link ErrorStatus}, which indicates an internal error code and a mapping to an HTTP status code.
 */
public class ServiceException extends RuntimeException {

	private static final long serialVersionUID = 1L;

	protected final ErrorStatus errorStatus;
	protected MessageTarget messageTarget;

	protected final Object[] args;

	protected static ServiceExceptionUtils Utils = CoreFactory.INSTANCE.createServiceExceptionUtils();

	/**
	 * Creates a new {@link ServiceException}
	 * 
* The {@link ErrorStatus} of the first {@link ServiceException} found within the cause chain is used. * If no {@link ErrorStatus} is found, it defaults to {@link ErrorStatuses#SERVER_ERROR}. * * @param e The causing {@link Throwable} */ public ServiceException(Throwable e) { this(null, e.getMessage(), new Object[] { e }); } /** * Creates a new {@link ServiceException}. The last argument might be the causing {@link Throwable} and not a formatting argument. *
* The {@link ErrorStatus} of the first {@link ServiceException} found within the cause chain is used. * If no {@link ErrorStatus} is found, it defaults to {@link ErrorStatuses#SERVER_ERROR}. * * @param message The formatting message, based on SLF4J's {@link MessageFormatter} * @param args The arguments to the formatting message. The last argument might the causing {@link Throwable}. */ public ServiceException(String message, Object... args) { this(null, message, args); } /** * Creates a new {@link ServiceException} * @param errorStatus The {@link ErrorStatus}, if null the {@link ErrorStatus} of the first {@link ServiceException} found within the cause chain is used. * If no {@link ErrorStatus} is found, it defaults to {@link ErrorStatuses#SERVER_ERROR} * @param message The formatting message, based on SLF4J's {@link MessageFormatter} * @param args The arguments to the formatting message. The last argument might the causing {@link Throwable}. */ public ServiceException(ErrorStatus errorStatus, String message, Object... args) { super(message); Throwable throwableCandidate = MessageFormatter.getThrowableCandidate(args); if (throwableCandidate != null) { super.initCause(throwableCandidate); this.args = MessageFormatter.trimmedCopy(args); } else { this.args = args; // NOSONAR } this.errorStatus = errorStatus; } /** * @return the {@link ErrorStatus} */ public ErrorStatus getErrorStatus() { ErrorStatus theErrorStatus = getNearest(e -> e.errorStatus); return theErrorStatus == null ? ErrorStatuses.SERVER_ERROR : theErrorStatus; } /** * @return the {@link MessageTarget} */ public MessageTarget getMessageTarget() { return getNearest(e -> e.messageTarget); } @Override public String getMessage() { return Utils.getMessage(getPlainMessage(), args); } @Override public String getLocalizedMessage() { return getLocalizedMessage(null); } public String getLocalizedMessage(Locale locale) { return Utils.getLocalizedMessage(getPlainMessage(), args, locale); } /** * @return The original message of this exception */ public String getPlainMessage() { return super.getMessage(); } /** * Returns the stack of {@link EventContext} which corresponds to the events processed along the exception stack trace. * The lists starts with the {@link EventContext} from which the exception was triggered. * If there are no contexts, the method returns an empty list * * @return the stack of {@link EventContext} which corresponds to the events processed along the exception stack trace. */ public List getEventContexts() { List contexts = getNearest(ServiceException::collectEventContexts); return contexts == null ? Collections.emptyList() : Collections.unmodifiableList(contexts); } protected List collectEventContexts() { return null; // getNearest works with nulls } /** * Iterates over all {@link ServiceException} in the chain and returns the nearest available value of an attribute * @param the attribute type * @param provider the provider for the attribute * @return the attribute value */ private T getNearest(Function provider) { T value = provider.apply(this); Throwable cause = getCause(); while (value == null && cause != null) { if (cause instanceof ServiceException exception) { value = provider.apply(exception); } cause = cause.getCause(); } return value; } // MessageTarget Builder API /** * Sets the provided string-based target. No further processing of the string is performed. * * @param target the string-based target * @return The current {@link ServiceException} */ public ServiceException messageTarget(String target) { return messageTarget(Utils.getMessageTarget(target)); } /** * Sets the provided message target. * * @param target the message target * @return The current {@link ServiceException} */ public ServiceException messageTarget(MessageTarget target) { this.messageTarget = target; return this; } /** * Adds the passed path as target to the current {@link ServiceException}. * * The path is interpreted relative to the CQN statement, which was determined from the request. * For CRUD events this CQN statement points to the targeted entity. * For bound actions or functions this CQN statement points to the bound entity. * * Is equivalent to calling {@code messageTarget(MessageTarget.PARAMETER_CQN, path)} * * @param path the path to the target element or association * @return The current {@link ServiceException} */ public ServiceException messageTarget(Function, Object> path) { return messageTarget(MessageTarget.PARAMETER_CQN, path); } /** * Adds the passed target parameter and path as target to the current * {@link ServiceException}. * * @param parameter target parameter serving as the entry point for the path resolution. * Passing {@link MessageTarget#PARAMETER_CQN} indicates that the path * should be interpreted relatively to the target entity of the request. * Alternatively you can pass names of action or function parameters. * * @param path the path to the target element or association * @return The current {@link ServiceException} */ public ServiceException messageTarget(String parameter, Function, Object> path) { this.messageTarget = Utils.getMessageTarget(parameter, path); return this; } /** * Adds the passed path as target to the current {@link ServiceException}. * This method allows to build the path in a type-safe way, by passing the corresponding entity or structured type interface. * * The path is interpreted relative to the CQN statement, which was determined from the request. * For CRUD events this CQN statement points to the targeted entity. * For bound actions or functions this CQN statement points to the bound entity. * * Is equivalent to calling {@code messageTarget(MessageTarget.PARAMETER_CQN, type, path)} * * @param type the root type of the path. Either an entity or a structured type. * @param path the path to the target element or association * @param the type of the root * @return The current {@link ServiceException} */ public > ServiceException messageTarget(Class type, Function path) { return messageTarget(MessageTarget.PARAMETER_CQN, type, path); } /** * Adds the passed target parameter and path as target to the current * {@link ServiceException}. This method allows to build the path in a type-safe way, * by passing the corresponding entity or structured type interface. * * @param parameter target parameter serving as the entry point for the path resolution. * Passing {@link MessageTarget#PARAMETER_CQN} indicates that the path * should be interpreted relatively to the target entity of the request. * Alternatively you can pass names of action or function parameters. * * @param type the root type of the path. Either an entity or a structured type. * @param path the path to the target element or association * @param the type of the root * @return The current {@link ServiceException} */ public > ServiceException messageTarget(String parameter, Class type, Function path) { this.messageTarget = Utils.getMessageTarget(parameter, type, path); return this; } /** * Sets the message target based on the provided path and cds element. * * @param path the target path * @param element the cds element * @return The current {@link ServiceException} */ public ServiceException messageTarget(Path path, CdsElement element) { this.messageTarget = Utils.getMessageTarget(path, element); return this; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy