View Javadoc
1   /*
2    * This file is part of ***  M y C o R e  ***
3    * See http://www.mycore.de/ for details.
4    *
5    * MyCoRe is free software: you can redistribute it and/or modify
6    * it under the terms of the GNU General Public License as published by
7    * the Free Software Foundation, either version 3 of the License, or
8    * (at your option) any later version.
9    *
10   * MyCoRe is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   * GNU General Public License for more details.
14   *
15   * You should have received a copy of the GNU General Public License
16   * along with MyCoRe.  If not, see <http://www.gnu.org/licenses/>.
17   */
18  
19  package org.mycore.restapi.v2;
20  
21  import java.beans.Transient;
22  import java.time.Instant;
23  import java.util.Date;
24  import java.util.Optional;
25  import java.util.UUID;
26  
27  import org.apache.logging.log4j.LogManager;
28  import org.mycore.restapi.converter.MCRInstantXMLAdapter;
29  
30  import com.fasterxml.jackson.annotation.JsonAutoDetect;
31  
32  import jakarta.ws.rs.BadRequestException;
33  import jakarta.ws.rs.ClientErrorException;
34  import jakarta.ws.rs.ForbiddenException;
35  import jakarta.ws.rs.InternalServerErrorException;
36  import jakarta.ws.rs.NotAcceptableException;
37  import jakarta.ws.rs.NotAllowedException;
38  import jakarta.ws.rs.NotAuthorizedException;
39  import jakarta.ws.rs.NotFoundException;
40  import jakarta.ws.rs.NotSupportedException;
41  import jakarta.ws.rs.ServerErrorException;
42  import jakarta.ws.rs.WebApplicationException;
43  import jakarta.ws.rs.core.Response;
44  import jakarta.xml.bind.annotation.XmlAccessType;
45  import jakarta.xml.bind.annotation.XmlAccessorType;
46  import jakarta.xml.bind.annotation.XmlAttribute;
47  import jakarta.xml.bind.annotation.XmlElement;
48  import jakarta.xml.bind.annotation.XmlRootElement;
49  import jakarta.xml.bind.annotation.XmlTransient;
50  import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
51  
52  @XmlRootElement(name = "error")
53  @XmlAccessorType(XmlAccessType.PUBLIC_MEMBER)
54  @JsonAutoDetect(getterVisibility = JsonAutoDetect.Visibility.PUBLIC_ONLY)
55  public class MCRErrorResponse {
56  
57      UUID uuid;
58  
59      Instant timestamp;
60  
61      String errorCode;
62  
63      Throwable cause;
64  
65      String message;
66  
67      String detail;
68  
69      int status;
70  
71      public static MCRErrorResponse fromStatus(int status) {
72          final MCRErrorResponse response = new MCRErrorResponse();
73          response.setStatus(status);
74          return response;
75      }
76  
77      private MCRErrorResponse() {
78          uuid = UUID.randomUUID();
79          timestamp = Instant.now();
80      }
81  
82      public WebApplicationException toException() {
83          WebApplicationException e;
84          Response.Status s = Response.Status.fromStatusCode(status);
85          final Response response = Response.status(s)
86              .entity(this)
87              .build();
88          //s maybe null
89          switch (Response.Status.Family.familyOf(status)) {
90          case CLIENT_ERROR:
91              //Response.Status.OK is to trigger "default" case
92              switch (s != null ? s : Response.Status.OK) {
93              case BAD_REQUEST:
94                  e = new BadRequestException(getMessage(), response, getCause());
95                  break;
96              case FORBIDDEN:
97                  e = new ForbiddenException(getMessage(), response, getCause());
98                  break;
99              case NOT_ACCEPTABLE:
100                 e = new NotAcceptableException(getMessage(), response, getCause());
101                 break;
102             case METHOD_NOT_ALLOWED:
103                 e = new NotAllowedException(getMessage(), response, getCause());
104                 break;
105             case UNAUTHORIZED:
106                 e = new NotAuthorizedException(getMessage(), response, getCause());
107                 break;
108             case NOT_FOUND:
109                 e = new NotFoundException(getMessage(), response, getCause());
110                 break;
111             case UNSUPPORTED_MEDIA_TYPE:
112                 e = new NotSupportedException(getMessage(), response, getCause());
113                 break;
114             default:
115                 e = new ClientErrorException(getMessage(), response, getCause());
116             }
117             break;
118         case SERVER_ERROR:
119             //Response.Status.OK is to trigger "default" case
120             switch (s != null ? s : Response.Status.OK) {
121             case INTERNAL_SERVER_ERROR:
122                 e = new InternalServerErrorException(getMessage(), response, getCause());
123                 break;
124             default:
125                 e = new ServerErrorException(getMessage(), response, getCause());
126             }
127             break;
128         default:
129             e = new WebApplicationException(getMessage(), getCause(), response);
130         }
131         LogManager.getLogger().error(this::getLogMessage, e);
132         return e;
133     }
134 
135     String getLogMessage() {
136         return getUuid() + " - " + getErrorCode() + ": " + getMessage();
137     }
138 
139     @XmlAttribute
140     public UUID getUuid() {
141         return uuid;
142     }
143 
144     public void setUuid(UUID uuid) {
145         this.uuid = uuid;
146     }
147 
148     @XmlAttribute
149     public String getErrorCode() {
150         return Optional.ofNullable(errorCode).filter(s -> !s.isBlank()).orElse("UNKNOWN");
151     }
152 
153     public void setErrorCode(String errorCode) {
154         this.errorCode = errorCode;
155     }
156 
157     @Transient
158     @XmlTransient
159     public Throwable getCause() {
160         return cause;
161     }
162 
163     public void setCause(Throwable cause) {
164         this.cause = cause;
165     }
166 
167     @XmlElement
168     public String getMessage() {
169         return message;
170     }
171 
172     public void setMessage(String message) {
173         this.message = message;
174     }
175 
176     @XmlElement
177     public String getDetail() {
178         return detail;
179     }
180 
181     public void setDetail(String detail) {
182         this.detail = detail;
183     }
184 
185     @XmlAttribute
186     @XmlJavaTypeAdapter(MCRInstantXMLAdapter.class)
187     public Instant getTimestamp() {
188         return timestamp;
189     }
190 
191     public void setTimestamp(Date timestamp) {
192         this.timestamp = timestamp.toInstant();
193     }
194 
195     @XmlTransient
196     @Transient
197     public int getStatus() {
198         return status;
199     }
200 
201     public void setStatus(int status) {
202         this.status = status;
203     }
204 
205     public MCRErrorResponse withCause(Throwable cause) {
206         setCause(cause);
207         return this;
208     }
209 
210     public MCRErrorResponse withMessage(String message) {
211         setMessage(message);
212         return this;
213     }
214 
215     public MCRErrorResponse withDetail(String detail) {
216         setDetail(detail);
217         return this;
218     }
219 
220     public MCRErrorResponse withErrorCode(String errorCode) {
221         setErrorCode(errorCode);
222         return this;
223     }
224 
225     @Override
226     public String toString() {
227         return "MCRErrorResponse{" +
228             "uuid=" + uuid +
229             ", status=" + status +
230             ", timestamp=" + timestamp +
231             ", errorCode='" + errorCode + '\'' +
232             ", message='" + message + '\'' +
233             ", detail='" + detail + '\'' +
234             ", cause=" + cause +
235             '}';
236     }
237 }