
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