| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to you under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.flume.http.source; |
| |
| import java.io.BufferedReader; |
| import java.lang.reflect.Type; |
| import java.nio.charset.UnsupportedCharsetException; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import org.apache.flume.Context; |
| import org.apache.flume.Event; |
| import org.apache.flume.event.EventBuilder; |
| import org.apache.flume.event.JSONEvent; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import com.google.gson.Gson; |
| import com.google.gson.GsonBuilder; |
| import com.google.gson.JsonSyntaxException; |
| import com.google.gson.reflect.TypeToken; |
| |
| import jakarta.servlet.http.HttpServletRequest; |
| |
| /** |
| * JSONHandler for HTTPSource that accepts an array of events. |
| * |
| * This handler throws exception if the deserialization fails because of bad |
| * format or any other reason. |
| * |
| * Each event must be encoded as a map with two key-value pairs. <p> 1. headers |
| * - the key for this key-value pair is "headers". The value for this key is |
| * another map, which represent the event headers. These headers are inserted |
| * into the Flume event as is. <p> 2. body - The body is a string which |
| * represents the body of the event. The key for this key-value pair is "body". |
| * All key-value pairs are considered to be headers. An example: <p> [{"headers" |
| * : {"a":"b", "c":"d"},"body": "random_body"}, {"headers" : {"e": "f"},"body": |
| * "random_body2"}] <p> would be interpreted as the following two flume events: |
| * <p> * Event with body: "random_body" (in UTF-8/UTF-16/UTF-32 encoded bytes) |
| * and headers : (a:b, c:d) <p> * |
| * Event with body: "random_body2" (in UTF-8/UTF-16/UTF-32 encoded bytes) and |
| * headers : (e:f) <p> |
| * |
| * The charset of the body is read from the request and used. If no charset is |
| * set in the request, then the charset is assumed to be JSON's default - UTF-8. |
| * The JSON handler supports UTF-8, UTF-16 and UTF-32. |
| * |
| * To set the charset, the request must have content type specified as |
| * "application/json; charset=UTF-8" (replace UTF-8 with UTF-16 or UTF-32 as |
| * required). |
| * |
| * One way to create an event in the format expected by this handler, is to |
| * use {@linkplain JSONEvent} and use {@linkplain Gson} to create the JSON |
| * string using the |
| * {@linkplain Gson#toJson(Object, Type) } |
| * method. The type token to pass as the 2nd argument of this method |
| * for list of events can be created by: <p> |
| * {@code |
| * Type type = new TypeToken<List<JSONEvent>>() {}.getType(); |
| * } |
| */ |
| public class JSONHandler implements HTTPSourceHandler { |
| |
| private static final Logger LOG = LoggerFactory.getLogger(JSONHandler.class); |
| private final Type listType = new TypeToken<List<JSONEvent>>() {}.getType(); |
| private final Gson gson; |
| |
| public JSONHandler() { |
| gson = new GsonBuilder().disableHtmlEscaping().create(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public List<Event> getEvents(HttpServletRequest request) throws Exception { |
| BufferedReader reader = request.getReader(); |
| String charset = request.getCharacterEncoding(); |
| // UTF-8 is default for JSON. If no charset is specified, UTF-8 is to |
| // be assumed. |
| if (charset == null) { |
| LOG.debug("Charset is null, default charset of UTF-8 will be used."); |
| charset = "UTF-8"; |
| } else if (!(charset.equalsIgnoreCase("utf-8") |
| || charset.equalsIgnoreCase("utf-16") |
| || charset.equalsIgnoreCase("utf-32"))) { |
| LOG.error( |
| "Unsupported character set in request {}. " |
| + "JSON handler supports UTF-8, " |
| + "UTF-16 and UTF-32 only.", |
| charset); |
| throw new UnsupportedCharsetException("JSON handler supports UTF-8, " + "UTF-16 and UTF-32 only."); |
| } |
| |
| /* |
| * Gson throws Exception if the data is not parseable to JSON. |
| * Need not catch it since the source will catch it and return error. |
| */ |
| List<Event> eventList = new ArrayList<Event>(0); |
| try { |
| eventList = gson.fromJson(reader, listType); |
| } catch (JsonSyntaxException ex) { |
| throw new HTTPBadRequestException("Request has invalid JSON Syntax.", ex); |
| } |
| |
| for (Event e : eventList) { |
| ((JSONEvent) e).setCharset(charset); |
| } |
| return getSimpleEvents(eventList); |
| } |
| |
| @Override |
| public void configure(Context context) {} |
| |
| private List<Event> getSimpleEvents(List<Event> events) { |
| List<Event> newEvents = new ArrayList<Event>(events.size()); |
| for (Event e : events) { |
| newEvents.add(EventBuilder.withBody(e.getBody(), e.getHeaders())); |
| } |
| return newEvents; |
| } |
| } |