package info.xiancloud.core.message; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.serializer.SerializerFeature; import info.xiancloud.core.Constant; import info.xiancloud.core.Group; import info.xiancloud.core.Page; import info.xiancloud.core.Unit; import info.xiancloud.core.distribution.LocalNodeManager; import info.xiancloud.core.distribution.MessageType; import info.xiancloud.core.distribution.NodeStatus; import info.xiancloud.core.util.CloneUtil; import info.xiancloud.core.util.LOG; import info.xiancloud.core.util.Reflection; import info.xiancloud.core.util.file.StreamUtil; import info.xiancloud.core.util.thread.MsgIdHolder; import java.io.InputStream; import java.math.BigDecimal; import java.util.List; import java.util.Map; import java.util.concurrent.Callable; import java.util.function.Consumer; import java.util.function.Function; /** * Unified response class for the {@link Unit Xian unit}. * * @author happyyangyuan */ final public class UnitResponse { /** * the code of this unit response. * code {@link Group#CODE_SUCCESS} represents a successful unit response, otherwise failure. */ private String code; /** * description message. */ private String message; /** * the data object. * Can be an exception. */ private Object data; ///null if no exception. Do not use this exception property when code is {@link Group#CODE_SUCCESS} // 'exception' property is deprecated, exception is put in 'data' property. // private Throwable exception; /** * There is always a default. Null pointer is not welcome. */ private Context context = Context.create(); /** * Singleton instance of a succeeded unit response. * * @deprecated This singleton object is not thread-safe, use it carefully. */ private static final UnitResponse SUCCEEDED_SINGLETON = createSuccess(); public UnitResponse setContext(Context context) { this.context = context; return this; } private UnitResponse() { } public Context getContext() { return context; } /** * @return {@link #SUCCEEDED_SINGLETON The success unit response object}. * @deprecated This method returns a singleton unit response which is not thread-safe. Use {@link #createSuccess()} instead. */ public static UnitResponse succeededSingleton() { return SUCCEEDED_SINGLETON; } /** * create a succeeded response instance. */ public static UnitResponse createSuccess() { return new UnitResponse().setCode(Group.CODE_SUCCESS); } /** * create a succeeded response instance with specified data. */ public static UnitResponse createSuccess(Object data) { return UnitResponse.createSuccess().setData(data); } /** * create a succeeded response instance with specified data. * * @param data the data for the susccessful response * @param errMsg the description message * @return the newly created response */ public static UnitResponse createSuccess(Object data, String errMsg) { return createSuccess(data).setMessage(errMsg); } /** * Please pass an exception object to this method, and it returns a newly created response object with error code {@link Group#CODE_EXCEPTION} * and the exception object as the data. * * @param e the exception object. */ public static UnitResponse createException(Throwable e) { return UnitResponse .createError(Group.CODE_EXCEPTION, e, null); } /** * Please pass an exception object to this method, and it returns a newly created response object with error code {@link Group#CODE_EXCEPTION} * and the exception object as the data. * * @param e the exception object. * @param errMsg the error message that you want to add. */ public static UnitResponse createException(Throwable e, String errMsg) { return UnitResponse.createException(e).setMessage(errMsg); } /** * Create an exception unit response object. * * @param errCode the error code for the unit response. * @param exception the exception object. * @return the newly created unit response. */ public static UnitResponse createException(String errCode, Throwable exception) { return UnitResponse.createException(exception).setCode(errCode); } /** * Create a exception unit response. * This method is the same as {@link #createError(String, Object, String)} * * @param errCode error code * @param exception exception * @param errMsg error message * @return a newly created unit response representing the exception. */ public static UnitResponse createException(String errCode, Throwable exception, String errMsg) { return UnitResponse.createException(errCode, exception).setMessage(errMsg); } /** * @param data The failure data. Leave it null if you have no data to set. * Under some complicated business situations, failed data must be included and returned. * @param errMsg failure reason or description. * @return an response object with failure data and failure message. */ public static UnitResponse createUnknownError(Object data, String errMsg) { return createError(Group.CODE_UNKNOWN_ERROR, data, errMsg); } /** * create a new error unit response instance. * * @param errCode the error code. * @param data the data, anything you want, can be an exception object too * @param errMsg the error message. * @return the newly created unit response object. */ public static UnitResponse createError(String errCode, Object data, String errMsg) { if (Group.CODE_SUCCESS.equalsIgnoreCase(errCode)) { throw new IllegalArgumentException("Only non-success code is allowed here."); } return new UnitResponse().setCode(errCode).setData(data).setMessage(errMsg); } /** * create a new timed out unit response. * * @param dataOrException the data or exception object. * @param errMsg the error message. * @return the newly created unit response object. */ public static UnitResponse createTimeout(Object dataOrException, String errMsg) { return new UnitResponse().setCode(Group.CODE_TIME_OUT).setData(dataOrException).setMessage(errMsg); } /** * @param data the value * @param errMsg error message. * @return the newly created response instance. */ public static UnitResponse createDataDoesNotExists(Object data, String errMsg) { return createError(Group.CODE_DATA_DOES_NOT_EXITS, data, errMsg); } /** * @param code {@link Group#CODE_SUCCESS SUCCESS}, {@link Group#CODE_UNKNOWN_ERROR FAILURE} etc. * @param data the data bean or json * @param errMsg the error message. */ public static UnitResponse create(String code, Object data, String errMsg) { return new UnitResponse().setCode(code).setData(data).setMessage(errMsg); } /** * Create neither an success response object or a failure one. * * @param successful true to return an succeeded response object, false the opposite. */ public static UnitResponse create(boolean successful) { if (successful) { return createSuccess(); } else { return createUnknownError(null, null); } } /** * create a rollback marked unit response * * @return the newly created unit reponse instance. */ @SuppressWarnings("unused") public static UnitResponse createRollingBack() { return createRollingBack(null); } /** * Create a rolling back response object with the given message. * * @param errMsg the error message. * @return An response object which will indicate a transactional rolling back. */ @SuppressWarnings("all") public static UnitResponse createRollingBack(String errMsg) { return UnitResponse.createUnknownError(null, errMsg).setContext(Context.create().setRollback(true)); } /** * create a new unit response when missingParam * * @param theMissingParameters theMissingParameters array, leave it null, if you dont know which parameter is missing. * @return the newly created response object describing the missed parameters. */ public static UnitResponse createMissingParam(Object theMissingParameters, String errMsg) { return UnitResponse.createError(Group.CODE_LACK_OF_PARAMETER, theMissingParameters, errMsg); } @SuppressWarnings({"unchecked"}) public static UnitResponse create(JSONObject json) { return Reflection.toType(json, UnitResponse.class); } public UnitResponse setCode(String code) { this.code = code; return this; } public String getCode() { return code; } public UnitResponse setData(Object data) { this.data = data; return this; } /** * Get the typed object directly. * * @return The casted object. */ @SuppressWarnings("unchecked") public <T> T getData() { return (T) data; } public String dataToStr() { return data == null ? null : data.toString(); } /** * Cast the data into exception * * @return the casted exception */ public Exception dataToException() { return Reflection.toType(data, Exception.class); } public Integer dataToInteger() { String dataStr = dataToStr(); return dataStr == null ? null : Integer.valueOf(dataStr); } public Long dataToLong() { String dataStr = dataToStr(); return dataStr == null ? null : Long.valueOf(dataStr); } public Boolean dataToBoolean() { return data == null ? null : Boolean.valueOf(dataToStr()); } /** * @return a bool value true/false * @throws NullPointerException if data is null */ @SuppressWarnings("unused") public boolean dataToBoolValue() { return Reflection.toType(data, boolean.class); } @SuppressWarnings("unused") public Double dataToDouble() { return Reflection.toType(data, Double.class); } @SuppressWarnings("unused") public BigDecimal dataToBigDecimal() { return Reflection.toType(data, BigDecimal.class); } public JSONObject dataToJson() { return dataToType(JSONObject.class); } @SuppressWarnings("unused") public JSONObject firstToJson() { return dataToJson(); } @SuppressWarnings("unused") public JSONArray dataToJsonArray() { return dataToType(JSONArray.class); } /** * data to Page */ @SuppressWarnings("unused") public Page dataToPage() { if (data instanceof String) { return Page.create(data.toString()); } if (data instanceof JSONObject) { return Page.create((JSONObject) data); } if (data instanceof Page) { return (Page) data; } throw new RuntimeException(toString() + " 不是page!"); } /** * 将unitResponse.data适配为map的列表,请放心大胆做转换 */ @SuppressWarnings("unused") public List<JSONObject> dataToListOfJsonObject() { return dataToTypedList(JSONObject.class); } /** * 将unitResponse.data适配为指定的类型,请放心大胆做转换 */ public <T> T dataToType(Class<T> type) { return Reflection.toType(data, type); } /** * 将unitResponse.data适配为指定类型的list,请放心大胆做转换 */ public <T> List<T> dataToTypedList(Class<T> type) { return Reflection.toTypedList(data, type); } /** * The same as {@link #toVoJSONString()} ()} */ @Override public String toString() { return toVoJSONString(); } /** * Standard json formation without data lost. */ public String toJSONString() { if (context.isPretty()) { return JSON.toJSONStringWithDateFormat(this, Constant.DATE_SERIALIZE_FORMAT, SerializerFeature.PrettyFormat); } else { return JSONObject.toJSONStringWithDateFormat(this, Constant.DATE_SERIALIZE_FORMAT); } } /** * Our api gateway uses this method instead of {@link #toJSONString()} {@link #toString()} to produce a desensitized output VO json string. * For detail, see the return description. * * @return json string with sensitive properties(the context property) hidden. */ public String toVoJSONString() { if (context.isPretty()) { return JSON.toJSONStringWithDateFormat(toVoJSONObject(), Constant.DATE_SERIALIZE_FORMAT, SerializerFeature.PrettyFormat); } else { return JSONObject.toJSONStringWithDateFormat(toVoJSONObject(), Constant.DATE_SERIALIZE_FORMAT); } } /** * No matter context pretty attribute is true or not, this method formats the json string you want. * * @param pretty pretty or not. * @return formatted json string. */ public String toVoJSONString(boolean pretty) { if (pretty) { return JSON.toJSONStringWithDateFormat(toVoJSONObject(), Constant.DATE_SERIALIZE_FORMAT, SerializerFeature.PrettyFormat); } else { return JSONObject.toJSONStringWithDateFormat(toVoJSONObject(), Constant.DATE_SERIALIZE_FORMAT); } } /** * Our api gateway uses this method to generate a desensitized json object instead of the {@link #toJSONObject()} method. * * @return A deep cloned JSONObject with the sensitive properties(context property) hidden. */ @SuppressWarnings("all") public JSONObject toVoJSONObject() { return toJSONObject().fluentPut("context", null); } /** * convert this response object into json object. */ @SuppressWarnings("all") public JSONObject toJSONObject() { try { return Reflection.toType(this, JSONObject.class); } catch (Throwable e) { throw new RuntimeException("The output cannot be converted to jsonObject!", e); } } public UnitResponse throwExceptionIfNotSuccess() { if (!succeeded()) { if (getData() != null && getData() instanceof Throwable) { throw new RuntimeException((Throwable) getData()); } else { throw new RuntimeException(toString()); } } return this; } @SuppressWarnings("unused") public UnitResponse throwExceptionIfNotSuccess(String exceptionMsg) { if (!succeeded()) { if (getData() != null && getData() instanceof Throwable) { throw new RuntimeException(exceptionMsg, getData()); } else { throw new RuntimeException(exceptionMsg, new Throwable(toString())); } } return this; } /** * Judge weather the code is 'SUCCESS'. */ public boolean succeeded() { return Group.CODE_SUCCESS.equals(code); } /** * return message id of this response object. * This getter method is prepared for json serialization. */ public String getMsgId() { return context.getMsgId(); } /** * @param successCall 若当前output的code为SUCCESS时,该方法将会被执行(建议使用lambda表达式). */ @SuppressWarnings("unused") public UnitResponse successCall(Function<UnitResponse, UnitResponse> successCall) { if (successCall == null || !succeeded()) { return this; } try { return successCall.apply(this); } catch (Throwable e) { LOG.error(e); return UnitResponse.createException(e); } } /** * @param successCall 若当前output的code为SUCCESS时,该方法将会被执行(建议使用lambda表达式). */ @SuppressWarnings("unused") public UnitResponse successCall(Callable<UnitResponse> successCall) { if (successCall == null || !succeeded()) { return this; } try { return successCall.call(); } catch (Throwable e) { LOG.error(e); return UnitResponse.createException(e); } } /** * @param successCall 若当前output的code为SUCCESS时,该方法将会被执行(建议使用lambda表达式). */ @SuppressWarnings("unused") public void successCall(Consumer<UnitResponse> successCall) { if (successCall != null && succeeded()) { try { successCall.accept(this); } catch (Throwable e) { LOG.error(e); } } } @SuppressWarnings("unused") public String getMessage() { return message; } @SuppressWarnings("all") public UnitResponse setMessage(String message) { this.message = message; return this; } /** * 将data转为{@link InputStream} */ public InputStream dataToStream() { return dataToType(InputStream.class); } /** * 一行一行地处理inputStream,串行处理。 * * @param function 处理函数,入参为流的每一行字符串 */ public void processStreamLineByLine(Function<String, Object> function) { StreamUtil.lineByLine(dataToStream(), function); } /** * Process the data InputStream Part By Part orderly. * Only use this method when data of this response object is an input stream. * * @param function the processing function whose parameter is the input stream part. * @param delimiterPattern 分段分隔符,支持正则表达式 */ public void processStreamPartByPart(String delimiterPattern, Function<String, Object> function) { StreamUtil.partByPart(dataToType(InputStream.class), delimiterPattern, function); } /** * copy all properties from one response object ot another response object. * * @param from the source */ @SuppressWarnings("unused") public static UnitResponse clone(UnitResponse from) { return from.clone(); } /** * copy all properties from one response object ot another response object. * * @param from the source * @param to the destiny */ public static void copy(UnitResponse from, UnitResponse to) { //remember to modify this method when new properties are added. to.setCode(from.code).setData(from.data).setContext(from.context.clone()).setMessage(from.message); } @Override @SuppressWarnings("all") protected UnitResponse clone() { return CloneUtil.cloneBean(this, UnitResponse.class); } /** * read the value of the specified index and key in this response object. * * @param i the index * @param key the key * @return the value */ public Object value(int i, String key) { Object data = getData(); if (data instanceof List) { List list = (List) data; if (list.size() > 0) { Map map = (Map) list.get(i); return map.get(key); } } else if (data instanceof Map) { return ((Map) data).get(key); } return null; } public static final class Context { /** * whether or not to format a pretty output json. */ private boolean pretty = false; /** * node id which this response object is from. * Defaults to local node id, because response object is always created from the local node, and then be transported * to remote. */ private String sourceNodeId = LocalNodeManager.LOCAL_NODE_ID; /** * where this response object is aimed to be transported to. */ private String destinationNodeId; private boolean rollback = false; /** * Use message id if you know what you are doing. * Defaults to context message id. */ private String msgId = MsgIdHolder.get(); private String ssid; private NodeStatus nodeStatus; /*private long creationTimestamp; timestamp for response creation; we disable this property for performance consideration.*/ private long sentTimestamp;//message sent time in milli /** * defaults to {@link MessageType#response}, * and can be {@link MessageType#responseStream} too. */ private MessageType messageType = MessageType.response; /** * the {@link HttpContentType content type} for the gateway to response in the http header. */ private String httpContentType; public String getHttpContentType() { return httpContentType; } /** * @param httpContentType the {@link HttpContentType content type string} * @return this context object. * @see HttpContentType */ public Context setHttpContentType(String httpContentType) { this.httpContentType = httpContentType; return this; } public String getSsid() { return ssid; } public Context setSsid(String ssid) { this.ssid = ssid; return this; } public String getMsgId() { return msgId; } public Context setMsgId(String msgId) { this.msgId = msgId; return this; } public static Context create() { return new Context(); } @SuppressWarnings("all") public boolean isPretty() { return pretty; } @SuppressWarnings("all") public Context setPretty(boolean pretty) { this.pretty = pretty; return this; } public String getSourceNodeId() { return sourceNodeId; } @SuppressWarnings("all") public Context setSourceNodeId(String sourceNodeId) { this.sourceNodeId = sourceNodeId; return this; } public String getDestinationNodeId() { return destinationNodeId; } public Context setDestinationNodeId(String destinationNodeId) { this.destinationNodeId = destinationNodeId; return this; } public boolean isRollback() { return rollback; } @SuppressWarnings("all") public Context setRollback(boolean rollback) { this.rollback = rollback; return this; } @SuppressWarnings("all") public NodeStatus getNodeStatus() { return nodeStatus; } @SuppressWarnings("all") public Context setNodeStatus(NodeStatus nodeStatus) { this.nodeStatus = nodeStatus; return this; } public MessageType getMessageType() { return messageType; } @SuppressWarnings("all") public Context setMessageType(MessageType messageType) { this.messageType = messageType; return this; } /*@SuppressWarnings("all") public long getCreationTimestamp() { return creationTimestamp; } @SuppressWarnings("all") public Context setCreationTimestamp(long creationTimestamp) { this.creationTimestamp = creationTimestamp; return this; }*/ public long getSentTimestamp() { return sentTimestamp; } @SuppressWarnings("all") public Context setSentTimestamp(long sentTimestamp) { this.sentTimestamp = sentTimestamp; return this; } @Override @SuppressWarnings("all") protected Context clone() { return CloneUtil.cloneBean(this, Context.class); } } }