View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.syncope.ext.scimv2.cxf;
20  
21  import java.util.Map;
22  import java.util.Optional;
23  import java.util.Set;
24  import javax.validation.ValidationException;
25  import javax.ws.rs.ForbiddenException;
26  import javax.ws.rs.NotAuthorizedException;
27  import javax.ws.rs.core.Response;
28  import javax.ws.rs.core.Response.ResponseBuilder;
29  import javax.ws.rs.ext.ExceptionMapper;
30  import javax.ws.rs.ext.Provider;
31  import org.apache.commons.lang3.exception.ExceptionUtils;
32  import org.apache.syncope.common.lib.SyncopeClientException;
33  import org.apache.syncope.common.lib.types.ClientExceptionType;
34  import org.apache.syncope.common.lib.types.EntityViolationType;
35  import org.apache.syncope.core.persistence.api.attrvalue.validation.InvalidEntityException;
36  import org.apache.syncope.core.persistence.api.attrvalue.validation.ParsingValidationException;
37  import org.apache.syncope.core.persistence.api.dao.DuplicateException;
38  import org.apache.syncope.core.persistence.api.dao.MalformedPathException;
39  import org.apache.syncope.core.persistence.api.dao.NotFoundException;
40  import org.apache.syncope.core.persistence.api.entity.PlainAttr;
41  import org.apache.syncope.core.spring.security.DelegatedAdministrationException;
42  import org.apache.syncope.core.workflow.api.WorkflowException;
43  import org.apache.syncope.ext.scimv2.api.BadRequestException;
44  import org.apache.syncope.ext.scimv2.api.data.SCIMError;
45  import org.apache.syncope.ext.scimv2.api.type.ErrorType;
46  import org.identityconnectors.framework.common.exceptions.ConfigurationException;
47  import org.slf4j.Logger;
48  import org.slf4j.LoggerFactory;
49  import org.springframework.dao.DataIntegrityViolationException;
50  import org.springframework.security.access.AccessDeniedException;
51  import org.springframework.transaction.TransactionSystemException;
52  
53  @Provider
54  public class SCIMExceptionMapper implements ExceptionMapper<Exception> {
55  
56      protected static final Logger LOG = LoggerFactory.getLogger(SCIMExceptionMapper.class);
57  
58      protected static Class<?> ENTITYEXISTS_EXCLASS = null;
59  
60      protected static Class<?> PERSISTENCE_EXCLASS = null;
61  
62      protected static Class<?> ROLLBACK_EXCLASS = null;
63  
64      protected static Class<?> JPASYSTEM_EXCLASS = null;
65  
66      protected static Class<?> CONNECTOR_EXCLASS = null;
67  
68      protected static Class<?> IBATISPERSISTENCE_EXCLASS = null;
69  
70      static {
71          try {
72              ENTITYEXISTS_EXCLASS = Class.forName("javax.persistence.EntityExistsException");
73              PERSISTENCE_EXCLASS = Class.forName("javax.persistence.PersistenceException");
74              ROLLBACK_EXCLASS = Class.forName("javax.persistence.RollbackException");
75              JPASYSTEM_EXCLASS = Class.forName("org.springframework.orm.jpa.JpaSystemException");
76              CONNECTOR_EXCLASS = Class.forName("org.identityconnectors.framework.common.exceptions.ConnectorException");
77              IBATISPERSISTENCE_EXCLASS = Class.forName("org.apache.ibatis.exceptions.PersistenceException");
78          } catch (ClassNotFoundException e) {
79              // ignore
80          }
81      }
82  
83      @Override
84      public Response toResponse(final Exception ex) {
85          LOG.error("Exception thrown", ex);
86  
87          ResponseBuilder builder;
88  
89          if (ex instanceof AccessDeniedException
90                  || ex instanceof ForbiddenException
91                  || ex instanceof NotAuthorizedException) {
92  
93              // leaves the default exception processing
94              builder = null;
95          } else if (ex instanceof NotFoundException) {
96              return Response.status(Response.Status.NOT_FOUND).entity(new SCIMError(null,
97                      Response.Status.NOT_FOUND.getStatusCode(), ExceptionUtils.getRootCauseMessage(ex))).
98                      build();
99          } else if (ex instanceof SyncopeClientException) {
100             SyncopeClientException sce = (SyncopeClientException) ex;
101             builder = builder(sce.getType(), ExceptionUtils.getRootCauseMessage(ex));
102         } else if (ex instanceof DelegatedAdministrationException
103                 || ExceptionUtils.getRootCause(ex) instanceof DelegatedAdministrationException) {
104 
105             builder = builder(ClientExceptionType.DelegatedAdministration, ExceptionUtils.getRootCauseMessage(ex));
106         } else if (ENTITYEXISTS_EXCLASS.isAssignableFrom(ex.getClass())
107                 || ex instanceof DuplicateException
108                 || PERSISTENCE_EXCLASS.isAssignableFrom(ex.getClass())
109                 && ENTITYEXISTS_EXCLASS.isAssignableFrom(ex.getCause().getClass())) {
110 
111             builder = builder(ClientExceptionType.EntityExists, ExceptionUtils.getRootCauseMessage(ex));
112         } else if (ex instanceof DataIntegrityViolationException || JPASYSTEM_EXCLASS.isAssignableFrom(ex.getClass())) {
113             builder = builder(ClientExceptionType.DataIntegrityViolation, ExceptionUtils.getRootCauseMessage(ex));
114         } else if (CONNECTOR_EXCLASS.isAssignableFrom(ex.getClass())) {
115             builder = builder(ClientExceptionType.ConnectorException, ExceptionUtils.getRootCauseMessage(ex));
116         } else {
117             builder = processInvalidEntityExceptions(ex);
118             if (builder == null) {
119                 builder = processBadRequestExceptions(ex);
120             }
121             // process JAX-RS validation errors
122             if (builder == null && ex instanceof ValidationException) {
123                 builder = builder(ClientExceptionType.RESTValidation, ExceptionUtils.getRootCauseMessage(ex));
124             }
125             // process requests for features not yet implemented 
126             if (builder == null && ex instanceof UnsupportedOperationException) {
127                 builder = Response.status(Response.Status.INTERNAL_SERVER_ERROR).
128                         entity(new SCIMError(
129                                 ErrorType.invalidSyntax,
130                                 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(),
131                                 ExceptionUtils.getRootCauseMessage(ex)));
132             }
133             // ...or just report as InternalServerError
134             if (builder == null) {
135                 builder = Response.status(Response.Status.INTERNAL_SERVER_ERROR).
136                         entity(ExceptionUtils.getRootCauseMessage(ex));
137             }
138         }
139 
140         return Optional.ofNullable(builder).map(ResponseBuilder::build).orElse(null);
141     }
142 
143     protected ResponseBuilder processInvalidEntityExceptions(final Exception ex) {
144         InvalidEntityException iee = null;
145 
146         if (ex instanceof InvalidEntityException) {
147             iee = (InvalidEntityException) ex;
148         }
149         if (ex instanceof TransactionSystemException && ROLLBACK_EXCLASS.isAssignableFrom(ex.getCause().getClass())
150                 && ex.getCause().getCause() instanceof InvalidEntityException) {
151 
152             iee = (InvalidEntityException) ex.getCause().getCause();
153         }
154 
155         if (iee != null) {
156             ClientExceptionType exType;
157             if (iee.getEntityClassSimpleName().endsWith("Policy")) {
158                 exType = ClientExceptionType.InvalidPolicy;
159             } else if (iee.getEntityClassSimpleName().equals(PlainAttr.class.getSimpleName())) {
160                 exType = ClientExceptionType.InvalidValues;
161             } else {
162                 try {
163                     exType = ClientExceptionType.valueOf("Invalid" + iee.getEntityClassSimpleName());
164                 } catch (IllegalArgumentException e) {
165                     // ignore
166                     exType = ClientExceptionType.InvalidEntity;
167                 }
168             }
169 
170             StringBuilder msg = new StringBuilder();
171 
172             for (Map.Entry<Class<?>, Set<EntityViolationType>> violation : iee.getViolations().entrySet()) {
173                 for (EntityViolationType violationType : violation.getValue()) {
174                     msg.append(violationType.name()).append(": ").append(violationType.getMessage()).append('\n');
175                 }
176             }
177 
178             return builder(exType, msg.toString());
179         }
180 
181         return null;
182     }
183 
184     protected ResponseBuilder processBadRequestExceptions(final Exception ex) {
185         if (ex instanceof WorkflowException) {
186             return builder(ClientExceptionType.Workflow, ExceptionUtils.getRootCauseMessage(ex));
187         } else if (PERSISTENCE_EXCLASS.isAssignableFrom(ex.getClass())) {
188             return builder(ClientExceptionType.GenericPersistence, ExceptionUtils.getRootCauseMessage(ex));
189         } else if (IBATISPERSISTENCE_EXCLASS != null && IBATISPERSISTENCE_EXCLASS.isAssignableFrom(ex.getClass())) {
190             return builder(ClientExceptionType.Workflow, "Currently unavailable. Please try later.");
191         } else if (JPASYSTEM_EXCLASS.isAssignableFrom(ex.getClass())) {
192             return builder(ClientExceptionType.DataIntegrityViolation, ExceptionUtils.getRootCauseMessage(ex));
193         } else if (ex instanceof ConfigurationException) {
194             return builder(ClientExceptionType.InvalidConnIdConf, ExceptionUtils.getRootCauseMessage(ex));
195         } else if (ex instanceof ParsingValidationException) {
196             return builder(ClientExceptionType.InvalidValues, ExceptionUtils.getRootCauseMessage(ex));
197         } else if (ex instanceof MalformedPathException) {
198             return builder(ClientExceptionType.InvalidPath, ExceptionUtils.getRootCauseMessage(ex));
199         } else if (ex instanceof BadRequestException) {
200             return Response.status(Response.Status.BAD_REQUEST).entity(new SCIMError((BadRequestException) ex));
201         }
202 
203         return null;
204     }
205 
206     protected ResponseBuilder builder(final ClientExceptionType hType, final String msg) {
207         ResponseBuilder builder = Response.status(hType.getResponseStatus());
208 
209         ErrorType scimType = null;
210         if (hType.name().startsWith("Invalid") || hType == ClientExceptionType.RESTValidation) {
211             scimType = ErrorType.invalidValue;
212         } else if (hType == ClientExceptionType.EntityExists) {
213             scimType = ErrorType.uniqueness;
214         }
215 
216         return builder.entity(new SCIMError(scimType, hType.getResponseStatus().getStatusCode(), msg));
217     }
218 }