1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.syncope.core.rest.cxf;
20
21 import java.sql.SQLException;
22 import java.util.HashMap;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.Optional;
26 import java.util.Set;
27 import java.util.stream.Collectors;
28 import javax.persistence.EntityExistsException;
29 import javax.persistence.PersistenceException;
30 import javax.persistence.RollbackException;
31 import javax.validation.ValidationException;
32 import javax.ws.rs.WebApplicationException;
33 import javax.ws.rs.core.HttpHeaders;
34 import javax.ws.rs.core.Response;
35 import javax.ws.rs.core.Response.ResponseBuilder;
36 import javax.ws.rs.ext.ExceptionMapper;
37 import javax.ws.rs.ext.Provider;
38 import org.apache.commons.lang3.exception.ExceptionUtils;
39 import org.apache.cxf.jaxrs.utils.JAXRSUtils;
40 import org.apache.cxf.jaxrs.validation.ValidationExceptionMapper;
41 import org.apache.syncope.common.lib.SyncopeClientCompositeException;
42 import org.apache.syncope.common.lib.SyncopeClientException;
43 import org.apache.syncope.common.lib.to.ErrorTO;
44 import org.apache.syncope.common.lib.types.ClientExceptionType;
45 import org.apache.syncope.common.lib.types.EntityViolationType;
46 import org.apache.syncope.common.rest.api.RESTHeaders;
47 import org.apache.syncope.core.persistence.api.attrvalue.validation.InvalidEntityException;
48 import org.apache.syncope.core.persistence.api.attrvalue.validation.ParsingValidationException;
49 import org.apache.syncope.core.persistence.api.dao.DuplicateException;
50 import org.apache.syncope.core.persistence.api.dao.MalformedPathException;
51 import org.apache.syncope.core.persistence.api.dao.NotFoundException;
52 import org.apache.syncope.core.persistence.api.entity.PlainAttr;
53 import org.apache.syncope.core.spring.security.DelegatedAdministrationException;
54 import org.apache.syncope.core.workflow.api.WorkflowException;
55 import org.identityconnectors.framework.common.exceptions.ConfigurationException;
56 import org.identityconnectors.framework.common.exceptions.ConnectorException;
57 import org.slf4j.Logger;
58 import org.slf4j.LoggerFactory;
59 import org.springframework.core.env.Environment;
60 import org.springframework.dao.DataIntegrityViolationException;
61 import org.springframework.dao.UncategorizedDataAccessException;
62 import org.springframework.security.access.AccessDeniedException;
63 import org.springframework.transaction.TransactionSystemException;
64
65 @Provider
66 public class RestServiceExceptionMapper implements ExceptionMapper<Exception> {
67
68 private static final Logger LOG = LoggerFactory.getLogger(RestServiceExceptionMapper.class);
69
70 private final ValidationExceptionMapper validationEM = new ValidationExceptionMapper();
71
72 private static final String UNIQUE_MSG_KEY = "UniqueConstraintViolation";
73
74 private static final Map<String, String> EXCEPTION_CODE_MAP = new HashMap<>() {
75
76 private static final long serialVersionUID = -7688359318035249200L;
77
78 {
79 put("23000", UNIQUE_MSG_KEY);
80 put("23505", UNIQUE_MSG_KEY);
81 }
82 };
83
84 protected final Environment env;
85
86 public RestServiceExceptionMapper(final Environment env) {
87 this.env = env;
88 }
89
90 @Override
91 public Response toResponse(final Exception ex) {
92 LOG.error("Exception thrown", ex);
93
94 ResponseBuilder builder;
95
96 if (ex instanceof AccessDeniedException) {
97
98 builder = null;
99 } else if (ex instanceof SyncopeClientException) {
100 SyncopeClientException sce = (SyncopeClientException) ex;
101 builder = sce.isComposite()
102 ? getSyncopeClientCompositeExceptionResponse(sce.asComposite())
103 : getSyncopeClientExceptionResponse(sce);
104 } else if (ex instanceof DelegatedAdministrationException
105 || ExceptionUtils.getRootCause(ex) instanceof DelegatedAdministrationException) {
106
107 builder = builder(ClientExceptionType.DelegatedAdministration, ExceptionUtils.getRootCauseMessage(ex));
108 } else if (ex instanceof EntityExistsException || ex instanceof DuplicateException
109 || ((ex instanceof PersistenceException || ex instanceof DataIntegrityViolationException)
110 && ex.getCause() instanceof EntityExistsException)) {
111
112 builder = builder(ClientExceptionType.EntityExists,
113 getPersistenceErrorMessage(
114 ex instanceof PersistenceException || ex instanceof DataIntegrityViolationException
115 ? ex.getCause() : ex));
116 } else if (ex instanceof DataIntegrityViolationException || ex instanceof UncategorizedDataAccessException) {
117 builder = builder(ClientExceptionType.DataIntegrityViolation, getPersistenceErrorMessage(ex));
118 } else if (ex instanceof ConnectorException) {
119 builder = builder(ClientExceptionType.ConnectorException, ExceptionUtils.getRootCauseMessage(ex));
120 } else if (ex instanceof NotFoundException) {
121 builder = builder(ClientExceptionType.NotFound, ExceptionUtils.getRootCauseMessage(ex));
122 } else {
123 builder = processInvalidEntityExceptions(ex);
124 if (builder == null) {
125 builder = processBadRequestExceptions(ex);
126 }
127
128 if (builder == null && ex instanceof ValidationException) {
129 builder = builder(validationEM.toResponse((ValidationException) ex)).
130 header(RESTHeaders.ERROR_CODE, ClientExceptionType.RESTValidation.name()).
131 header(RESTHeaders.ERROR_INFO, ClientExceptionType.RESTValidation.getInfoHeaderValue(
132 ExceptionUtils.getRootCauseMessage(ex)));
133 }
134
135 if (builder == null && ex instanceof WebApplicationException) {
136 builder = builder(((WebApplicationException) ex).getResponse()).
137 header(RESTHeaders.ERROR_CODE, ClientExceptionType.Unknown.name()).
138 header(RESTHeaders.ERROR_INFO, ClientExceptionType.Unknown.getInfoHeaderValue(
139 ExceptionUtils.getRootCauseMessage(ex)));
140 }
141
142 if (builder == null) {
143 builder = Response.status(Response.Status.INTERNAL_SERVER_ERROR).
144 header(RESTHeaders.ERROR_CODE, ClientExceptionType.Unknown.name()).
145 header(RESTHeaders.ERROR_INFO, ClientExceptionType.Unknown.getInfoHeaderValue(
146 ExceptionUtils.getRootCauseMessage(ex)));
147
148 ErrorTO error = new ErrorTO();
149 error.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode());
150 error.setType(ClientExceptionType.Unknown);
151 error.getElements().add(ExceptionUtils.getRootCauseMessage(ex));
152
153 builder.entity(error);
154 }
155 }
156
157 return Optional.ofNullable(builder).map(ResponseBuilder::build).orElse(null);
158 }
159
160 private static ResponseBuilder getSyncopeClientExceptionResponse(final SyncopeClientException ex) {
161 ResponseBuilder builder = Response.status(ex.getType().getResponseStatus());
162 builder.header(RESTHeaders.ERROR_CODE, ex.getType().name());
163
164 ErrorTO error = new ErrorTO();
165 error.setStatus(ex.getType().getResponseStatus().getStatusCode());
166 error.setType(ex.getType());
167
168 ex.getElements().forEach(element -> {
169 builder.header(RESTHeaders.ERROR_INFO, ex.getType().getInfoHeaderValue(element));
170 error.getElements().add(element);
171 });
172
173 return builder.entity(error);
174 }
175
176 private static ResponseBuilder getSyncopeClientCompositeExceptionResponse(
177 final SyncopeClientCompositeException ex) {
178 if (ex.getExceptions().size() == 1) {
179 return getSyncopeClientExceptionResponse(ex.getExceptions().iterator().next());
180 }
181
182 ResponseBuilder builder = Response.status(Response.Status.BAD_REQUEST);
183
184 List<ErrorTO> errors = ex.getExceptions().stream().map(sce -> {
185 builder.header(RESTHeaders.ERROR_CODE, sce.getType().name());
186
187 ErrorTO error = new ErrorTO();
188 error.setStatus(sce.getType().getResponseStatus().getStatusCode());
189 error.setType(sce.getType());
190
191 sce.getElements().forEach(element -> {
192 builder.header(RESTHeaders.ERROR_INFO, sce.getType().getInfoHeaderValue(element));
193 error.getElements().add(element);
194 });
195
196 return error;
197 }).collect(Collectors.toList());
198
199 return builder.entity(errors);
200 }
201
202 private static ResponseBuilder processInvalidEntityExceptions(final Exception ex) {
203 InvalidEntityException iee = null;
204
205 if (ex instanceof InvalidEntityException) {
206 iee = (InvalidEntityException) ex;
207 }
208 if (ex instanceof TransactionSystemException && ex.getCause() instanceof RollbackException
209 && ex.getCause().getCause() instanceof InvalidEntityException) {
210
211 iee = (InvalidEntityException) ex.getCause().getCause();
212 }
213
214 if (iee != null) {
215 ClientExceptionType exType;
216 if (iee.getEntityClassSimpleName().endsWith("Policy")) {
217 exType = ClientExceptionType.InvalidPolicy;
218 } else if (iee.getEntityClassSimpleName().equals(PlainAttr.class.getSimpleName())) {
219 exType = ClientExceptionType.InvalidValues;
220 } else {
221 try {
222 exType = ClientExceptionType.valueOf("Invalid" + iee.getEntityClassSimpleName());
223 } catch (IllegalArgumentException e) {
224
225 exType = ClientExceptionType.InvalidEntity;
226 }
227 }
228
229 ResponseBuilder builder = Response.status(Response.Status.BAD_REQUEST);
230 builder.header(RESTHeaders.ERROR_CODE, exType.name());
231
232 ErrorTO error = new ErrorTO();
233 error.setStatus(exType.getResponseStatus().getStatusCode());
234 error.setType(exType);
235
236 for (Map.Entry<Class<?>, Set<EntityViolationType>> violation : iee.getViolations().entrySet()) {
237 for (EntityViolationType violationType : violation.getValue()) {
238 builder.header(RESTHeaders.ERROR_INFO,
239 exType.getInfoHeaderValue(violationType.name() + ": " + violationType.getMessage()));
240 error.getElements().add(violationType.name() + ": " + violationType.getMessage());
241 }
242 }
243
244 return builder;
245 }
246
247 return null;
248 }
249
250 private static ResponseBuilder processBadRequestExceptions(final Exception ex) {
251
252 Class<?> ibatisPersistenceException = null;
253 try {
254 ibatisPersistenceException = Class.forName("org.apache.ibatis.exceptions.PersistenceException");
255 } catch (ClassNotFoundException e) {
256
257 }
258
259 if (ex instanceof WorkflowException) {
260 return builder(ClientExceptionType.Workflow, ExceptionUtils.getRootCauseMessage(ex));
261 } else if (ex instanceof PersistenceException) {
262 return builder(ClientExceptionType.GenericPersistence, ExceptionUtils.getRootCauseMessage(ex));
263 } else if (ibatisPersistenceException != null && ibatisPersistenceException.isAssignableFrom(ex.getClass())) {
264 return builder(ClientExceptionType.Workflow, "Currently unavailable. Please try later.");
265 } else if (ex instanceof UncategorizedDataAccessException) {
266 return builder(ClientExceptionType.DataIntegrityViolation, ExceptionUtils.getRootCauseMessage(ex));
267 } else if (ex instanceof ConfigurationException) {
268 return builder(ClientExceptionType.InvalidConnIdConf, ExceptionUtils.getRootCauseMessage(ex));
269 } else if (ex instanceof ParsingValidationException) {
270 return builder(ClientExceptionType.InvalidValues, ExceptionUtils.getRootCauseMessage(ex));
271 } else if (ex instanceof MalformedPathException) {
272 return builder(ClientExceptionType.InvalidPath, ExceptionUtils.getRootCauseMessage(ex));
273 }
274
275 return null;
276 }
277
278 private static ResponseBuilder builder(final ClientExceptionType hType, final String msg) {
279 ResponseBuilder builder = Response.status(hType.getResponseStatus()).
280 header(RESTHeaders.ERROR_CODE, hType.name()).
281 header(RESTHeaders.ERROR_INFO, hType.getInfoHeaderValue(msg));
282
283 ErrorTO error = new ErrorTO();
284 error.setStatus(hType.getResponseStatus().getStatusCode());
285 error.setType(hType);
286 error.getElements().add(msg);
287
288 return builder.entity(error);
289 }
290
291
292
293
294
295
296
297
298 private static ResponseBuilder builder(final Response response) {
299 ResponseBuilder builder = JAXRSUtils.toResponseBuilder(response.getStatus());
300 builder.entity(response.getEntity());
301 response.getMetadata().forEach((key, value) -> {
302 if (!HttpHeaders.CONTENT_TYPE.equals(key)) {
303 value.forEach(headerValue -> builder.header(key, headerValue));
304 }
305 });
306
307 return builder;
308 }
309
310 private String getPersistenceErrorMessage(final Throwable ex) {
311 Throwable throwable = ExceptionUtils.getRootCause(ex);
312
313 String message = null;
314 if (throwable instanceof SQLException) {
315 String messageKey = EXCEPTION_CODE_MAP.get(((SQLException) throwable).getSQLState());
316 if (messageKey != null) {
317 message = env.getProperty("errMessage." + messageKey);
318 }
319 } else if (throwable instanceof EntityExistsException || throwable instanceof DuplicateException) {
320 message = env.getProperty("errMessage." + UNIQUE_MSG_KEY);
321 }
322
323 return Optional.ofNullable(message).
324 orElseGet(() -> Optional.ofNullable(ex.getCause()).
325 map(Throwable::getMessage).
326 orElseGet(() -> ex.getMessage()));
327 }
328 }