
com.composum.sling.core.servlet.Status Maven / Gradle / Ivy
package com.composum.sling.core.servlet;
import com.composum.sling.core.logging.Message;
import com.composum.sling.core.logging.Message.Level;
import com.composum.sling.core.logging.MessageContainer;
import com.composum.sling.core.logging.MessageTypeAdapterFactory;
import com.composum.sling.core.util.I18N;
import com.composum.sling.core.util.ResponseUtil;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonIOException;
import com.google.gson.stream.JsonWriter;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
import org.apache.jackrabbit.JcrConstants;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.request.RequestParameter;
import org.apache.sling.api.resource.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.io.StringWriter;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Supplier;
import java.util.regex.Pattern;
import static javax.servlet.http.HttpServletResponse.SC_ACCEPTED;
import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import static javax.servlet.http.HttpServletResponse.SC_OK;
import static org.apache.sling.api.resource.ResourceUtil.isSyntheticResource;
/**
* The standardised answer object of a servlet request to fill the response output.
* It allows adding generic objects and lists with the {@link #data(String)} / {@link #list(String)} methods.
* Can be serialized and deserialized with {@link Gson}, so it is possible to extend this and add your own attributes, too.
* (Caution: for deserializing it with Gson you might run into trouble if you put arbitrary objects into the data /
* list - it's probably easier to extend Status and have attributes with the specific types needed.)
*/
public class Status {
private static final Logger LOG = LoggerFactory.getLogger(Status.class);
/** Constant for often used argument for {@link #data(String)}. */
public static final String DATA = "data";
protected transient final Gson gson;
protected transient final SlingHttpServletRequest request;
protected transient final SlingHttpServletResponse response;
protected int status = SC_OK;
protected boolean success = true;
protected boolean warning = false;
/** If set, added messages will be logged here. */
@Nullable
protected transient Logger messageLogger;
@Nullable
protected String title;
@Nullable
protected MessageContainer messages;
@Nullable
protected Map> data;
@Nullable
protected Map>> list;
public Status(@Nullable final SlingHttpServletRequest request, @Nullable final SlingHttpServletResponse response,
@Nullable Logger messageLogger) {
this(new GsonBuilder(), request, response, messageLogger);
}
/**
* Constructor without logging messages - consider {@link #Status(SlingHttpServletRequest, SlingHttpServletResponse, Logger)}
* to automatically log added messages.
*/
public Status(@Nullable final SlingHttpServletRequest request, @Nullable final SlingHttpServletResponse response) {
this(new GsonBuilder(), request, response, null);
}
/** Construction */
public Status(@NotNull final GsonBuilder gsonBuilder,
@Nullable final SlingHttpServletRequest request, @Nullable final SlingHttpServletResponse response,
@Nullable Logger messageLogger) {
this.gson = initGson(Objects.requireNonNull(gsonBuilder), () -> request).create();
this.request = request;
this.response = response;
this.messageLogger = messageLogger;
}
/**
* Construct with a specific Gson instance. CAUTION: you need to have called
* {@link #initGson(GsonBuilder, SlingHttpServletRequest)} for proper message translation.
* Perhaps rather use {@link #Status(GsonBuilder, SlingHttpServletRequest, SlingHttpServletResponse)}.
*
* @deprecated since it's dangerous to forget {@link #initGson(GsonBuilder, SlingHttpServletRequest)}.
*/
@Deprecated
public Status(@NotNull final Gson gson,
@Nullable final SlingHttpServletRequest request, @Nullable final SlingHttpServletResponse response) {
this.gson = Objects.requireNonNull(gson);
this.request = request;
this.response = response;
}
/** @deprecated Constructor for deserialization with gson only */
@Deprecated
public Status() {
this(new GsonBuilder().create(), null, null);
}
/**
* Registers a type adapter for the JSON serialization of {@link Message} and {@link MessageContainer} that
* performs i18n according to the given request. If there is no request, we can skip this step as these classes are
* annotated with {@link com.google.gson.annotations.JsonAdapter}.
*/
@NotNull
public static GsonBuilder initGson(@NotNull GsonBuilder gsonBuilder,
@NotNull Supplier requestProvider) {
if (requestProvider != null) {
return gsonBuilder.registerTypeAdapterFactory(new MessageTypeAdapterFactory(requestProvider));
}
return gsonBuilder;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
if (status < 200 || status >= 300) {
success = false;
}
}
/**
* Retrieves and validates a required parameter from the request, adding the errorMessage if that fails.
*
* @return the validated parameter value, or null if that fails
*/
@Nullable
public String getRequiredParameter(@NotNull final String paramName,
@Nullable final Pattern pattern, @NotNull final String errorMessage) {
final RequestParameter requestParameter = request.getRequestParameter(paramName);
String value = null;
if (requestParameter != null) {
value = requestParameter.getString();
if (pattern == null || pattern.matcher(value).matches()) {
return value;
}
}
validationError(null, null, errorMessage, value);
return null;
}
public List getRequiredParameters(@NotNull final String paramName,
@Nullable final Pattern pattern, @NotNull final String errorMessage) {
final RequestParameter[] requestParameters = request.getRequestParameters(paramName);
if (requestParameters == null || requestParameters.length < 1) {
validationError(null, null, errorMessage);
return null;
}
List values = new ArrayList<>();
for (RequestParameter parameter : requestParameters) {
String value = parameter.getString();
if (pattern != null && !pattern.matcher(value).matches()) {
validationError(null, null, errorMessage, value);
}
values.add(value);
}
return values;
}
public String getTitle() {
return title;
}
public boolean hasTitle() {
return StringUtils.isNotBlank(title);
}
public void setTitle(String rawTitle) {
if (StringUtils.isNotBlank(rawTitle)) {
this.title = request != null ? I18N.get(request, rawTitle) : rawTitle;
if (StringUtils.isBlank(this.title)) {
this.title = rawTitle;
}
} else {
this.title = null;
}
}
@NotNull
public MessageContainer getMessages() {
if (messages == null) { messages = new MessageContainer(messageLogger); }
return messages;
}
public boolean isValid() {
return isSuccess();
}
public boolean isSuccess() {
return success;
}
public boolean isWarning() {
return warning;
}
public void setWarning(boolean value) {
this.warning = value;
}
public boolean isError() {
return !isSuccess();
}
/** For the meaning of arguments - compare {@link #shortMessage(Level, String, Object...)} . */
public void info(@NotNull final String text, Object... args) {
shortMessage(Level.info, text, args);
}
/** For the meaning of arguments - compare {@link #addValidationMessage(Level, String, String, String, Object...)} . */
public void validationInfo(@NotNull final String context, @NotNull final String label,
@NotNull final String text, Object... args) {
addValidationMessage(Level.info, context, label, text, args);
}
/** For the meaning of arguments - compare {@link #shortMessage(Level, String, Object...)} . */
public void warn(@NotNull final String text, Object... args) {
shortMessage(Level.warn, text, args);
}
/** For the meaning of arguments - compare {@link #addValidationMessage(Level, String, String, String, Object...)} . */
public void validationWarn(@NotNull final String context, @NotNull final String label,
@NotNull final String text, Object... args) {
addValidationMessage(Level.warn, context, label, text, args);
}
/** For the meaning of arguments - compare {@link #shortMessage(Level, String, Object...)} . */
public void error(@NotNull final String text, Object... args) {
shortMessage(Level.error, text, args);
}
/**
* For the meaning of arguments - compare {@link #shortMessage(Level, String, Object...)} .
* This adds the {@link Exception#getLocalizedMessage()} as message argument and logs the exception if
* a message logger is set.
*/
public void error(@NotNull String text, @NotNull Throwable exception) {
getMessages().add(Message.error(text, exception.getLocalizedMessage()), exception);
adjustToMessageLevel(Level.error);
}
/** For the meaning of arguments - compare {@link #addValidationMessage(Level, String, String, String, Object...)} . */
public void validationError(@NotNull final String context, @NotNull final String label,
@NotNull final String text, Object... args) {
addValidationMessage(Level.error, context, label, text, args);
}
/**
* Returns a map that is saved with the given name in the data section of the Status, creating it if necessary.
*
* @param name the key of the data element to insert in the status response
* @return a Map for the data values of the added status object, created if neccesary.
*/
@NotNull
public Map data(@NotNull final String name) {
if (data == null) { data = new LinkedHashMap<>();}
Map object = data.computeIfAbsent(name, k -> new LinkedHashMap<>());
return object;
}
/**
* Adds information about resource as {@link #data(String)} object.
*
* @param name the key of the data element to insert in the status response
* @see #reference(Map, Resource)
*/
public void reference(@NotNull final String name, Resource resource) {
Map object = data(name);
reference(object, resource);
}
/**
* Adds general information about the resource into the object: name, path, type (Sling resourcetype),
* prim (primary type), synthetic (whether it is a synthetic resource).
*/
public void reference(Map object, Resource resource) {
object.put("name", resource.getName());
object.put("path", resource.getPath());
object.put("type", resource.getResourceType());
object.put("prim", resource.getValueMap().get(JcrConstants.JCR_PRIMARYTYPE, ""));
object.put("synthetic", isSyntheticResource(resource));
}
/**
* Returns the list for the given name, creating it if neccesary.
*
* @param name the key of the data element to insert in the status response
* @return a new list of Map items for the data values of the added status object
*/
@NotNull
public List
© 2015 - 2025 Weber Informatics LLC | Privacy Policy