Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.wl4g.component.common.web.rest.RespBase Maven / Gradle / Ivy
/*
* Copyright 2017 ~ 2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.wl4g.component.common.web.rest;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.google.common.annotations.Beta;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import com.wl4g.component.common.annotation.Stable;
import com.wl4g.component.common.collection.CollectionUtils2;
import com.wl4g.component.common.remoting.standard.HttpStatus;
import static com.wl4g.component.common.lang.Assert2.hasText;
import static com.wl4g.component.common.lang.Assert2.state;
import static com.wl4g.component.common.remoting.standard.HttpStatus.*;
import static com.wl4g.component.common.lang.Exceptions.getRootCausesString;
import static com.wl4g.component.common.serialize.JacksonUtils.convertBean;
import static com.wl4g.component.common.serialize.JacksonUtils.toJSONString;
import static com.wl4g.component.common.web.rest.RespBase.RetCode.newCode;
import static java.lang.String.format;
import static java.lang.System.getProperty;
import static java.lang.reflect.Modifier.isFinal;
import static java.lang.reflect.Modifier.isStatic;
import static java.util.Collections.emptyMap;
import static java.util.Collections.unmodifiableMap;
import static java.util.Locale.US;
import static java.util.Objects.isNull;
import static java.util.Objects.nonNull;
import static org.apache.commons.lang3.StringUtils.*;
/**
* Generic restful response base model wrapper.
*
* @author Wangl.sir <[email protected] >
* @version v1.0
* @date 2018年3月9日
* @since
*/
@Stable
public class RespBase implements Serializable {
private static final long serialVersionUID = 2647155468624590650L;
private RetCode code = RetCode.OK;
private String status = DEFAULT_STATUS; // [Extensible]
private String requestId = DEFAULT_REQUESTID; // [Extensible]
private String message = EMPTY;
@SuppressWarnings("unchecked")
private D data = (D) DEFAULT_DATA;
public RespBase() {
this(null);
}
public RespBase(RetCode retCode) {
this(retCode, null);
}
public RespBase(D data, String status) {
this(null, data);
}
public RespBase(RetCode retCode, D data) {
this(retCode, null, data);
}
public RespBase(RetCode retCode, String message, D data) {
this(retCode, null, message, data);
}
public RespBase(RetCode retCode, String status, String message, D data) {
setCode(retCode);
setStatus(status);
setMessage(message);
setData(data);
}
/**
* Gets response code value.
*
* @return
*/
public int getCode() {
return code.getErrcode();
}
/**
* Sets response code of {@link RetCode}.
*
* @param retCode
*/
public void setCode(RetCode retCode) {
if (nonNull(retCode)) {
this.code = (RetCode) retCode;
}
}
/**
* Sets response code of {@link RetCode}.
*
* @param retCode
* @return
*/
@JsonIgnore
public RespBase withCode(RetCode retCode) {
setCode(retCode);
return this;
}
/**
* Sets response code of int.
*
* @param retCode
*/
public void setCode(int retCode) {
this.code = newCode(retCode);
}
/**
* Sets response code of int.
*
* @param retCode
* @return
*/
@JsonIgnore
public RespBase withCode(int retCode) {
setCode(retCode);
return this;
}
/**
* Gets status
*
* @return
*/
public String getStatus() {
return status;
}
/**
* Sets status.
*
* @param status
*/
public void setStatus(String status) {
if (!isBlank(status)) {
this.status = status;
}
}
/**
* Sets status.
*
* @param status
* @return
*/
@JsonIgnore
public RespBase withStatus(String status) {
setStatus(status);
return this;
}
/**
* Gets current requestId.
*
* @return
*/
public String getRequestId() {
return requestId;
}
/**
* Sets current requestId.
*
* @param requestId
*/
public void setRequestId(String requestId) {
this.requestId = requestId;
}
/**
* Sets current requestId.
*
* @param requestId
* @return
*/
@JsonIgnore
public RespBase withRequestId(String requestId) {
setRequestId(requestId);
return this;
}
/**
* Gets error message text.
*
* @return
*/
public String getMessage() {
return isBlank(message) ? code.getErrmsg() : message;
}
/**
* Sets error message text.
*/
public void setMessage(String message) {
this.message = ErrorPromptMessageBuilder.build(code, !isBlank(message) ? message : this.message);
}
/**
* Sets error message text.
*
* @return
*/
@JsonIgnore
public RespBase withMessage(String message) {
setMessage(message);
return this;
}
/**
* Gets response data node of {@link Object}.
*
* @return
*/
public D getData() {
return data;
}
// --- Expanded's. ---.
/**
* Sets response bean to data.
*
* @param data
*/
public void setData(D data) {
if (isNull(data))
return;
if (checkDataAvailable()) // Data already payLoad ?
throw new IllegalStateException(format(
"Already data payload, In order to set it successful the data node must be the initial value or empty. - %s",
getData()));
this.data = data;
}
/**
* Sets response bean to data.
*
* @param data
* @return
*/
@JsonIgnore
public RespBase withData(D data) {
setData(data);
return this;
}
/**
* Sets error throwable, does not set response status code.
*
* @param th
*/
@JsonIgnore
public void setThrowable(Throwable th) {
setMessage(getRootCausesString(th));
}
/**
* Sets error throwable, does not set response status code.
*
* @param th
* @return
*/
@JsonIgnore
public RespBase withThrowable(Throwable th) {
setThrowable(th);
return this;
}
/**
* Handle exceptions, at the same time, the restful API compatible error
* status code is automatically set. If there is no match, the default value
* of {@link RetCode.SYS_ERR} is used
*
* @param th
*/
@JsonIgnore
public void handleError(Throwable th) {
withCode(getRestfulCode(th, RetCode.SYS_ERR)).withMessage(getRootCausesString(th));
}
/**
* As {@link RespBase#data} convert to {@link DataMap}.
*
* @see {@link RespBase#getData()}
* @return
*/
@SuppressWarnings({ "unchecked" })
@JsonIgnore
public synchronized DataMap asMap() {
if (isNull(getData()))
return null;
if (data instanceof Map) // typeof Map ?
return (DataMap) getData();
setData((D) convertBean(data, DataMap.class));
return (DataMap) getData();
}
/**
* Build {@link DataMap} instance for response data body.(if
* {@link RespBase#getData()} is null)
*
* @see {@link RespBase#getData()}
* @return
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
@JsonIgnore
public synchronized DataMap forMap() {
if (!checkDataAvailable()) { // Data unalready ?
data = (D) new DataMap<>(); // Init
} else {
// Convert to DataMap.
/**
* ###[Note(scene): This logic is to solve the data analysis of, for
* example:{@link org.springframework.web.client.RestTemplate}.response]
*/
if (data instanceof Map) { // e.g: LinkedHashMap
if (!(data instanceof DataMap)) {
this.data = (D) new DataMap<>((Map) data);
}
} else {
throw new UnsupportedOperationException(format(
"Illegal type compatible operation, because RespBase.data has initialized the available data, class type is: %s, and forMap() requires RespBase.data to be uninitialized or the initialized data type is must an instance of Map",
data.getClass()));
}
}
return (DataMap) data;
}
/**
* Build child node data map.
*
* @param nodeKey
* @return
*/
@SuppressWarnings("unchecked")
@JsonIgnore
public synchronized DataMap forMapNode(String nodeKey) {
hasText(nodeKey, "RespBase build datamap nodeKey name can't be empty");
DataMap data = forMap();
DataMap nodeMap = (DataMap) data.get(nodeKey);
if (isNull(nodeMap)) {
data.put(nodeKey, (D) (nodeMap = new DataMap<>()));
}
return nodeMap;
}
/**
* Check whether the {@link RespBase#data} is available, for example, it
* will become available payload after {@link RespBase#setData(Object)} or
* {@link RespBase#forMap()} has been invoked.
*
* @return
*/
private boolean checkDataAvailable() {
return nonNull(getData()) && getData() != DEFAULT_DATA;
}
/**
* As convert {@link RespBase} to JSON string.
*
* @return
*/
public String asJson() {
return toJSONString(this);
}
@Override
public String toString() {
return "RespBase [code=" + getCode() + ", status=" + getStatus() + ", message=" + getMessage() + ", data=" + getData()
+ "]";
}
// --- Function's. ---
/**
* Gets restful exceptions and corresponding response status code.
*
* @param th
* @return
*/
public static final RetCode getRestfulCode(Throwable th) {
return getRestfulCode(th, null);
}
/**
* Gets restful exceptions and corresponding response status code.
*
* @param th
* @param defaultCode
* default status code
* @return
* @see {@link RESTfulException}
* @see {@link FunctionalRuleRestrictException}
* @see {@link InvalidParametersException}
* @see {@link ServiceUnavailableException}
*/
public static final RetCode getRestfulCode(Throwable th, RetCode defaultCode) {
if (nonNull(th) && (th instanceof RESTfulException)) {
return ((RESTfulException) th).getCode();
}
return defaultCode;
}
/**
* New create {@link RespBase} instance.
*
* @return
*/
public static final RespBase create() {
return create(null);
}
/**
* New create {@link RespBase} instance.
*
* @param status
* @return
*/
public static final RespBase create(String status) {
return new RespBase().withStatus(status);
}
/**
* Checking the response status code for success.
*
* @param resp
* @return
*/
public static final boolean isSuccess(RespBase> resp) {
return resp != null && RetCode.OK.getErrcode() == resp.getCode();
}
/**
* Check whether the {@link RespBase} status code is the expected value
*
* @param resp
* @param retCode
* @return
*/
public static final boolean eq(RespBase> resp, RetCode retCode) {
return !isNull(resp) && retCode.getErrcode() == resp.getCode();
}
/**
* Response data model
*
* @author Wangl.sir
* @version v1.0 2019年8月22日
* @since
* @param
*/
@Beta
public static class DataMap extends LinkedHashMap {
private static final long serialVersionUID = 741193108777950437L;
/**
* Constructs an empty insertion-ordered LinkedHashMap instance
* with the specified initial capacity and a default load factor (0.75).
*
* @throws IllegalArgumentException
* if the initial capacity is negative
*/
public DataMap() {
}
/**
* Constructs an empty insertion-ordered LinkedHashMap instance
* with the specified initial capacity and a default load factor (0.75).
*
* @param initialCapacity
* the initial capacity
* @throws IllegalArgumentException
* if the initial capacity is negative
*/
public DataMap(int initialCapacity) {
super(initialCapacity);
}
/**
* Constructs an empty insertion-ordered LinkedHashMap instance
* with the specified initial capacity and load factor.
*
* @param initialCapacity
* the initial capacity
* @param loadFactor
* the load factor
* @throws IllegalArgumentException
* if the initial capacity is negative or the load factor is
* nonpositive
*/
public DataMap(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor);
}
/**
* Constructs an insertion-ordered LinkedHashMap instance with
* the same mappings as the specified map. The LinkedHashMap
* instance is created with a default load factor (0.75) and an initial
* capacity sufficient to hold the mappings in the specified map.
*
* @param m
* the map whose mappings are to be placed in this map
* @throws NullPointerException
* if the specified map is null
*/
public DataMap(Map m) {
super(m);
}
/**
* Constructs an empty LinkedHashMap instance with the
* specified initial capacity, load factor and ordering mode.
*
* @param initialCapacity
* the initial capacity
* @param loadFactor
* the load factor
* @param accessOrder
* the ordering mode - true for access-order,
* false for insertion-order
* @throws IllegalArgumentException
* if the initial capacity is negative or the load factor is
* nonpositive
*/
public DataMap(int initialCapacity, float loadFactor, boolean accessOrder) {
super(initialCapacity, loadFactor, accessOrder);
}
@Override
public V put(String key, V value) {
if (isNotBlank(key) && value != null) {
return super.put(key, value);
}
return null;
}
@Override
public V putIfAbsent(String key, V value) {
if (isNotBlank(key) && value != null) {
return super.putIfAbsent(key, value);
}
return null;
}
@Override
public void putAll(Map extends String, ? extends V> m) {
if (!CollectionUtils2.isEmpty(m)) {
super.putAll(m);
}
}
public DataMap andPut(String key, V value) {
put(key, value);
return this;
}
public DataMap andPutIfAbsent(String key, V value) {
putIfAbsent(key, value);
return this;
}
public DataMap andPutAll(Map extends String, ? extends V> m) {
putAll(m);
return this;
}
}
/**
* HTTP response code definitions.
*
* @author wangl.sir
* @version v1.0 2019年1月10日
* @since
* @see RFC1216
* @see RFC2314
*/
public abstract static class RetCode {
/**
* Successful code
* {@link HttpStatus.OK}
*/
public static final RetCode OK = new RetCode(HttpStatus.OK.value(), "Ok") {
};
/**
* Parameter error
* {@link HttpStatus.BAD_REQUEST}
*/
public static final RetCode BAD_PARAMS = new RetCode(BAD_REQUEST.value(), "Bad parameters") {
};
/**
* Unauthenticated
* {@link HttpStatus.UNAUTHORIZED}
*/
public static final RetCode UNAUTHC = new RetCode(UNAUTHORIZED.value(), "Unauthenticated") {
};
/**
* Unauthorized
* {@link HttpStatus.FORBIDDEN}
*/
public static final RetCode UNAUTHZ = new RetCode(FORBIDDEN.value(), "Unauthorized") {
};
/**
* Not found
* {@link HttpStatus.NOT_FOUND}
*/
public static final RetCode NOT_FOUND_ERR = new RetCode(NOT_FOUND.value(), "Not found") {
};
/**
* Business constraints
* {@link HttpStatus.NOT_IMPLEMENTED}
*/
public static final RetCode BIZ_ERR = new RetCode(EXPECTATION_FAILED.value(), "Business restricted") {
};
/**
* Business locked constraints
* {@link HttpStatus.LOCKED}
*
* @see 423-Locked
*/
public static final RetCode LOCKD_ERR = new RetCode(LOCKED.value(), "Resources locked") {
};
/**
* Precondition limited
* {@link HttpStatus.PRECONDITION_FAILED}
*/
public static final RetCode PRECONDITITE_LIMITED = new RetCode(PRECONDITION_FAILED.value(), "Precondition limited") {
};
/**
* Unsuppported
* {@link HttpStatus.NOT_IMPLEMENTED}
*/
public static final RetCode UNSUPPORTED = new RetCode(NOT_IMPLEMENTED.value(), "Unsuppported") {
};
/**
* System abnormality
* {@link HttpStatus.SERVICE_UNAVAILABLE}
*/
public static final RetCode SYS_ERR = new RetCode(SERVICE_UNAVAILABLE.value(),
"Service unavailable, please try again later") {
};
/**
* Unavailable For Legal Reasons
* {@link HttpStatus.UNAVAILABLE_FOR_LEGAL_REASONS}
*/
public static final RetCode LEGAL_ERR = new RetCode(UNAVAILABLE_FOR_LEGAL_REASONS.value(),
"Not available for legal reasons") {
};
/**
* Name to {@link RetCode} value definitions.
*/
private static final Map internalNameValues;
/**
* Code to {@link RetCode} value definitions.
*/
private static final Map internalCodeValues;
/**
* Errors code.
*/
private final int errcode;
/**
* Errors message.
*/
private final String errmsg;
private RetCode(int errcode, String errmsg) {
// hasTextOf(errmsg, "errmsg");
this.errcode = errcode;
this.errmsg = errmsg;
}
/**
* Get error code.
* If custom status code ({@link #_$$}) is used, it takes precedence.
*
* @return
*/
public final int getErrcode() {
return errcode;
}
/**
* Get error message.
* If custom status error message ({@link #_$$}) is used, it takes
* precedence.
*
* @return
*/
public final String getErrmsg() {
return errmsg;
}
/**
* Case insensitive handling of JACKSON mapping enumeration.
*
* See
*
* @param value
* @return
*/
public static final RetCode of(String nameOrCode) {
RetCode retCode = safeOf(nameOrCode);
if (nonNull(retCode)) {
return retCode;
}
throw new IllegalArgumentException(format("'%s'", nameOrCode));
}
/**
* Safe convert retCode.
*
* @param nameOrCode
* @return
*/
public static final RetCode safeOf(Object nameOrCode) {
if (isNull(nameOrCode)) {
return null;
}
RetCode retCode = internalNameValues.get(nameOrCode);
if (isNull(retCode)) {
return internalCodeValues.get(nameOrCode);
}
return retCode;
}
/**
* New create custom status code
*
* @param errcode
* @return
*/
public static final RetCode newCode(int errcode) {
return new RetCode(errcode, null) {
};
}
/**
* New create custom status code
*
* @param errcode
* @param errmsg
* @return
*/
public static final RetCode newCode(int errcode, String errmsg) {
return new RetCode(errcode, errmsg) {
};
}
/**
* Register defaults instance definitions.
*
* @return
*/
static {
try {
final Map nameValueMap = new HashMap<>();
final Map codeValueMap = new HashMap<>();
for (Field f : RetCode.class.getDeclaredFields()) {
if (isStatic(f.getModifiers()) && isFinal(f.getModifiers()) && f.getType() == RetCode.class) {
AccessController.doPrivileged(new PrivilegedAction() {
@Override
public Void run() {
f.setAccessible(true);
return null;
}
});
Object fObj = f.get(null);
if (fObj instanceof RetCode) {
RetCode retCode = (RetCode) f.get(null);
state(isNull(nameValueMap.putIfAbsent(f.getName(), retCode)), "");
state(isNull(codeValueMap.putIfAbsent(retCode.getErrcode(), retCode)), "");
}
}
}
internalNameValues = unmodifiableMap(nameValueMap);
internalCodeValues = unmodifiableMap(codeValueMap);
} catch (Exception ex) {
throw new IllegalStateException("", ex);
}
}
}
/**
*
* Global errors code message prefix builder.
*
* @author Wangl.sir
* @version v1.0 2019年11月7日
* @since
*/
public static final class ErrorPromptMessageBuilder {
/**
* Errors prefix definition.
*
* @see {@link com.wl4g.component.common.web.rest.RespBase#globalErrPrefix()}
*/
private static String errorPrompt = getProperty("spring.xcloud.component.resp-base.error-prompt", "API");
/**
* Building error message with prefix.
*
* @param retCode
* @param errmsg
* @return
*/
static final String build(RetCode retCode, String errmsg) {
// Ignore display in message when response code is OK.
if (isBlank(errmsg) || retCode == RetCode.OK) {
return errmsg;
}
return format("[%s-%s] %s", errorPrompt, retCode.getErrcode(), errmsg);
}
/**
* Setup global error message prefix.
*
* @param errorPrompt
*/
public static final void setPrompt(String errorPrompt) {
// hasText(errorPrompt, "Global error prompt can't be empty.");
if (!isBlank(errorPrompt)) {
ErrorPromptMessageBuilder.errorPrompt = errorPrompt.replaceAll("-", "").toUpperCase(US);
}
}
}
/**
* Default status value.
*/
public static final String DEFAULT_STATUS = "Normal";
/**
* Default requestId value.
*/
public static final String DEFAULT_REQUESTID = null;
/**
* Default data value.
* Note: can't be {@link DEFAULT_DATA} = new Object(),
* otherwise jackson serialization will have the following error,
* e.g.:
*
*
*JsonMappingException: No serializer found for class java.lang.Object and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: com.wl4g.component.common.web.rest.RespBase["data"])
*
*/
public static final Object DEFAULT_DATA = emptyMap();
}