View Javadoc
1   /*
2    * This file is part of Waarp Project (named also Waarp or GG).
3    *
4    *  Copyright (c) 2019, Waarp SAS, and individual contributors by the @author
5    *  tags. See the COPYRIGHT.txt in the distribution for a full listing of
6    * individual contributors.
7    *
8    *  All Waarp Project is free software: you can redistribute it and/or
9    * modify it under the terms of the GNU General Public License as published by
10   * the Free Software Foundation, either version 3 of the License, or (at your
11   * option) any later version.
12   *
13   * Waarp is distributed in the hope that it will be useful, but WITHOUT ANY
14   * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
15   * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
16   *
17   *  You should have received a copy of the GNU General Public License along with
18   * Waarp . If not, see <http://www.gnu.org/licenses/>.
19   */
20  package org.waarp.gateway.kernel.rest;
21  
22  import com.fasterxml.jackson.databind.node.ArrayNode;
23  import io.netty.buffer.ByteBuf;
24  import io.netty.buffer.Unpooled;
25  import io.netty.channel.ChannelFuture;
26  import io.netty.channel.ChannelHandlerContext;
27  import io.netty.handler.codec.http.HttpHeaderNames;
28  import io.netty.handler.codec.http.HttpResponse;
29  import io.netty.handler.codec.http.HttpResponseStatus;
30  import io.netty.handler.codec.http.multipart.FileUpload;
31  import org.waarp.common.logging.SysErrLogger;
32  import org.waarp.common.logging.WaarpLogger;
33  import org.waarp.common.logging.WaarpLoggerFactory;
34  import org.waarp.common.utility.WaarpStringUtils;
35  import org.waarp.gateway.kernel.exception.HttpForbiddenRequestException;
36  import org.waarp.gateway.kernel.exception.HttpIncorrectRequestException;
37  import org.waarp.gateway.kernel.exception.HttpInvalidAuthenticationException;
38  import org.waarp.gateway.kernel.exception.HttpMethodNotAllowedRequestException;
39  import org.waarp.gateway.kernel.exception.HttpNotFoundRequestException;
40  import org.waarp.gateway.kernel.rest.DataModelRestMethodHandler.COMMAND_TYPE;
41  import org.waarp.gateway.kernel.rest.HttpRestHandler.METHOD;
42  
43  import java.util.Collections;
44  import java.util.HashSet;
45  import java.util.Set;
46  
47  /**
48   * Rest Method handler (used by Http Rest Handler)
49   */
50  public abstract class RestMethodHandler {
51    /**
52     * Internal Logger
53     */
54    private static final WaarpLogger logger =
55        WaarpLoggerFactory.getLogger(RestMethodHandler.class);
56    private static final METHOD[] METHOD_0_LENGTH = new METHOD[0];
57  
58    protected final String name;
59    protected final String path;
60    protected final Set<METHOD> methods;
61    protected final boolean isBodyJsonDecode;
62    protected final RestConfiguration restConfiguration;
63  
64    /**
65     * @param name name associated with this Method Handler (to enable
66     *     some
67     *     HashMap or Enum
68     *     classification)
69     * @param path associated base Path
70     * @param isBodyJsonDecode Is this method Handler using a Json as
71     *     Body
72     * @param config the associated configuration
73     * @param method the associated methods
74     */
75    protected RestMethodHandler(final String name, final String path,
76                                final boolean isBodyJsonDecode,
77                                final RestConfiguration config,
78                                final METHOD... method) {
79      this.name = name;
80      this.path = path;
81      methods = new HashSet<HttpRestHandler.METHOD>();
82      setMethods(method);
83      setMethods(METHOD.OPTIONS);
84      this.isBodyJsonDecode = isBodyJsonDecode;
85      restConfiguration = config;
86    }
87  
88    protected final void setMethods(final METHOD... method) {
89      Collections.addAll(methods, method);
90    }
91  
92    /**
93     * Will assign the intersection of both set of Methods
94     *
95     * @param selectedMethods the selected Methods among available
96     * @param validMethod the validMethod for this handler
97     */
98    protected final void setIntersectionMethods(final METHOD[] selectedMethods,
99                                                final METHOD... validMethod) {
100     final Set<METHOD> set = new HashSet<METHOD>();
101     Collections.addAll(set, validMethod);
102     final Set<METHOD> set2 = new HashSet<METHOD>();
103     Collections.addAll(set2, selectedMethods);
104     set.retainAll(set2);
105     final METHOD[] methodsToSet = set.toArray(METHOD_0_LENGTH);
106     setMethods(methodsToSet);
107   }
108 
109   public final String getName() {
110     return name;
111   }
112 
113   public final String getPath() {
114     return path;
115   }
116 
117   /**
118    * @param method
119    *
120    * @return True if the Method is valid for this Handler
121    */
122   public final boolean isMethodIncluded(final METHOD method) {
123     return methods.contains(method);
124   }
125 
126   /**
127    * Check the session (arguments, result) vs handler correctness, called
128    * before
129    * any BODY elements but after URI
130    * and HEADER.
131    *
132    * @param handler
133    * @param arguments
134    * @param result
135    *
136    * @throws HttpForbiddenRequestException
137    */
138   public abstract void checkHandlerSessionCorrectness(HttpRestHandler handler,
139                                                       RestArgument arguments,
140                                                       RestArgument result)
141       throws HttpForbiddenRequestException;
142 
143   /**
144    * Get a new Http Uploaded File from BODY
145    *
146    * @param handler
147    * @param data
148    * @param arguments
149    * @param result
150    *
151    * @throws HttpIncorrectRequestException
152    */
153   public abstract void getFileUpload(HttpRestHandler handler, FileUpload data,
154                                      RestArgument arguments,
155                                      RestArgument result)
156       throws HttpIncorrectRequestException;
157 
158   /**
159    * Get data from BODY (supposedly a Json)
160    *
161    * @param handler
162    * @param body
163    * @param arguments
164    * @param result
165    *
166    * @return the object related to BODY decoding
167    *
168    * @throws HttpIncorrectRequestException
169    */
170   public abstract Object getBody(HttpRestHandler handler, ByteBuf body,
171                                  RestArgument arguments, RestArgument result)
172       throws HttpIncorrectRequestException;
173 
174   /**
175    * Called when all Data were passed to the handler
176    *
177    * @param handler
178    * @param arguments
179    * @param result
180    * @param body
181    *
182    * @throws HttpIncorrectRequestException
183    * @throws HttpNotFoundRequestException
184    */
185   public abstract void endParsingRequest(HttpRestHandler handler,
186                                          RestArgument arguments,
187                                          RestArgument result, Object body)
188       throws HttpIncorrectRequestException, HttpInvalidAuthenticationException,
189              HttpNotFoundRequestException;
190 
191   /**
192    * Called when an exception occurs
193    *
194    * @param handler
195    * @param arguments
196    * @param result
197    * @param body
198    * @param exception
199    *
200    * @return the status to used in sendReponse
201    */
202   public HttpResponseStatus handleException(final HttpRestHandler handler,
203                                             final RestArgument arguments,
204                                             final RestArgument result,
205                                             final Object body,
206                                             final Exception exception) {
207     if (exception instanceof HttpInvalidAuthenticationException) {
208       result.setResult(HttpResponseStatus.UNAUTHORIZED);
209       return HttpResponseStatus.UNAUTHORIZED;
210     } else if (exception instanceof HttpForbiddenRequestException) {
211       result.setResult(HttpResponseStatus.FORBIDDEN);
212       return HttpResponseStatus.FORBIDDEN;
213     } else if (exception instanceof HttpIncorrectRequestException) {
214       result.setResult(HttpResponseStatus.BAD_REQUEST);
215       return HttpResponseStatus.BAD_REQUEST;
216     } else if (exception instanceof HttpMethodNotAllowedRequestException) {
217       result.setResult(HttpResponseStatus.METHOD_NOT_ALLOWED);
218       return HttpResponseStatus.METHOD_NOT_ALLOWED;
219     } else if (exception instanceof HttpNotFoundRequestException) {
220       result.setResult(HttpResponseStatus.NOT_FOUND);
221       return HttpResponseStatus.NOT_FOUND;
222     } else {
223       result.setResult(HttpResponseStatus.INTERNAL_SERVER_ERROR);
224       return HttpResponseStatus.INTERNAL_SERVER_ERROR;
225     }
226   }
227 
228   /**
229    * Send a response (correct or not)
230    *
231    * @param handler
232    * @param ctx
233    * @param arguments
234    * @param result
235    * @param body
236    * @param status
237    *
238    * @return The ChannelFuture if this response will need the channel to be
239    *     closed, else null
240    */
241   public abstract ChannelFuture sendResponse(HttpRestHandler handler,
242                                              ChannelHandlerContext ctx,
243                                              RestArgument arguments,
244                                              RestArgument result, Object body,
245                                              HttpResponseStatus status);
246 
247   protected final ChannelFuture sendOptionsResponse(
248       final HttpRestHandler handler, final ChannelHandlerContext ctx,
249       final RestArgument result, final HttpResponseStatus status) {
250     final String list = result.getAllowOption();
251     final String answer = result.toString();
252     final ByteBuf buffer =
253         Unpooled.wrappedBuffer(answer.getBytes(WaarpStringUtils.UTF8));
254     final HttpResponse response = handler.getResponse(buffer);
255     if (status == HttpResponseStatus.UNAUTHORIZED) {
256       return ctx.writeAndFlush(response);
257     }
258     response.headers().add(HttpHeaderNames.CONTENT_TYPE, "application/json");
259     response.headers().add(HttpHeaderNames.REFERER, handler.getRequest().uri());
260     response.headers().add(HttpHeaderNames.ALLOW, list);
261     logger.debug("Msg ready");
262     final ChannelFuture future = ctx.writeAndFlush(response);
263     if (handler.isWillClose()) {
264       SysErrLogger.FAKE_LOGGER.sysout(
265           "Will close session in RestMethodHandler");
266       return future;
267     }
268     return null;
269   }
270 
271   /**
272    * Options command that all handler should implement
273    *
274    * @param handler
275    * @param arguments
276    * @param result
277    */
278   protected void optionsCommand(final HttpRestHandler handler,
279                                 final RestArgument arguments,
280                                 final RestArgument result) {
281     result.setCommand(COMMAND_TYPE.OPTIONS);
282     final METHOD[] realmethods = METHOD.values();
283     final boolean[] allMethods = new boolean[realmethods.length];
284     for (final METHOD methoditem : methods) {
285       allMethods[methoditem.ordinal()] = true;
286     }
287     StringBuilder allow = null;
288     for (int i = 0; i < allMethods.length; i++) {
289       if (allMethods[i]) {
290         if (allow == null) {
291           allow = new StringBuilder(realmethods[i].name());
292         } else {
293           allow.append(',').append(realmethods[i].name());
294         }
295       }
296     }
297     if (allow != null) {
298       result.addOptions(allow.toString(), path, getDetailedAllow());
299     }
300   }
301 
302   /**
303    * @return the detail of the method handler
304    */
305   protected abstract ArrayNode getDetailedAllow();
306 
307   /**
308    * @return the isBodyJson
309    */
310   public final boolean isBodyJsonDecoded() {
311     return isBodyJsonDecode;
312   }
313 }