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.frontend.jersey.resources;
20  
21  import java.util.Optional;
22  import java.util.stream.Stream;
23  
24  import org.apache.logging.log4j.LogManager;
25  import org.apache.logging.log4j.Logger;
26  import org.jdom2.Document;
27  import org.mycore.common.MCRSessionMgr;
28  import org.mycore.common.MCRTransactionHelper;
29  import org.mycore.common.content.MCRContent;
30  import org.mycore.frontend.jersey.MCRJerseyUtil;
31  import org.mycore.frontend.servlets.MCRErrorServlet;
32  
33  import jakarta.servlet.http.HttpServletRequest;
34  import jakarta.ws.rs.InternalServerErrorException;
35  import jakarta.ws.rs.WebApplicationException;
36  import jakarta.ws.rs.core.Context;
37  import jakarta.ws.rs.core.HttpHeaders;
38  import jakarta.ws.rs.core.MediaType;
39  import jakarta.ws.rs.core.Response;
40  import jakarta.ws.rs.core.UriInfo;
41  import jakarta.ws.rs.core.Response.Status;
42  import jakarta.ws.rs.ext.ExceptionMapper;
43  import jakarta.ws.rs.ext.Provider;
44  import jakarta.xml.bind.annotation.XmlAttribute;
45  import jakarta.xml.bind.annotation.XmlElement;
46  import jakarta.xml.bind.annotation.XmlRootElement;
47  import jakarta.xml.bind.annotation.adapters.XmlAdapter;
48  import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
49  
50  /**
51   * Handles jersey web application exceptions. By default this class
52   * just forwards the exception. Only if the request accepts HTML a
53   * custom error page is generated.
54   *
55   * @author Matthias Eichner
56   */
57  @Provider
58  public class MCRJerseyExceptionMapper implements ExceptionMapper<Exception> {
59  
60      private static Logger LOGGER = LogManager.getLogger();
61  
62      @Context
63      HttpHeaders headers;
64  
65      @Context
66      HttpServletRequest request; //required for MCRParamCollector
67  
68      @Context
69      UriInfo uriInfo;
70  
71      @Override
72      public Response toResponse(Exception exc) {
73          Response response = getResponse(exc);
74          if (exc instanceof WebApplicationException && !(exc instanceof InternalServerErrorException)) {
75              LOGGER.warn("{}: {}, {}", uriInfo.getRequestUri(), response.getStatus(), exc.getMessage());
76              LOGGER.debug("Exception: ", exc);
77          } else {
78              LOGGER.warn(() -> "Error while processing request " + uriInfo.getRequestUri(), exc);
79          }
80          if (headers.getAcceptableMediaTypes().contains(MediaType.TEXT_HTML_TYPE)) {
81              // try to return a html error page
82              if (!MCRSessionMgr.isLocked() && !MCRTransactionHelper.isTransactionActive()) {
83                  MCRSessionMgr.getCurrentSession();
84                  MCRTransactionHelper.beginTransaction();
85              }
86              try {
87                  int status = response.getStatus();
88                  String source = exc.getStackTrace().length > 0 ? exc.getStackTrace()[0].toString() : null;
89                  Document errorPageDocument = MCRErrorServlet.buildErrorPage(exc.getMessage(), status,
90                      uriInfo.getRequestUri().toASCIIString(), null, source, exc);
91                  MCRContent result = MCRJerseyUtil.transform(errorPageDocument, request);
92                  return Response.serverError()
93                      .entity(result.getInputStream())
94                      .type(result.getMimeType())
95                      .status(status)
96                      .build();
97              } catch (Exception transformException) {
98                  return Response.status(Status.INTERNAL_SERVER_ERROR).entity(transformException).build();
99              } finally {
100                 if (!MCRSessionMgr.isLocked()) {
101                     MCRTransactionHelper.commitTransaction();
102                 }
103             }
104         }
105         return response;
106     }
107 
108     private Response getResponse(Exception exc) {
109         if (exc instanceof WebApplicationException) {
110             Response response = ((WebApplicationException) exc).getResponse();
111             if (response.hasEntity()) {
112                 return response;
113             }
114             return Response.fromResponse(response)
115                 .entity(exc.getMessage())
116                 .type(MediaType.TEXT_PLAIN_TYPE)
117                 .build();
118         }
119         Object entity = Optional.ofNullable(request.getContentType())
120             .map(MediaType::valueOf)
121             .filter(t -> t.isCompatible(MediaType.APPLICATION_FORM_URLENCODED_TYPE) || t
122                 .isCompatible(MediaType.MULTIPART_FORM_DATA_TYPE))
123             .map(t -> (Object) exc.getMessage()) //do not container form requests responses
124             .orElseGet(() -> new MCRExceptionContainer(exc));
125         return Response.status(Status.INTERNAL_SERVER_ERROR).entity(entity).build();
126     }
127 
128     @XmlRootElement(name = "error")
129     private static class MCRExceptionContainer {
130         private Exception exception;
131 
132         private MCRExceptionContainer() {
133             //required for JAXB
134         }
135 
136         MCRExceptionContainer(Exception e) {
137             this.exception = e;
138         }
139 
140         @XmlElement
141         @XmlJavaTypeAdapter(ExceptionTypeAdapter.class)
142         public Exception getException() {
143             return exception;
144         }
145 
146         @XmlAttribute
147         public Class<? extends Exception> getType() {
148             return exception.getClass();
149         }
150 
151     }
152 
153     private static class ExceptionTypeAdapter extends XmlAdapter<RException, Exception> {
154         @Override
155         public Exception unmarshal(RException v) throws Exception {
156             throw new UnsupportedOperationException();
157         }
158 
159         @Override
160         public RException marshal(Exception v) {
161             RException ep = new RException();
162             ep.message = v.getMessage();
163             ep.stackTrace = Stream.of(v.getStackTrace()).map(RStackTraceElement::getInstance)
164                 .toArray(RStackTraceElement[]::new);
165             Optional.ofNullable(v.getCause())
166                 .filter(Exception.class::isInstance)
167                 .map(Exception.class::cast)
168                 .map(this::marshal)
169                 .ifPresent(c -> ep.cause = c);
170             return ep;
171         }
172     }
173 
174     private static class RException {
175         @XmlElement
176         private String message;
177 
178         @XmlElement
179         private RStackTraceElement[] stackTrace;
180 
181         @XmlElement
182         private RException cause;
183 
184     }
185 
186     private static class RStackTraceElement {
187         @XmlAttribute
188         private String className;
189 
190         @XmlAttribute
191         private String method;
192 
193         @XmlAttribute
194         private String file;
195 
196         @XmlAttribute
197         private int line;
198 
199         private static RStackTraceElement getInstance(StackTraceElement ste) {
200             RStackTraceElement rste = new RStackTraceElement();
201             rste.className = ste.getClassName();
202             rste.method = ste.getMethodName();
203             rste.file = ste.getFileName();
204             rste.line = ste.getLineNumber();
205             return rste;
206         }
207     }
208 
209 }