docs: public void handleEvent(HttpServletRequest request) throws GitLabApiException {
  
          String eventName = request.getHeader("X-Gitlab-Event");
          if (eventName == null || eventName.trim().isEmpty()) {
              String message = "X-Gitlab-Event header is missing!";
              LOGGER.warning(message);
              return;
          }
  
          if (!isValidSecretToken(request)) {
              String message = "X-Gitlab-Token mismatch!";
              LOGGER.warning(message);
              throw new GitLabApiException(message);
          }
  
          LOGGER.info("handleEvent: X-Gitlab-Event=" + eventName);
          if (!SYSTEM_HOOK_EVENT.equals(eventName)) {
              String message = "Unsupported X-Gitlab-Event, event Name=" + eventName;
              LOGGER.warning(message);
              throw new GitLabApiException(message);
          }
  
          // Get the JSON as a JsonNode tree.  We do not directly unmarshal the input as special handling must
          // be done for "merge_request" events.
          JsonNode tree;
          try {
  
              if (LOGGER.isLoggable(Level.FINE)) {
                  LOGGER.fine(HttpRequestUtils.getShortRequestDump("System Hook", true, request));
                  String postData = HttpRequestUtils.getPostDataAsString(request);
                  LOGGER.fine("Raw POST data:\n" + postData);
                  tree = jacksonJson.readTree(postData);
              } else {
                  InputStreamReader reader = new InputStreamReader(request.getInputStream());
                  tree = jacksonJson.readTree(reader);
              }
  
          } catch (Exception e) {
              LOGGER.warning("Error reading JSON data, exception=" +
                      e.getClass().getSimpleName() + ", error=" + e.getMessage());
              throw new GitLabApiException(e);
          }
  
          // NOTE: This is a hack based on the GitLab documentation and actual content of the "merge_request" event
          // showing that the "event_name" property is missing from the merge_request system hook event.  The hack is
          // to inject the "event_name" node so that the polymorphic deserialization of a SystemHookEvent works correctly
          // when the system hook event is a "merge_request" event.
          if (!tree.has("event_name") && tree.has("object_kind")) {
  
              String objectKind = tree.get("object_kind").asText();
              if (MergeRequestSystemHookEvent.MERGE_REQUEST_EVENT.equals(objectKind)) {
                  ObjectNode node = (ObjectNode)tree;
                  node.put("event_name", MergeRequestSystemHookEvent.MERGE_REQUEST_EVENT);
              } else {
                  String message = "Unsupported object_kind for system hook event, object_kind=" + objectKind;
                  LOGGER.warning(message);
                  throw new GitLabApiException(message);
              }
          }
  
          // Unmarshal the tree to a concrete instance of a SystemHookEvent and fire the event to any listeners
          try {
  
              SystemHookEvent event = jacksonJson.unmarshal(SystemHookEvent.class, tree);
              if (LOGGER.isLoggable(Level.FINE)) {
                  LOGGER.fine(event.getEventName() + "\n" + jacksonJson.marshal(event) + "\n");
              }
  
              StringBuffer requestUrl = request.getRequestURL();
              event.setRequestUrl(requestUrl != null ? requestUrl.toString() : null);
              event.setRequestQueryString(request.getQueryString());
              fireEvent(event);
  
          } catch (Exception e) {
              LOGGER.warning("Error processing JSON data, exception=" +
                      e.getClass().getSimpleName() + ", error=" + e.getMessage());
              throw new GitLabApiException(e);
          }
      }