All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.inverno.mod.boot.converter.JacksonStringConverter Maven / Gradle / Ivy

There is a newer version: 1.11.0
Show newest version
/*
 * Copyright 2022 Jeremy KUHN
 *
 * Licensed 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 io.inverno.mod.boot.converter;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.JsonStreamContext;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.core.async.ByteArrayFeeder;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
import com.fasterxml.jackson.databind.deser.DefaultDeserializationContext;
import com.fasterxml.jackson.databind.util.TokenBuffer;
import io.inverno.core.annotation.Bean;
import io.inverno.core.annotation.Provide;
import io.inverno.mod.base.converter.Converter;
import io.inverno.mod.base.converter.ConverterException;
import io.inverno.mod.base.converter.JoinableEncoder;
import io.inverno.mod.base.converter.ReactiveConverter;
import io.inverno.mod.base.converter.SplittableDecoder;
import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import org.reactivestreams.Publisher;
import reactor.core.Exceptions;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

/**
 * 

* JSON String to Object converter backed by an {@link ObjectMapper}. *

* * @author Jeremy Kuhn * @since 1.5 * * @see Converter * @see ReactiveConverter * @see ObjectMapper */ @Bean( name = "jsonStringConverter" ) public class JacksonStringConverter implements @Provide ReactiveConverter, SplittableDecoder, JoinableEncoder { private static final String LAST_CHUNK = new String(); private static final Mono LAST_CHUNK_PUBLISHER = Mono.just(LAST_CHUNK); private final ObjectMapper mapper; /** *

* Creates a JSON String converter. *

* * @param mapper a Jackson object mapper */ public JacksonStringConverter(ObjectMapper mapper) { this.mapper = mapper; } @Override public Publisher encodeOne(Mono value) { return value.map(t -> this.encode(t)); } @Override public Publisher encodeOne(Mono value, Class type) { return this.encodeOne(value, (Type)type); } @Override public Publisher encodeOne(Mono value, Type type) { return value.map(t -> this.encode(t, type)); } @Override public Publisher encodeMany(Flux value) { return value.map(t -> this.encode(t)); } @Override public Publisher encodeMany(Flux value, Class type) { return this.encodeMany(value, (Type)type); } @Override public Publisher encodeMany(Flux value, Type type) { return value.map(t -> this.encode(t, type)); } @Override public String encode(T value) throws ConverterException { try { return this.mapper.writeValueAsString(value); } catch (JsonProcessingException e) { throw new ConverterException("Error encoding value", e); } } @Override public String encode(T value, Class type) throws ConverterException { return this.encode(value, (Type)type); } @Override public String encode(T value, Type type) throws ConverterException { try { return this.mapper.writerFor(this.mapper.constructType(type)).writeValueAsString(value); } catch (JsonProcessingException e) { throw new ConverterException("Error encoding value", e); } } @Override public String encodeList(List value) throws ConverterException { return this.encode(value); } @Override public String encodeList(List value, Class type) throws ConverterException { return this.encode(value); } @Override public String encodeList(List value, Type type) throws ConverterException { return this.encode(value); } @Override public String encodeSet(Set value) throws ConverterException { return this.encode(value); } @Override public String encodeSet(Set value, Class type) throws ConverterException { return this.encode(value); } @Override public String encodeSet(Set value, Type type) throws ConverterException { return this.encode(value); } @Override public String encodeArray(T[] value) throws ConverterException { return this.encode(value); } @Override public String encodeArray(T[] value, Class type) throws ConverterException { return this.encode(value); } @Override public String encodeArray(T[] value, Type type) throws ConverterException { return this.encode(value); } @Override public Mono decodeOne(Publisher value, Class type) { return this.decodeMany(value, type, false).single(); } @Override public Mono decodeOne(Publisher value, Type type) { return this.decodeMany(value, type, false).single(); } @Override public Flux decodeMany(Publisher value, Class type) { return this.decodeMany(value, type, true); } @Override public Flux decodeMany(Publisher value, Type type) { return this.decodeMany(value, type, true); } private Flux decodeMany(Publisher value, Type type, boolean scanRootArray) { // Performance wise, this might not be ideal because creating a flux is resource consuming // TODO assess performance and see whether it is interesting to optimize this return Flux.concat(value, LAST_CHUNK_PUBLISHER).scanWith( () -> { try { return new ObjectScanner(type, this.mapper, scanRootArray); } catch(IOException e) { throw Exceptions.propagate(e); } }, (scanner, chunk) -> { try { // This is on purpose we want to compare the instance so we can differentiate it from an empty chunk if(chunk == LAST_CHUNK) { scanner.endOfInput(); } else { scanner.feedInput(chunk); } return scanner; } catch (IOException e) { throw Exceptions.propagate(e); } } ) .skip(1) .concatMap(scanner -> { try { List objects = new LinkedList<>(); T object; while( (object = scanner.nextObject()) != null) { objects.add(object); } return Flux.fromIterable(objects); } catch (IOException e) { throw Exceptions.propagate(e); } }); } @Override public T decode(String value, Class type) throws ConverterException { return this.decode(value, (Type)type); } @Override public T decode(String value, Type type) throws ConverterException { try { return this.mapper.readerFor(this.mapper.constructType(type)).readValue(value); } catch (IOException e) { throw new ConverterException("Error decoding value", e); } } @Override public List decodeToList(String value, Class type) { return this.decodeToList(value, (Type)type); } @Override public List decodeToList(String value, Type type) { try { ObjectScanner scanner = new ObjectScanner<>(type, this.mapper, true); scanner.feedInput(value); scanner.endOfInput(); List objects = new LinkedList<>(); T object; while( (object = scanner.nextObject()) != null) { objects.add(object); } return objects; } catch (IOException e) { throw new ConverterException("Error decoding value", e); } } @Override public Set decodeToSet(String value, Class type) { return this.decodeToSet(value, (Type)type); } @Override public Set decodeToSet(String value, Type type) { try { ObjectScanner scanner = new ObjectScanner<>(type, this.mapper, true); scanner.feedInput(value); scanner.endOfInput(); Set objects = new HashSet<>(); T object; while( (object = scanner.nextObject()) != null) { objects.add(object); } return objects; } catch (IOException e) { throw new ConverterException("Error decoding value", e); } } @SuppressWarnings("unchecked") @Override public T[] decodeToArray(String value, Class type) { List objects = this.decodeToList(value, type); return objects.toArray((T[])Array.newInstance(type, objects.size())); } @SuppressWarnings("unchecked") @Override public T[] decodeToArray(String value, Type type) { List objects = this.decodeToList(value, type); if(type instanceof Class) { return objects.toArray((T[]) Array.newInstance((Class)type, objects.size())); } else if(type instanceof ParameterizedType) { ParameterizedType parameterizedType = (ParameterizedType)type; return objects.toArray((T[]) Array.newInstance((Class)parameterizedType.getRawType(), objects.size())); } else { throw new ConverterException("Can't decode " + String.class.getCanonicalName() + " to array of " + type.getTypeName()); } } private static class ObjectScanner { private final Type type; private final ObjectMapper mapper; private final ObjectReader reader; private final boolean scanRootArray; private final JsonParser parser; private final ByteArrayFeeder feeder; private DeserializationContext deserializationContext; private TokenBuffer tokenBuffer; public ObjectScanner(Type type, ObjectMapper mapper, boolean scanRootArray) throws IOException { this.type = type; this.mapper = mapper; this.reader = this.mapper.readerFor(this.mapper.constructType(this.type)); this.scanRootArray = scanRootArray; this.parser = this.mapper.getFactory().createNonBlockingByteArrayParser(); this.feeder = (ByteArrayFeeder)this.parser.getNonBlockingInputFeeder(); this.deserializationContext = this.mapper.getDeserializationContext(); if (this.deserializationContext instanceof DefaultDeserializationContext) { this.deserializationContext = ((DefaultDeserializationContext) this.deserializationContext).createInstance(this.mapper.getDeserializationConfig(), this.parser, this.mapper.getInjectableValues()); } } @SuppressWarnings("unused") public ObjectScanner(Class type, ObjectMapper mapper, boolean scanRootArray) throws IOException { this((Type)type, mapper, scanRootArray); } private TokenBuffer getTokenBuffer() { if(this.tokenBuffer == null) { this.tokenBuffer = new TokenBuffer(this.parser, this.deserializationContext); this.tokenBuffer.forceUseOfBigDecimal(this.mapper.isEnabled(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS)); } return this.tokenBuffer; } public void feedInput(String chunk) throws IOException { byte[] bytes = chunk.getBytes(); this.feeder.feedInput(bytes, 0, bytes.length); } public void endOfInput() { this.feeder.endOfInput(); } public T nextObject() throws IOException { while (!this.parser.isClosed()) { JsonToken token = this.parser.nextToken(); // TODO smile value format uses null to separate document // we actually know in advanced that we are dealing with that format so maybe we can provide another scanner implementation to make things explicit if(token == null || token == JsonToken.NOT_AVAILABLE) { // end of input break; } JsonStreamContext context = this.parser.getParsingContext(); TokenBuffer currentTokenBuffer = this.getTokenBuffer(); if(this.scanRootArray && context.inArray() && context.getParent().inRoot() && (token == JsonToken.START_ARRAY || token == JsonToken.END_ARRAY)) { continue; } currentTokenBuffer.copyCurrentEvent(this.parser); if( (context.inRoot() || (this.scanRootArray && context.inArray() && context.getParent().inRoot())) && (token.isScalarValue() || token.isStructEnd())) { try { return this.reader.readValue(currentTokenBuffer.asParser()); } finally { this.tokenBuffer = null; } } } return null; } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy