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

io.deephaven.server.session.SharedTicketResolver Maven / Gradle / Ivy

The newest version!
//
// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending
//
package io.deephaven.server.session;

import com.google.common.collect.MapMaker;
import com.google.protobuf.ByteString;
import com.google.protobuf.ByteStringAccess;
import com.google.rpc.Code;
import io.deephaven.engine.table.Table;
import io.deephaven.proto.backplane.grpc.ExportNotification;
import io.deephaven.proto.flight.util.FlightExportTicketHelper;
import io.deephaven.proto.flight.util.TicketRouterHelper;
import io.deephaven.proto.util.ByteHelper;
import io.deephaven.proto.util.Exceptions;
import io.deephaven.proto.util.SharedTicketHelper;
import io.deephaven.server.auth.AuthorizationProvider;
import io.grpc.StatusRuntimeException;
import org.apache.arrow.flight.impl.Flight;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.inject.Inject;
import javax.inject.Singleton;
import java.nio.ByteBuffer;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Consumer;

import static io.deephaven.proto.util.SharedTicketHelper.FLIGHT_DESCRIPTOR_ROUTE;
import static io.deephaven.proto.util.SharedTicketHelper.TICKET_PREFIX;

@Singleton
public class SharedTicketResolver extends TicketResolverBase {

    private final ConcurrentMap> sharedVariables = new MapMaker()
            .weakValues()
            .makeMap();

    @Inject
    public SharedTicketResolver(
            final AuthorizationProvider authProvider) {
        super(authProvider, (byte) TICKET_PREFIX, FLIGHT_DESCRIPTOR_ROUTE);
    }

    @Override
    public String getLogNameFor(ByteBuffer ticket, String logId) {
        final ByteString ticketId = idForTicket(ticket, logId);
        return String.format("%s/%s", FLIGHT_DESCRIPTOR_ROUTE, toHexString(ticketId));
    }

    private static @NotNull String toHexString(ByteString ticketId) {
        return ByteHelper.byteBufToHex(ticketId.asReadOnlyByteBuffer());
    }

    @Override
    public SessionState.ExportObject flightInfoFor(
            @Nullable final SessionState session, final Flight.FlightDescriptor descriptor, final String logId) {
        if (session == null) {
            throw Exceptions.statusRuntimeException(Code.UNAUTHENTICATED, String.format(
                    "Could not resolve '%s': no session to handoff to", logId));
        }

        final ByteString sharedId = idForDescriptor(descriptor, logId);

        SessionState.ExportObject export = sharedVariables.get(sharedId);
        if (export == null) {
            throw newNotFoundSRE(logId, toHexString(sharedId));
        }

        return session.nonExport()
                .require(export)
                .submit(() -> {
                    Object result = export.get();
                    if (result instanceof Table) {
                        result = authorization.transform(result);
                    }
                    if (result instanceof Table) {
                        return TicketRouter.getFlightInfo((Table) result, descriptor,
                                FlightExportTicketHelper.descriptorToFlightTicket(descriptor, logId));
                    }

                    throw newNotFoundSRE(logId, toHexString(sharedId));
                });
    }

    @Override
    public void forAllFlightInfo(@Nullable final SessionState session, final Consumer visitor) {
        // shared tickets are otherwise private, so we don't need to do anything here
    }

    @Override
    public  SessionState.ExportObject resolve(
            @Nullable final SessionState session, final ByteBuffer ticket, final String logId) {
        return resolve(session, idForTicket(ticket, logId), logId);
    }

    @Override
    public  SessionState.ExportObject resolve(
            @Nullable final SessionState session, final Flight.FlightDescriptor descriptor, final String logId) {
        return resolve(session, idForDescriptor(descriptor, logId), logId);
    }

    private  SessionState.ExportObject resolve(
            @Nullable final SessionState session, final ByteString sharedId, final String logId) {
        if (session == null) {
            throw Exceptions.statusRuntimeException(Code.UNAUTHENTICATED, String.format(
                    "Could not resolve '%s': no session to handoff to", logId));
        }

        // noinspection unchecked
        final SessionState.ExportObject sharedVar = (SessionState.ExportObject) sharedVariables.get(sharedId);
        if (sharedVar == null) {
            return SessionState.wrapAsFailedExport(newNotFoundSRE(logId, toHexString(sharedId)));
        }

        // we need to wrap this in a new export object to hand off to the new session and defer checking permissions
        return session.nonExport()
                .require(sharedVar)
                .submit(() -> {
                    T result = sharedVar.get();
                    result = authorization.transform(result);
                    if (result == null) {
                        throw newNotFoundSRE(logId, toHexString(sharedId));
                    }
                    return result;
                });
    }

    @Override
    public  SessionState.ExportBuilder publish(
            final SessionState session,
            final ByteBuffer ticket,
            final String logId,
            @Nullable final Runnable onPublish) {
        return failDueToBadSource(logId, toHexString(idForTicket(ticket, logId)));
    }

    @Override
    public  SessionState.ExportBuilder publish(
            final SessionState session,
            final Flight.FlightDescriptor descriptor,
            final String logId,
            @Nullable final Runnable onPublish) {
        return failDueToBadSource(logId, toHexString(idForDescriptor(descriptor, logId)));
    }

    @Override
    public  void publish(
            final SessionState session,
            final ByteBuffer ticket,
            final String logId,
            @Nullable final Runnable onPublish,
            final SessionState.ExportErrorHandler errorHandler,
            final SessionState.ExportObject source) {
        if (source.isNonExport()) {
            failDueToBadSource(logId, toHexString(idForTicket(ticket, logId)));
            return;
        }
        final ByteString sharedId = idForTicket(ticket, logId);
        final SessionState.ExportObject existing = sharedVariables.putIfAbsent(sharedId, source);
        if (existing != null) {
            final String ticketHex = toHexString(sharedId);
            errorHandler.onError(ExportNotification.State.FAILED, "",
                    Exceptions.statusRuntimeException(Code.ALREADY_EXISTS, String.format(
                            "Could not publish '%s' to shared ticket '%s' (hex): destination already exists",
                            logId, ticketHex)),
                    null);
        } else if (onPublish != null) {
            onPublish.run();
        }
    }

    private static  T failDueToBadSource(String logId, String ticketHex) {
        throw Exceptions.statusRuntimeException(Code.FAILED_PRECONDITION, String.format(
                "Could not publish '%s' to shared ticket '%s' (hex): can only publish directly from a session"
                        + " export to a shared ticket",
                logId, ticketHex));
    }

    /**
     * Convenience method to convert from a shared identifier to Flight.Ticket
     *
     * @param identifier the shared identifier to convert
     * @return the flight ticket this descriptor represents
     */
    public static Flight.Ticket flightTicketForId(final byte[] identifier) {
        return Flight.Ticket.newBuilder()
                .setTicket(ByteString.copyFrom(identifier))
                .build();
    }

    /**
     * Convenience method to convert from a shared identifier to Ticket
     *
     * @param identifier the shared identifier to convert
     * @return the flight ticket this descriptor represents
     */
    public static io.deephaven.proto.backplane.grpc.Ticket ticketForId(final byte[] identifier) {
        return io.deephaven.proto.backplane.grpc.Ticket.newBuilder()
                .setTicket(ByteString.copyFrom(identifier))
                .build();
    }

    /**
     * Convenience method to convert from a shared identifier to Flight.FlightDescriptor
     *
     * @param identifier the shared identifier to convert
     * @return the flight descriptor this descriptor represents
     */
    public static Flight.FlightDescriptor descriptorForId(final byte[] identifier) {
        return Flight.FlightDescriptor.newBuilder()
                .setType(Flight.FlightDescriptor.DescriptorType.PATH)
                .addAllPath(SharedTicketHelper.idToPath(identifier))
                .build();
    }

    /**
     * Convenience method to convert from a Flight.Ticket (as ByteBuffer) to shared identifier
     *
     * @param ticket the ticket to convert
     * @param logId an end-user friendly identification of the ticket should an error occur
     * @return the query scope name this ticket represents
     */
    private static ByteString idForTicket(final ByteBuffer ticket, final String logId) {
        if (ticket == null || ticket.remaining() == 0) {
            throw Exceptions.statusRuntimeException(Code.FAILED_PRECONDITION,
                    String.format("Could not resolve '%s': no ticket supplied", logId));
        }
        if (ticket.remaining() < 2 || ticket.get(ticket.position()) != SharedTicketHelper.TICKET_PREFIX) {
            throw Exceptions.statusRuntimeException(Code.FAILED_PRECONDITION,
                    String.format("Could not resolve '%s': found 0x%s (hex)", logId, ByteHelper.byteBufToHex(ticket)));
        }

        final int initialPosition = ticket.position();
        try {
            ticket.position(initialPosition + 1);
            final byte[] dst = new byte[ticket.remaining()];
            ticket.get(dst);
            return ByteStringAccess.wrap(dst);
        } finally {
            ticket.position(initialPosition);
        }
    }

    /**
     * Convenience method to convert from a Flight.FlightDescriptor to shared identifier
     *
     * @param descriptor the descriptor to convert
     * @param logId an end-user friendly identification of the ticket should an error occur
     * @return the query scope name this descriptor represents
     */
    private static ByteString idForDescriptor(final Flight.FlightDescriptor descriptor, final String logId) {
        if (descriptor.getType() != Flight.FlightDescriptor.DescriptorType.PATH) {
            throw Exceptions.statusRuntimeException(Code.FAILED_PRECONDITION, String.format(
                    "Could not resolve descriptor '%s': only paths are supported", logId));
        }
        if (descriptor.getPathCount() != 2) {
            throw Exceptions.statusRuntimeException(Code.FAILED_PRECONDITION, String.format(
                    "Could not resolve descriptor '%s': unexpected path length (found: %s, expected: 2)",
                    logId, TicketRouterHelper.getLogNameFor(descriptor)));
        }
        if (!descriptor.getPath(0).equals(FLIGHT_DESCRIPTOR_ROUTE)) {
            throw Exceptions.statusRuntimeException(Code.FAILED_PRECONDITION, String.format(
                    "Could not resolve descriptor '%s': unexpected path (found: %s, expected: %s)",
                    logId, TicketRouterHelper.getLogNameFor(descriptor), FLIGHT_DESCRIPTOR_ROUTE));
        }
        return ByteString.fromHex(descriptor.getPath(1));
    }

    /**
     * Convenience method to convert from a Flight.Ticket to a Flight.FlightDescriptor.
     *
     * @param ticket the ticket to convert
     * @param logId an end-user friendly identification of the ticket should an error occur
     * @return a flight descriptor that represents the ticket
     */
    public static Flight.FlightDescriptor ticketToDescriptor(final Flight.Ticket ticket, final String logId) {
        return descriptorForId(idForTicket(ticket.getTicket().asReadOnlyByteBuffer(), logId).toByteArray());
    }

    /**
     * Convenience method to convert from a Flight.Descriptor to a Flight.Ticket.
     *
     * @param descriptor the descriptor to convert
     * @param logId an end-user friendly identification of the ticket should an error occur
     * @return a flight ticket that represents the descriptor
     */
    public static Flight.Ticket descriptorToTicket(final Flight.FlightDescriptor descriptor, final String logId) {
        return flightTicketForId(idForDescriptor(descriptor, logId).toByteArray());
    }

    private static @NotNull StatusRuntimeException newNotFoundSRE(String logId, String sharedId) {
        return Exceptions.statusRuntimeException(Code.NOT_FOUND, String.format(
                "Could not resolve '%s': ticket '%s' not found", logId, sharedId));
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy