|
| 1 | +// |
| 2 | +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending |
| 3 | +// |
| 4 | +package io.deephaven.web.client.api; |
| 5 | + |
| 6 | +import elemental2.core.TypedArray; |
| 7 | +import elemental2.core.Uint8Array; |
| 8 | +import elemental2.dom.DomGlobal; |
| 9 | +import io.deephaven.javascript.proto.dhinternal.io.deephaven_core.proto.table_pb.TableReference; |
| 10 | +import io.deephaven.javascript.proto.dhinternal.io.deephaven_core.proto.ticket_pb.Ticket; |
| 11 | +import io.deephaven.web.client.api.console.JsVariableDefinition; |
| 12 | +import jsinterop.annotations.JsMethod; |
| 13 | +import jsinterop.annotations.JsPackage; |
| 14 | +import jsinterop.base.Js; |
| 15 | + |
| 16 | +/** |
| 17 | + * Single factory for known ticket types. By definition, this cannot be exhaustive, since flight tickets have no |
| 18 | + * inherent structure - Deephaven Core only specifies that the first byte will indicate the type of ticket, and later |
| 19 | + * bytes will be handled by handlers for that type. Deephaven Core requires only that export tickets be support, but |
| 20 | + * also offers application tickets, scope tickets, and shared tickets. Downstream projects may define new ticket types, |
| 21 | + * which won't necessarily be understood by this client. |
| 22 | + * |
| 23 | + * @see io.deephaven.server.session.ExportTicketResolver |
| 24 | + * @see io.deephaven.server.appmode.ApplicationTicketResolver |
| 25 | + * @see io.deephaven.server.console.ScopeTicketResolver |
| 26 | + * @see io.deephaven.server.session.SharedTicketResolver |
| 27 | + */ |
| 28 | +public class Tickets { |
| 29 | + // Prefix for all export tickets |
| 30 | + private static final byte EXPORT_PREFIX = 'e'; |
| 31 | + // Prefix for all application tickets |
| 32 | + private static final byte APPLICATION_PREFIX = 'a'; |
| 33 | + // Prefix for all scope tickets |
| 34 | + private static final byte SCOPE_PREFIX = 's'; |
| 35 | + // Prefix for all shared tickets |
| 36 | + private static final byte SHARED_PREFIX = 'h'; |
| 37 | + |
| 38 | + // Some ticket types use a slash as a delimeter between fields |
| 39 | + private static final char TICKET_DELIMITER = '/'; |
| 40 | + |
| 41 | + /** |
| 42 | + * The next number to use when making an export ticket. These values must always be positive, as zero is an invalid |
| 43 | + * value, and negative values represent server-created tickets. |
| 44 | + */ |
| 45 | + private int nextExport = 1; |
| 46 | + |
| 47 | + public Tickets() {} |
| 48 | + |
| 49 | + /** |
| 50 | + * Utility method to create a ticket from a known-valid base64 encoding of a ticket. |
| 51 | + * <p> |
| 52 | + * Use caution with non-export tickets, the definition may change between calls to the server - they should be |
| 53 | + * exported before use. |
| 54 | + * |
| 55 | + * @param varDef the variable definition to create a ticket from |
| 56 | + * @return a ticket with the variable's id as the ticket bytes |
| 57 | + */ |
| 58 | + public static Ticket createTicket(JsVariableDefinition varDef) { |
| 59 | + Ticket ticket = new Ticket(); |
| 60 | + ticket.setTicket(varDef.getId()); |
| 61 | + return ticket; |
| 62 | + } |
| 63 | + |
| 64 | + /** |
| 65 | + * Utility method to create a ticket wrapped in a TableReference from a known-valid base64 encoding of a ticket. |
| 66 | + * <p> |
| 67 | + * Use caution with non-export tickets, the definition may change between calls to the server - they should be |
| 68 | + * exported before use. |
| 69 | + * |
| 70 | + * @param varDef the variable definition to create a ticket from |
| 71 | + * @return a table reference with the variable's id as the ticket bytes |
| 72 | + */ |
| 73 | + |
| 74 | + public static TableReference createTableRef(JsVariableDefinition varDef) { |
| 75 | + TableReference tableRef = new TableReference(); |
| 76 | + tableRef.setTicket(createTicket(varDef)); |
| 77 | + return tableRef; |
| 78 | + } |
| 79 | + |
| 80 | + public static void validateScopeOrApplicationTicketBase64(String base64Bytes) { |
| 81 | + String bytes = DomGlobal.atob(base64Bytes); |
| 82 | + if (bytes.length() > 2) { |
| 83 | + String prefix = bytes.substring(0, 2); |
| 84 | + if ((prefix.charAt(0) == SCOPE_PREFIX || prefix.charAt(0) == APPLICATION_PREFIX) |
| 85 | + && prefix.charAt(1) == TICKET_DELIMITER) { |
| 86 | + return; |
| 87 | + } |
| 88 | + } |
| 89 | + throw new IllegalArgumentException("Cannot create a VariableDefinition from a non-scope ticket"); |
| 90 | + } |
| 91 | + |
| 92 | + /** |
| 93 | + * Provides the next export id for the current session as a ticket. |
| 94 | + * |
| 95 | + * @return a new ticket with an export id that hasn't previously been used for this session |
| 96 | + */ |
| 97 | + public Ticket newExportTicket() { |
| 98 | + Ticket ticket = new Ticket(); |
| 99 | + ticket.setTicket(newExportTicketRaw()); |
| 100 | + return ticket; |
| 101 | + } |
| 102 | + |
| 103 | + /** |
| 104 | + * Provides the next export id for the current session. |
| 105 | + * |
| 106 | + * @return the next export id |
| 107 | + */ |
| 108 | + public int newTicketInt() { |
| 109 | + if (nextExport == Integer.MAX_VALUE) { |
| 110 | + throw new IllegalStateException("Ran out of tickets!"); |
| 111 | + } |
| 112 | + |
| 113 | + return nextExport++; |
| 114 | + } |
| 115 | + |
| 116 | + private Uint8Array newExportTicketRaw() { |
| 117 | + final int exportId = newTicketInt(); |
| 118 | + final double[] dest = new double[5]; |
| 119 | + dest[0] = EXPORT_PREFIX; |
| 120 | + dest[1] = (byte) exportId; |
| 121 | + dest[2] = (byte) (exportId >>> 8); |
| 122 | + dest[3] = (byte) (exportId >>> 16); |
| 123 | + dest[4] = (byte) (exportId >>> 24); |
| 124 | + |
| 125 | + final Uint8Array bytes = new Uint8Array(5); |
| 126 | + bytes.set(dest); |
| 127 | + return bytes; |
| 128 | + } |
| 129 | + |
| 130 | + /** |
| 131 | + * Provides the next export id for the current session as a table ticket. |
| 132 | + * |
| 133 | + * @return a new table ticket with an export id that hasn't previously been used for this session |
| 134 | + */ |
| 135 | + public TableTicket newTableTicket() { |
| 136 | + return new TableTicket(newExportTicketRaw()); |
| 137 | + } |
| 138 | + |
| 139 | + /** |
| 140 | + * Creates a shared ticket from the provided array of bytes. |
| 141 | + * <p> |
| 142 | + * Use caution with non-export tickets, the definition may change between calls to the server - they should be |
| 143 | + * exported before use. |
| 144 | + * |
| 145 | + * @param array array of bytes to populate the ticket with |
| 146 | + * @return a new shared ticket |
| 147 | + */ |
| 148 | + public Ticket sharedTicket(TypedArray.SetArrayUnionType array) { |
| 149 | + int length = Js.asArrayLike(array).getLength(); |
| 150 | + Uint8Array bytesWithPrefix = new Uint8Array(length + 2); |
| 151 | + // Add the shared ticket prefix at the start of the provided value |
| 152 | + bytesWithPrefix.setAt(0, (double) SHARED_PREFIX); |
| 153 | + bytesWithPrefix.setAt(1, (double) TICKET_DELIMITER); |
| 154 | + bytesWithPrefix.set(array, 2); |
| 155 | + |
| 156 | + Ticket ticket = new Ticket(); |
| 157 | + ticket.setTicket(bytesWithPrefix); |
| 158 | + return ticket; |
| 159 | + } |
| 160 | +} |
0 commit comments