ResolvedError.java

package cn.home1.oss.lib.errorhandle.api;

import static com.google.common.collect.Maps.newLinkedHashMap;
import static lombok.AccessLevel.PRIVATE;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;

import org.apache.commons.lang3.ArrayUtils;
import org.codehaus.jackson.map.annotate.JsonSerialize;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;

import java.io.Serializable;
import java.util.Map;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlElements;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;

/**
 * Resolved error.
 *
 * <p>
 * Created by zhanghaolun on 16/7/1.
 * </p>
 */
@XmlRootElement(name = "error") // Jaxb2RootElementHttpMessageConverter
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(propOrder = { //
  "error", "exception", "message", "path", "status", "timestamp", "trace", "validationErrors", //
  "datetime", "headers", "localizedMessage", "tracks"})
@JsonInclude(JsonInclude.Include.NON_EMPTY) // for Jackson 2.x
@JsonSerialize(include = JsonSerialize.Inclusion.NON_EMPTY) // for Jackson 1.x
@Builder(builderMethodName = "resolvedErrorBuilder")
@AllArgsConstructor(access = PRIVATE)
@ToString
@EqualsAndHashCode(of = {"error", "exception", "path", "status", "timestamp", "localizedMessage"})
@Setter(PRIVATE)
@Getter
@Slf4j
public class ResolvedError implements Serializable {

  public static final String RESOLVED_ERROR_COOKIE = "resolvedErrorCookie";
  public static final String RESOLVED_ERROR_OBJECT = "resolvedError";
  static final String HEADER_RESOLVED_ERROR = "RESOLVED-ERROR";
  private static final long serialVersionUID = 1L;

  // ------------------------------ basic ------------------------------
  @JsonProperty("error")
  private String error;
  @XmlElement
  private String exception;
  private String message;
  private String path;
  private Integer status;
  private Long timestamp;
  private String trace;
  /**
   * Nested XmlElements see: https://github.com/FasterXML/jackson-module-jaxb-annotations/issues/42
   */
  @JsonProperty("validationErrors")
  @XmlElementWrapper(name = "validationErrors")
  @XmlElement(name = "validationError")
  //@XmlElements(@XmlElement(name = "validationError"))
  private ValidationError[] validationErrors;
  // ------------------------------ extended ------------------------------
  /**
   * ISO8601 string.
   */
  private String datetime;
  /**
   * 解析得到的 异常响应头信息.
   */
  @JsonProperty("headers")
  @XmlElementWrapper(name = "headers")
  @XmlElement(name = "header")
  private HttpHeader[] headers;
  /**
   * 解析得到的 错误信息.
   */
  private String localizedMessage;
  /**
   * 解析得到的 异常的调用路径 (RPC).
   */
  @JsonProperty("tracks")
  @XmlElementWrapper(name = "tracks")
  @XmlElement(name = "track")
  private String[] tracks;

  private ResolvedError() {
    this.headers = HttpHeader.fromHttpHeaders(newHttpHeaders());
  }

  public static HttpHeaders newHttpHeaders() {
    final HttpHeaders headers = new HttpHeaders();
    headers.add(HEADER_RESOLVED_ERROR, HEADER_RESOLVED_ERROR);
    return headers;
  }

  public static ResolvedError fromErrorAttributes(final Map<String, Object> errorAttributes) {
    return errorAttributes == null ? null : ResolvedError.resolvedErrorBuilder()
      // basic
      .error((String) errorAttributes.get("error")) //
      .exception((String) errorAttributes.get("exception"))
      .message((String) errorAttributes.get("message")) //
      .path((String) errorAttributes.get("path")) //
      .status((Integer) errorAttributes.get("status")) //
      .timestamp((Long) errorAttributes.get("timestamp")) //
      .trace((String) errorAttributes.get("trace")) //
      .validationErrors((ValidationError[]) errorAttributes.get("validationErrors")) //
      // extended
      .datetime((String) errorAttributes.get("datetime"))
      .headers((HttpHeader[]) errorAttributes.get("headers")) //
      .localizedMessage((String) errorAttributes.get("localizedMessage")) //
      .tracks((String[]) errorAttributes.get("tracks")).build();
  }

  @JsonIgnore
  @org.codehaus.jackson.annotate.JsonIgnore
  public HttpStatus getHttpStatus() {
    HttpStatus result;
    try {
      result = HttpStatus.valueOf(this.status);
    } catch (final Exception ex) {
      result = HttpStatus.INTERNAL_SERVER_ERROR;
      log.debug("error parse http status {}", this.status, ex);
    }
    return result;
  }

  /**
   * before set into cookie, call this method to avoid header size exceed limit.
   *
   * @return this
   */
  public ResolvedError eraseTraces() {
    this.setTracks(null);
    this.setTrace(null);
    return this;
  }

  public ResolvedError trackPrepend(final String track) {
    this.tracks = this.tracks != null ? //
      ArrayUtils.add(this.tracks, 0, track) : //
      new String[]{track};
    return this;
  }

  public Map<String, Object> toErrorAttributes() {
    final Map<String, Object> map = newLinkedHashMap();
    // basic
    map.put("error", this.error);
    map.put("exception", this.exception);
    map.put("message", this.message);
    map.put("path", this.path);
    map.put("status", this.status);
    map.put("timestamp", this.timestamp);
    map.put("trace", this.trace);
    map.put("validationErrors", this.validationErrors);
    // extended
    map.put("datetime", datetime);
    map.put("headers", this.headers);
    map.put("localizedMessage", this.localizedMessage);
    map.put("tracks", this.tracks);

    map.entrySet().removeIf(e -> e.getValue() == null);
    return map;
  }
}