/* * ***************************************************************************** * * Pentaho Data Integration * * Copyright (C) 2002-2017 by Pentaho : http://www.pentaho.com * * ******************************************************************************* * 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 org.pentaho.pdi.engine.serializers; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.deser.std.StdNodeBasedDeserializer; import com.fasterxml.jackson.databind.module.SimpleModule; import org.apache.commons.codec.binary.Base64; import org.pentaho.di.engine.api.events.DataEvent; import org.pentaho.di.engine.api.model.Row; import org.pentaho.di.engine.api.model.Rows; import org.pentaho.di.engine.api.remote.RemoteSource; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.math.BigDecimal; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; /** * Created by nbaker on 3/4/17. */ public class DataEventSerializer extends BaseSerializer<DataEvent> { public static final DateFormat DATE_TIME_INSTANCE = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss.SSS" ); public DataEventSerializer() { super( DataEvent.class ); SimpleModule module = new SimpleModule(); module.addSerializer( DataEvent.class, new JsonSerializer<DataEvent>() { @Override public void serialize( DataEvent dataEvent, JsonGenerator jsonGenerator, SerializerProvider serializerProvider ) throws IOException, JsonProcessingException { jsonGenerator.writeStartObject(); Rows rows = (Rows) dataEvent.getData(); jsonGenerator.writeStringField( "model-id", dataEvent.getSource().getId() ); jsonGenerator.writeStringField( "type", rows.getType().toString() ); jsonGenerator.writeStringField( "state", rows.getState().toString() ); jsonGenerator.writeArrayFieldStart( "rows" ); for ( Row row : rows ) { jsonGenerator.writeStartObject(); jsonGenerator.writeArrayFieldStart( "names" ); for ( String name : row.getColumnNames() ) { jsonGenerator.writeString( name ); } jsonGenerator.writeEndArray(); jsonGenerator.writeArrayFieldStart( "objects" ); for ( Object obj : row.getObjects() ) { jsonGenerator.writeStartObject(); if ( obj == null ) { jsonGenerator.writeStringField( "type", "Null" ); jsonGenerator.writeEndObject(); continue; } switch ( obj.getClass().getSimpleName() ) { case "String": jsonGenerator.writeStringField( "type", "String" ); jsonGenerator.writeStringField( "obj", obj.toString() ); break; case "Date": jsonGenerator.writeStringField( "type", "Date" ); jsonGenerator.writeStringField( "obj", DATE_TIME_INSTANCE.format( (Date) obj ) ); break; case "Integer": jsonGenerator.writeStringField( "type", "Integer" ); jsonGenerator.writeNumberField( "obj", (Integer) obj ); break; case "Long": jsonGenerator.writeStringField( "type", "Long" ); jsonGenerator.writeNumberField( "obj", (Long) obj ); break; case "Double": jsonGenerator.writeStringField( "type", "Double" ); jsonGenerator.writeNumberField( "obj", (Double) obj ); break; case "BigDecimal": jsonGenerator.writeStringField( "type", "BigDecimal" ); jsonGenerator.writeStringField( "obj", obj.toString() ); break; case "Boolean": jsonGenerator.writeStringField( "type", "Boolean" ); jsonGenerator.writeBooleanField( "obj", (Boolean) obj ); break; case "byte[]": jsonGenerator.writeStringField( "type", "byte[]" ); jsonGenerator.writeStringField( "obj", new String( ( (byte[]) obj ), "UTF-8" ) ); break; default: if ( obj instanceof Serializable ) { jsonGenerator.writeStringField( "type", "Object" ); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream( outputStream ); objectOutputStream.writeObject( obj ); objectOutputStream.close(); outputStream.close(); byte[] bytes = outputStream.toByteArray(); jsonGenerator.writeStringField( "obj", Base64.encodeBase64String( bytes ) ); } } jsonGenerator.writeEndObject(); } jsonGenerator.writeEndArray(); jsonGenerator.writeEndObject(); } jsonGenerator.writeEndArray(); jsonGenerator.writeEndObject(); } } ); module.addDeserializer( DataEvent.class, new StdNodeBasedDeserializer<DataEvent>( DataEvent.class ) { @Override public DataEvent convert( JsonNode jsonNode, DeserializationContext deserializationContext ) throws IOException { Rows.TYPE type = Rows.TYPE.valueOf( jsonNode.get( "type" ).asText() ); Rows.STATE state = Rows.STATE.valueOf( jsonNode.get( "state" ).asText() ); List<Row> rows = new ArrayList<>(); JsonNode json_rows = jsonNode.get( "rows" ); for ( JsonNode row : json_rows ) { List<Class> types = new ArrayList<>(); List<String> names = new ArrayList<>(); for ( JsonNode name : row.get( "names" ) ) { names.add( name.asText() ); } List<Object> objects = new ArrayList<>(); for ( JsonNode obj : row.get( "objects" ) ) { JsonNode t = obj.get( "type" ); JsonNode rawObject = obj.get( "obj" ); Object object = null; String objType = t.asText(); switch ( objType ) { case "Null": types.add( Void.class ); break; case "String": types.add( String.class ); object = rawObject.asText(); break; case "Integer": types.add( Integer.class ); object = rawObject.asInt(); break; case "Long": types.add( Long.class ); object = rawObject.asLong(); break; case "Date": types.add( Date.class ); try { object = DATE_TIME_INSTANCE.parse( rawObject.asText() ); } catch ( ParseException e ) { e.printStackTrace(); } break; case "Double": types.add( Double.class ); object = rawObject.asDouble(); break; case "BigDecimal": types.add( BigDecimal.class ); object = new BigDecimal( rawObject.asText() ); break; case "Boolean": types.add( Boolean.class ); object = rawObject.asBoolean(); break; case "byte[]": types.add( byte[].class ); object = rawObject.asText().getBytes( "UTF-8" ); break; case "Object": try { types.add( Object.class ); object = new ObjectInputStream( new ByteArrayInputStream( Base64.decodeBase64( rawObject.asText() ) ) ).readObject(); } catch ( ClassNotFoundException e ) { e.printStackTrace(); } break; } objects.add( object ); } Row r = new DeserializedRow( names, types, objects ); rows.add( r ); } Rows rowsObj = new Rows( rows, type, state ); return new DataEvent( new RemoteSource( jsonNode.get( "model-id" ).asText() ), rowsObj ); } } ); mapper.registerModule( module ); } }