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

org.neo4j.fabric.bookmark.BookmarkStateSerializer Maven / Gradle / Ivy

There is a newer version: 5.25.1
Show newest version
/*
 * Copyright (c) "Neo4j"
 * Neo4j Sweden AB [http://neo4j.com]
 *
 * This file is part of Neo4j.
 *
 * Neo4j 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.neo4j.fabric.bookmark;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.UUID;

import org.neo4j.bolt.packstream.PackOutput;
import org.neo4j.bolt.packstream.PackStream;
import org.neo4j.bolt.packstream.PackedInputArray;
import org.neo4j.fabric.bolt.FabricBookmark;
import org.neo4j.fabric.executor.FabricException;

import static org.neo4j.kernel.api.exceptions.Status.Transaction.InvalidBookmark;

public class BookmarkStateSerializer
{
    public static String serialize( FabricBookmark fabricBookmark )
    {
        try
        {
            var packer = new Packer( fabricBookmark );
            var p = packer.pack();
            return p;
        }
        catch ( IOException exception )
        {
            // if this fails, it means a bug
            throw new IllegalStateException( "Failed to serialize bookmark", exception );
        }
    }

    public static FabricBookmark deserialize( String serializedBookmark )
    {
        try
        {
            var unpacker = new Unpacker( serializedBookmark );
            return unpacker.unpack();
        }
        catch ( IOException exception )
        {
            throw new FabricException( InvalidBookmark, "Failed to deserialize bookmark", exception );
        }
    }

    private static class Packer
    {
        final PackedOutputArray packedOutputArray = new PackedOutputArray();
        final PackStream.Packer packer = new BookmarkPackStreamPacker( packedOutputArray );
        final FabricBookmark fabricBookmark;

        Packer( FabricBookmark fabricBookmark )
        {
            this.fabricBookmark = fabricBookmark;
        }

        String pack() throws IOException
        {
            packInternalGraphs( fabricBookmark.getInternalGraphStates() );
            packExternalGraphs( fabricBookmark.getExternalGraphStates() );
            packer.flush();
            return Base64.getEncoder().encodeToString( packedOutputArray.bytes() );
        }

        void packInternalGraphs( List internalGraphStates ) throws IOException
        {
            packer.packListHeader( internalGraphStates.size() );

            for ( var internalGraphState : internalGraphStates )
            {
                packInternalGraph( internalGraphState );
            }
        }

        void packInternalGraph( FabricBookmark.InternalGraphState internalGraphState ) throws IOException
        {
            packUuid( internalGraphState.getGraphUuid() );
            packer.pack( internalGraphState.getTransactionId() );
        }

        void packExternalGraphs( List externalGraphStates ) throws IOException
        {
            packer.packListHeader( externalGraphStates.size() );

            for ( var externalGraphState : externalGraphStates )
            {
                packExternalGraph( externalGraphState );
            }
        }

        void packExternalGraph( FabricBookmark.ExternalGraphState externalGraphState ) throws IOException
        {
            packUuid( externalGraphState.getGraphUuid() );
            packer.packListHeader( externalGraphState.getBookmarks().size() );
            for ( var remoteBookmark : externalGraphState.getBookmarks() )
            {
                packer.pack( remoteBookmark.getSerialisedState() );
            }
        }

        void packUuid( UUID uuid ) throws IOException
        {
            ByteBuffer buffer = ByteBuffer.allocate( 16 );
            buffer.putLong( uuid.getMostSignificantBits() );
            buffer.putLong( uuid.getLeastSignificantBits() );
            packer.pack( buffer.array() );
        }
    }

    private static class Unpacker
    {
        final PackStream.Unpacker unpacker;

        Unpacker( String serializedBookmark )
        {
            var bytes = Base64.getDecoder().decode( serializedBookmark );
            var packedInputArray = new PackedInputArray( bytes );
            unpacker = new PackStream.Unpacker( packedInputArray );
        }

        FabricBookmark unpack() throws IOException
        {
            var internalGraphs = unpackInternalGraphs();
            var externalGraphs = unpackExternalGraphs();

            return new FabricBookmark( internalGraphs, externalGraphs );
        }

        List unpackInternalGraphs() throws IOException
        {
            int listSize = (int) unpacker.unpackListHeader();
            List internalGraphs = new ArrayList<>( listSize );
            for ( int i = 0; i < listSize; i++ )
            {
                var internalGraphState = unpackInternalGraph();
                internalGraphs.add( internalGraphState );
            }

            return internalGraphs;
        }

        FabricBookmark.InternalGraphState unpackInternalGraph() throws IOException
        {
            UUID graphUuid = unpackUuid();
            long txId = unpacker.unpackLong();

            return new FabricBookmark.InternalGraphState( graphUuid, txId );
        }

        List unpackExternalGraphs() throws IOException
        {
            int listSize = (int) unpacker.unpackListHeader();
            List externalGraphs = new ArrayList<>( listSize );
            for ( int i = 0; i < listSize; i++ )
            {
                var externalGraphState = unpackExternalGraph();
                externalGraphs.add( externalGraphState );
            }

            return externalGraphs;
        }

        FabricBookmark.ExternalGraphState unpackExternalGraph() throws IOException
        {
            UUID graphUuid = unpackUuid();
            int listSize = (int) unpacker.unpackListHeader();

            List remoteBookmarks = new ArrayList<>( listSize );
            for ( int i = 0; i < listSize; i++ )
            {
                String serializedRemoteBookmark = unpacker.unpackString();
                remoteBookmarks.add( new RemoteBookmark( serializedRemoteBookmark ) );
            }

            return new FabricBookmark.ExternalGraphState( graphUuid, remoteBookmarks );
        }

        UUID unpackUuid() throws IOException
        {
            byte[] uuidBytes = unpacker.unpackBytes();
            ByteBuffer byteBuffer = ByteBuffer.wrap( uuidBytes );
            long high = byteBuffer.getLong();
            long low = byteBuffer.getLong();

            return new UUID( high, low );
        }
    }

    private static class BookmarkPackStreamPacker extends PackStream.Packer
    {
        // The default PackStream.Packer creates a UTF-8 encoder
        // whose construction is quite expensive (created using reflection, allocating 16K buffer, ...).
        // And since external bookmarks are the only Strings encoded,
        // it is better to use the JDK UTF 8 encoder.
        // Also the external bookmarks should be already UTF-8 encoded (we should not rely on that),
        // which means quite an easy job for the encoder.
        BookmarkPackStreamPacker( PackOutput out )
        {
            super( out, input -> ByteBuffer.wrap( input.getBytes( StandardCharsets.UTF_8 ) ) );
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy