View Javadoc
1   package net.sumaris.server.http.graphql;
2   
3   /*-
4    * #%L
5    * SUMARiS:: Server
6    * %%
7    * Copyright (C) 2018 SUMARiS Consortium
8    * %%
9    * This program is free software: you can redistribute it and/or modify
10   * it under the terms of the GNU General Public License as
11   * published by the Free Software Foundation, either version 3 of the
12   * License, or (at your option) any later version.
13   * 
14   * This program is distributed in the hope that it will be useful,
15   * but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17   * GNU General Public License for more details.
18   * 
19   * You should have received a copy of the GNU General Public
20   * License along with this program.  If not, see
21   * <http://www.gnu.org/licenses/gpl-3.0.html>.
22   * #L%
23   */
24  
25  import com.fasterxml.jackson.databind.ObjectMapper;
26  import com.google.common.collect.Lists;
27  import com.google.common.collect.Maps;
28  import graphql.ExceptionWhileDataFetching;
29  import graphql.ExecutionResult;
30  import graphql.GraphQLError;
31  import graphql.GraphQLException;
32  import graphql.execution.AbortExecutionException;
33  import graphql.execution.ExecutionPath;
34  import graphql.servlet.GenericGraphQLError;
35  import net.sumaris.core.exception.SumarisBusinessException;
36  import net.sumaris.core.exception.SumarisTechnicalException;
37  import net.sumaris.server.exception.ErrorCodes;
38  import net.sumaris.server.exception.ErrorHelper;
39  import org.apache.commons.collections4.CollectionUtils;
40  import org.apache.commons.lang3.StringUtils;
41  import org.springframework.dao.DataRetrievalFailureException;
42  import org.springframework.security.access.AccessDeniedException;
43  
44  import java.io.IOException;
45  import java.util.HashMap;
46  import java.util.List;
47  import java.util.Map;
48  
49  public class GraphQLHelper {
50  
51      private GraphQLHelper() {
52          // helper class
53      }
54  
55  
56      public static Map<String, Object> getVariables(Map<String, Object> request, ObjectMapper objectMapper) {
57          Object variablesObj = request.get("variables");
58          if (variablesObj == null) {
59              return Maps.newHashMap(); // Need empty map
60          }
61          if (variablesObj instanceof String) {
62              String variableStr = (String) variablesObj;
63              if (StringUtils.isBlank(variableStr)) {
64                  return null;
65              }
66              // Parse String
67              try {
68                  return objectMapper.readValue(variableStr, Map.class);
69              } catch (IOException e) {
70                  throw new SumarisTechnicalException(ErrorCodes.INVALID_QUERY_VARIABLES, e);
71              }
72          } else if (variablesObj instanceof Map) {
73              return (Map<String, Object>) variablesObj;
74          }
75          else {
76              throw new SumarisTechnicalException(ErrorCodes.INVALID_QUERY_VARIABLES, "Unable to read param [variables] from the GraphQL request");
77          }
78      }
79  
80      public static Map<String, Object> processExecutionResult(ExecutionResult executionResult) {
81          if (CollectionUtils.isEmpty(executionResult.getErrors())) return executionResult.toSpecification();
82  
83          Map<String, Object> specifications = Maps.newHashMap();
84          specifications.putAll(executionResult.toSpecification());
85  
86          List<Map<String, Object>> errors = Lists.newArrayList();
87          for (GraphQLError error: executionResult.getErrors()) {
88              error = processGraphQLError(error);
89              Map<String, Object> newError = Maps.newLinkedHashMap();
90              newError.put("message", error.getMessage());
91              newError.put("locations", error.getLocations());
92              newError.put("path", error.getPath());
93              errors.add(newError);
94          }
95          specifications.put("errors", errors);
96  
97          return specifications;
98      }
99  
100     public static Map<String, Object> processError(Throwable throwable) {
101 
102         Map<String, Object> payload = Maps.newHashMap();
103         List<Map<String, Object>> errors = Lists.newArrayList();
104 
105         Throwable cause = getSqlExceptionOrRootCause(throwable);
106         GraphQLError error = tryCreateGraphQLError(cause);
107         if (error == null) {
108             error = new GenericGraphQLError(cause.getMessage());
109         }
110 
111         Map<String, Object> newError = Maps.newLinkedHashMap();
112         newError.put("message", error.getMessage());
113         newError.put("locations", error.getLocations());
114         newError.put("path", error.getPath());
115         errors.add(newError);
116 
117         payload.put("errors", errors);
118 
119         return payload;
120     }
121 
122     public static GraphQLError processGraphQLError(final GraphQLError error) {
123         if (error instanceof ExceptionWhileDataFetching) {
124             ExceptionWhileDataFetching exError = (ExceptionWhileDataFetching) error;
125             Throwable baseException = getSqlExceptionOrRootCause(exError.getException());
126 
127             // try to create it from the exception
128             GraphQLError result = tryCreateGraphQLError(baseException);
129             if (result != null) return result;
130 
131             return new ExceptionWhileDataFetching(ExecutionPath.fromList(exError.getPath()),
132                     new GraphQLException(baseException.getMessage()),
133                     exError.getLocations().get(0));
134         }
135         return error;
136     }
137 
138     public static GraphQLError tryCreateGraphQLError(final Throwable error) {
139         Throwable cause = getSqlExceptionOrRootCause(error);
140         // Sumaris exceptions
141         if (cause instanceof SumarisBusinessException) {
142             SumarisBusinessExceptionmaris/core/exception/SumarisBusinessException.html#SumarisBusinessException">SumarisBusinessException exception = (SumarisBusinessException) cause;
143             return new AbortExecutionException(toJsonErrorString(exception.getCode(), error.getMessage()));
144         }
145         else if (cause instanceof SumarisTechnicalException) {
146             SumarisTechnicalExceptionaris/core/exception/SumarisTechnicalException.html#SumarisTechnicalException">SumarisTechnicalException exception = (SumarisTechnicalException) cause;
147             return new AbortExecutionException(toJsonErrorString(exception.getCode(), error.getMessage()));
148         }
149 
150         // SQL exceptions
151         else if (cause instanceof java.sql.SQLException) {
152             return new AbortExecutionException(toJsonErrorString(ErrorCodes.INTERNAL_ERROR, error.getMessage()));
153         }
154 
155         // Spring exceptions
156         else if (cause instanceof DataRetrievalFailureException) {
157             DataRetrievalFailureException exception = (DataRetrievalFailureException) cause;
158             return new AbortExecutionException(toJsonErrorString(ErrorCodes.NOT_FOUND, exception.getMessage()));
159         }
160 
161         // Spring Security exceptions
162         else if (cause instanceof AccessDeniedException) {
163             return new AbortExecutionException(toJsonErrorString(ErrorCodes.UNAUTHORIZED, cause.getMessage()));
164         }
165 
166         return null;
167     }
168 
169 
170     public static Throwable getSqlExceptionOrRootCause(Throwable t) {
171         if (t instanceof java.sql.SQLException) {
172             return t;
173         }
174         if (t.getCause() != null) {
175             return getSqlExceptionOrRootCause(t.getCause());
176         }
177         return t;
178     }
179 
180     public static String toJsonErrorString(int code, String message) {
181         return ErrorHelper.toJsonErrorString(code, message);
182     }
183 
184 
185 }