-
Notifications
You must be signed in to change notification settings - Fork 1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: Thomas Gran <[email protected]>
- Loading branch information
Showing
14 changed files
with
997 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
99 changes: 99 additions & 0 deletions
99
src/main/java/org/opentripplanner/framework/token/Deserializer.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
package org.opentripplanner.framework.token; | ||
|
||
import java.io.ByteArrayInputStream; | ||
import java.io.IOException; | ||
import java.io.ObjectInputStream; | ||
import java.time.Duration; | ||
import java.time.Instant; | ||
import java.util.ArrayList; | ||
import java.util.Base64; | ||
import java.util.List; | ||
import javax.annotation.Nullable; | ||
import org.opentripplanner.framework.time.DurationUtils; | ||
|
||
class Deserializer { | ||
|
||
private final ByteArrayInputStream input; | ||
|
||
Deserializer(String token) { | ||
this.input = new ByteArrayInputStream(Base64.getUrlDecoder().decode(token)); | ||
} | ||
|
||
List<Object> deserialize(TokenDefinition definition) throws IOException { | ||
try { | ||
// Assume deprecated fields are included in the token | ||
return readFields(definition, false); | ||
} catch (IOException ignore) { | ||
// If the token is the next version, then deprecated field are removed. Try | ||
// skipping the deprecated tokens | ||
return readFields(definition, true); | ||
} | ||
} | ||
|
||
private List<Object> readFields(TokenDefinition definition, boolean matchNewVersionPlusOne) | ||
throws IOException { | ||
input.reset(); | ||
List<Object> result = new ArrayList<>(); | ||
|
||
var in = new ObjectInputStream(input); | ||
|
||
readAndMatchVersion(in, definition, matchNewVersionPlusOne); | ||
|
||
for (FieldDefinition field : definition.listFields()) { | ||
if (matchNewVersionPlusOne && field.deprecated()) { | ||
continue; | ||
} | ||
var v = read(in, field); | ||
if (!field.deprecated()) { | ||
result.add(v); | ||
} | ||
} | ||
return result; | ||
} | ||
|
||
private void readAndMatchVersion( | ||
ObjectInputStream in, | ||
TokenDefinition definition, | ||
boolean matchVersionPlusOne | ||
) throws IOException { | ||
int matchVersion = (matchVersionPlusOne ? 1 : 0) + definition.version(); | ||
|
||
int v = readInt(in); | ||
if (v != matchVersion) { | ||
throw new IOException( | ||
"Version does not match. Token version: " + v + ", schema version: " + definition.version() | ||
); | ||
} | ||
} | ||
|
||
private Object read(ObjectInputStream in, FieldDefinition field) throws IOException { | ||
return switch (field.type()) { | ||
case BYTE -> readByte(in); | ||
case DURATION -> readDuration(in); | ||
case INT -> readInt(in); | ||
case STRING -> readString(in); | ||
case TIME_INSTANT -> readTimeInstant(in); | ||
}; | ||
} | ||
|
||
private static byte readByte(ObjectInputStream in) throws IOException { | ||
return in.readByte(); | ||
} | ||
|
||
private static int readInt(ObjectInputStream in) throws IOException { | ||
return Integer.parseInt(in.readUTF()); | ||
} | ||
|
||
private static String readString(ObjectInputStream in) throws IOException { | ||
return in.readUTF(); | ||
} | ||
|
||
private static Duration readDuration(ObjectInputStream in) throws IOException { | ||
return DurationUtils.duration(in.readUTF()); | ||
} | ||
|
||
@Nullable | ||
private static Instant readTimeInstant(ObjectInputStream in) throws IOException { | ||
return Instant.parse(in.readUTF()); | ||
} | ||
} |
54 changes: 54 additions & 0 deletions
54
src/main/java/org/opentripplanner/framework/token/FieldDefinition.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
package org.opentripplanner.framework.token; | ||
|
||
import java.util.Objects; | ||
|
||
class FieldDefinition { | ||
|
||
private final String name; | ||
private final TokenType type; | ||
private final boolean deprecated; | ||
|
||
private FieldDefinition(String name, TokenType type, boolean deprecated) { | ||
this.name = Objects.requireNonNull(name); | ||
this.type = Objects.requireNonNull(type); | ||
this.deprecated = deprecated; | ||
} | ||
|
||
public FieldDefinition(String name, TokenType type) { | ||
this(name, type, false); | ||
} | ||
|
||
public String name() { | ||
return name; | ||
} | ||
|
||
public TokenType type() { | ||
return type; | ||
} | ||
|
||
public boolean deprecated() { | ||
return deprecated; | ||
} | ||
|
||
@Override | ||
public boolean equals(Object o) { | ||
if (this == o) return true; | ||
if (o == null || getClass() != o.getClass()) return false; | ||
FieldDefinition that = (FieldDefinition) o; | ||
return deprecated == that.deprecated && Objects.equals(name, that.name) && type == that.type; | ||
} | ||
|
||
@Override | ||
public int hashCode() { | ||
return Objects.hash(name, type, deprecated); | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
return (deprecated ? "@deprecated " : "") + name + ":" + type; | ||
} | ||
|
||
public FieldDefinition deprecate() { | ||
return new FieldDefinition(name, type, true); | ||
} | ||
} |
64 changes: 64 additions & 0 deletions
64
src/main/java/org/opentripplanner/framework/token/Serializer.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
package org.opentripplanner.framework.token; | ||
|
||
import java.io.ByteArrayOutputStream; | ||
import java.io.IOException; | ||
import java.io.ObjectOutputStream; | ||
import java.time.Duration; | ||
import java.time.Instant; | ||
import java.util.Base64; | ||
import org.opentripplanner.framework.time.DurationUtils; | ||
|
||
class Serializer { | ||
|
||
private final TokenDefinition definition; | ||
private final ByteArrayOutputStream buf = new ByteArrayOutputStream(); | ||
|
||
Serializer(TokenDefinition definition) { | ||
this.definition = definition; | ||
} | ||
|
||
String serialize(Object[] values) throws IOException { | ||
try (var out = new ObjectOutputStream(buf)) { | ||
writeInt(out, definition.version()); | ||
|
||
for (var fieldName : definition.fieldNames()) { | ||
var value = values[definition.index(fieldName)]; | ||
write(out, fieldName, value); | ||
} | ||
out.flush(); | ||
} | ||
return Base64.getUrlEncoder().encodeToString(buf.toByteArray()); | ||
} | ||
|
||
private void write(ObjectOutputStream out, String fieldName, Object value) throws IOException { | ||
var type = definition.type(fieldName); | ||
switch (type) { | ||
case BYTE -> writeByte(out, (byte) value); | ||
case DURATION -> writeDuration(out, (Duration) value); | ||
case INT -> writeInt(out, (int) value); | ||
case STRING -> writeString(out, (String) value); | ||
case TIME_INSTANT -> writeTimeInstant(out, (Instant) value); | ||
default -> throw new IllegalArgumentException("Unknown type: " + type); | ||
} | ||
} | ||
|
||
private static void writeByte(ObjectOutputStream out, byte value) throws IOException { | ||
out.writeByte(value); | ||
} | ||
|
||
private static void writeInt(ObjectOutputStream out, int value) throws IOException { | ||
out.writeUTF(Integer.toString(value)); | ||
} | ||
|
||
private static void writeString(ObjectOutputStream out, String value) throws IOException { | ||
out.writeUTF(value); | ||
} | ||
|
||
private static void writeDuration(ObjectOutputStream out, Duration duration) throws IOException { | ||
out.writeUTF(DurationUtils.durationToStr(duration)); | ||
} | ||
|
||
private static void writeTimeInstant(ObjectOutputStream out, Instant time) throws IOException { | ||
out.writeUTF(time.toString()); | ||
} | ||
} |
61 changes: 61 additions & 0 deletions
61
src/main/java/org/opentripplanner/framework/token/Token.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
package org.opentripplanner.framework.token; | ||
|
||
import java.time.Duration; | ||
import java.time.Instant; | ||
import java.util.List; | ||
import java.util.Objects; | ||
import java.util.stream.Collectors; | ||
|
||
/** | ||
* Given a schema definition and a token version this class holds the values for | ||
* all fields in a token. | ||
*/ | ||
public class Token { | ||
|
||
private final TokenDefinition definition; | ||
private final List<Object> fieldValues; | ||
|
||
Token(TokenDefinition definition, List<Object> fieldValues) { | ||
this.definition = Objects.requireNonNull(definition); | ||
this.fieldValues = Objects.requireNonNull(fieldValues); | ||
} | ||
|
||
public int version() { | ||
return definition.version(); | ||
} | ||
|
||
public byte getByte(String fieldName) { | ||
return (byte) get(fieldName, TokenType.BYTE); | ||
} | ||
|
||
public Duration getDuration(String fieldName) { | ||
return (Duration) get(fieldName, TokenType.DURATION); | ||
} | ||
|
||
public int getInt(String fieldName) { | ||
return (int) get(fieldName, TokenType.INT); | ||
} | ||
|
||
public String getString(String fieldName) { | ||
return (String) get(fieldName, TokenType.STRING); | ||
} | ||
|
||
public Instant getTimeInstant(String fieldName) { | ||
return (Instant) get(fieldName, TokenType.TIME_INSTANT); | ||
} | ||
|
||
private Object get(String fieldName, TokenType type) { | ||
return fieldValues.get(definition.getIndex(fieldName, type)); | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
return ( | ||
"(v" + | ||
version() + | ||
", " + | ||
fieldValues.stream().map(Objects::toString).collect(Collectors.joining(", ")) + | ||
')' | ||
); | ||
} | ||
} |
55 changes: 55 additions & 0 deletions
55
src/main/java/org/opentripplanner/framework/token/TokenBuilder.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
package org.opentripplanner.framework.token; | ||
|
||
import java.io.IOException; | ||
import java.time.Duration; | ||
import java.time.Instant; | ||
import org.opentripplanner.framework.lang.ObjectUtils; | ||
|
||
/** | ||
* This class is used to create a {@link Token} before encoding it. | ||
*/ | ||
public class TokenBuilder { | ||
|
||
private final TokenDefinition definition; | ||
private final Object[] values; | ||
|
||
public TokenBuilder(TokenDefinition definition) { | ||
this.definition = definition; | ||
this.values = new Object[definition.size()]; | ||
} | ||
|
||
public TokenBuilder withByte(String fieldName, byte v) { | ||
return with(fieldName, TokenType.BYTE, v); | ||
} | ||
|
||
public TokenBuilder withDuration(String fieldName, Duration v) { | ||
return with(fieldName, TokenType.DURATION, v); | ||
} | ||
|
||
public TokenBuilder withInt(String fieldName, int v) { | ||
return with(fieldName, TokenType.INT, v); | ||
} | ||
|
||
public TokenBuilder withString(String fieldName, String v) { | ||
return with(fieldName, TokenType.STRING, v); | ||
} | ||
|
||
public TokenBuilder withTimeInstant(String fieldName, Instant v) { | ||
return with(fieldName, TokenType.TIME_INSTANT, v); | ||
} | ||
|
||
public String build() { | ||
try { | ||
return new Serializer(definition).serialize(values); | ||
} catch (IOException e) { | ||
throw new RuntimeException(e.getMessage(), e); | ||
} | ||
} | ||
|
||
private TokenBuilder with(String fieldName, TokenType type, Object value) { | ||
int index = definition.getIndex(fieldName, type); | ||
ObjectUtils.requireNotInitialized(fieldName, values[index], value); | ||
values[index] = value; | ||
return this; | ||
} | ||
} |
Oops, something went wrong.