From 9c1fb7e002ccd12fefe231d0bcada58e2d801cb3 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Thu, 2 May 2024 14:33:32 +0200 Subject: [PATCH] Add support for handling JSON input. --- org.jgrapes.io/build.gradle | 1 + .../src/org/jgrapes/io/util/JsonReader.java | 74 +++++++++++++++++++ .../org/jgrapes/io/util/events/DataInput.java | 50 +++++++++++++ .../io/util/events/JsonParsingError.java | 37 ++++++++++ .../jgrapes/io/util/events/package-info.java | 5 ++ .../org/jgrapes/io/test/JsonReaderTests.java | 69 +++++++++++++++++ 6 files changed, 236 insertions(+) create mode 100644 org.jgrapes.io/src/org/jgrapes/io/util/JsonReader.java create mode 100644 org.jgrapes.io/src/org/jgrapes/io/util/events/DataInput.java create mode 100644 org.jgrapes.io/src/org/jgrapes/io/util/events/JsonParsingError.java create mode 100644 org.jgrapes.io/src/org/jgrapes/io/util/events/package-info.java create mode 100644 org.jgrapes.io/test/org/jgrapes/io/test/JsonReaderTests.java diff --git a/org.jgrapes.io/build.gradle b/org.jgrapes.io/build.gradle index d6ec31c54db..d726713eb0c 100644 --- a/org.jgrapes.io/build.gradle +++ b/org.jgrapes.io/build.gradle @@ -1,6 +1,7 @@ dependencies { api project(':org.jgrapes.core') api project(':org.jgrapes.util') + api 'com.fasterxml.jackson.core:jackson-databind:[2.13,3)' } sourceSets { diff --git a/org.jgrapes.io/src/org/jgrapes/io/util/JsonReader.java b/org.jgrapes.io/src/org/jgrapes/io/util/JsonReader.java new file mode 100644 index 00000000000..1c82c392249 --- /dev/null +++ b/org.jgrapes.io/src/org/jgrapes/io/util/JsonReader.java @@ -0,0 +1,74 @@ +/* + * JGrapes Event Driven Framework + * Copyright (C) 2022 Michael N. Lipp + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see . + */ + +package org.jgrapes.io.util; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.jgrapes.core.Channel; +import org.jgrapes.core.EventPipeline; +import org.jgrapes.io.util.events.DataInput; +import org.jgrapes.io.util.events.JsonParsingError; + +/** + * A {@link ManagedBufferStreamer} that feeds the data to a JSON parser. + * When the data is fully parsed, it is made available by firing a + * {@link DataInput} event. + * + * @since 2.8 + */ +@SuppressWarnings("PMD.DataflowAnomalyAnalysis") +public class JsonReader extends ManagedBufferStreamer { + + /** + * Instantiates a new JSON reader. + * + * @param the result data type + * @param mapper the mapper + * @param resultType the result type + * @param pipeline the pipeline to use for sending the + * {@link DataInput} event + * @param channel the channel to use for sending the + * {@link DataInput} event + */ + public JsonReader(ObjectMapper mapper, Class resultType, + EventPipeline pipeline, Channel channel) { + super(r -> { + try { + pipeline.fire(new DataInput(mapper.readValue(r, resultType)), + channel); + } catch (Exception e) { + pipeline.fire(new JsonParsingError(e), channel); + } + }); + } + + /** + * Instantiates a new JSON reader that uses a default object mapper. + * + * @param the result data type + * @param resultType the result type + * @param pipeline the pipeline to use for sending the + * {@link DataInput} event + * @param channel the channel to use for sending the + * {@link DataInput} event + */ + public JsonReader(Class resultType, EventPipeline pipeline, + Channel channel) { + this(new ObjectMapper(), resultType, pipeline, channel); + } +} diff --git a/org.jgrapes.io/src/org/jgrapes/io/util/events/DataInput.java b/org.jgrapes.io/src/org/jgrapes/io/util/events/DataInput.java new file mode 100644 index 00000000000..6e15328d9bf --- /dev/null +++ b/org.jgrapes.io/src/org/jgrapes/io/util/events/DataInput.java @@ -0,0 +1,50 @@ +/* + * JGrapes Event Driven Framework + * Copyright (C) 2024 Michael N. Lipp + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with this program; if not, see . + */ + +package org.jgrapes.io.util.events; + +import org.jgrapes.core.Event; + +/** + * Signals that some data is available for handling. + * + * @param + */ +public class DataInput extends Event { + + private T data; + + /** + * Instantiates a new data input providing the given data. + * + * @param data the data + */ + public DataInput(T data) { + this.data = data; + } + + /** + * Gets the data. + * + * @return the data + */ + public T data() { + return data; + } + +} diff --git a/org.jgrapes.io/src/org/jgrapes/io/util/events/JsonParsingError.java b/org.jgrapes.io/src/org/jgrapes/io/util/events/JsonParsingError.java new file mode 100644 index 00000000000..584802fecc5 --- /dev/null +++ b/org.jgrapes.io/src/org/jgrapes/io/util/events/JsonParsingError.java @@ -0,0 +1,37 @@ +/* + * JGrapes Event Driven Framework + * Copyright (C) 2024 Michael N. Lipp + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License along + * with this program; if not, see . + */ + +package org.jgrapes.io.util.events; + +import org.jgrapes.core.events.Error; + +/** + * Signals an error that occurred while parsing JSON. + */ +public class JsonParsingError extends Error { + + /** + * Instantiates a new JSON parsing error. + * + * @param throwable the throwable + */ + public JsonParsingError(Throwable throwable) { + super(null, throwable); + } + +} diff --git a/org.jgrapes.io/src/org/jgrapes/io/util/events/package-info.java b/org.jgrapes.io/src/org/jgrapes/io/util/events/package-info.java new file mode 100644 index 00000000000..0169ec9f7b5 --- /dev/null +++ b/org.jgrapes.io/src/org/jgrapes/io/util/events/package-info.java @@ -0,0 +1,5 @@ +/** + * Events associated with classes from {@link org.jgrapes.io.util}. + */ + +package org.jgrapes.io.util.events; \ No newline at end of file diff --git a/org.jgrapes.io/test/org/jgrapes/io/test/JsonReaderTests.java b/org.jgrapes.io/test/org/jgrapes/io/test/JsonReaderTests.java new file mode 100644 index 00000000000..768c45abbeb --- /dev/null +++ b/org.jgrapes.io/test/org/jgrapes/io/test/JsonReaderTests.java @@ -0,0 +1,69 @@ +package org.jgrapes.io.test; + +import java.io.IOException; +import java.nio.CharBuffer; +import java.util.Map; +import org.jgrapes.core.Channel; +import org.jgrapes.core.Component; +import org.jgrapes.core.Components; +import org.jgrapes.core.annotation.Handler; +import org.jgrapes.core.events.Started; +import org.jgrapes.io.util.JsonReader; +import org.jgrapes.io.util.ManagedBuffer; +import org.jgrapes.io.util.ManagedBufferPool; +import org.jgrapes.io.util.events.DataInput; +import static org.junit.Assert.*; +import org.junit.Test; + +public class JsonReaderTests { + + public class TestApp extends Component { + + @Handler + public void onStarted(Started event, Channel channel) + throws InterruptedException, IOException { + registerAsGenerator(); + var charBufferPool = new ManagedBufferPool<>(ManagedBuffer::new, + () -> CharBuffer.allocate(4096), 2).setName("Test"); + + // Create reader + JsonReader rdr + = new JsonReader(Object.class, activeEventPipeline(), this); + + // Feed + var data = charBufferPool.acquire(); + data.backingBuffer().append("{\"hello\": \"world\"}"); + data.backingBuffer().flip(); + rdr.feed(data); + data.unlockBuffer(); + + // End of feed + rdr.feed(null); + } + + @Handler + public void onJson(DataInput event, Channel channel) { + result = event.data(); + unregisterAsGenerator(); + } + } + + private Object result; + + @SuppressWarnings("unchecked") + @Test(timeout = 1000) + public void test() throws InterruptedException, IOException { + + var app = new TestApp(); + Components.start(app); + + while (result == null) { + Thread.sleep(10); + } + assertTrue(result instanceof Map); + assertEquals("world", ((Map) result).get("hello")); + + Components.awaitExhaustion(); + } + +}