Skip to content

Commit 2db1abf

Browse files
committed
Preserve exact toLegacyUrlSafe() format of v6.0.7, but allow namespaces
Namespaces are incompatible, of course
1 parent 14d57b8 commit 2db1abf

3 files changed

Lines changed: 94 additions & 43 deletions

File tree

src/main/java/com/googlecode/objectify/Key.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,12 @@ public String toUrlSafe() {
338338
* for {@code Key<?>} understands both formats.
339339
*/
340340
public String toLegacyUrlSafe() {
341-
return KeyFormat.INSTANCE.formatOldStyleAppEngineKey(this.raw);
341+
// Preserve the exact string format of 6.0.7 and before, if we can
342+
if (getNamespace() == null) {
343+
return KeyFormat.NAMESPACELESS.formatOldStyleAppEngineKey(this.raw);
344+
} else {
345+
return KeyFormat.INSTANCE.formatOldStyleAppEngineKey(this.raw);
346+
}
342347
}
343348

344349
/**

src/main/java/com/googlecode/objectify/util/KeyFormat.java

Lines changed: 61 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
11
package com.googlecode.objectify.util;
22

33
import com.google.cloud.datastore.Key;
4+
import com.google.common.base.Preconditions;
45
import com.google.common.io.BaseEncoding;
56
import com.google.protobuf.DescriptorProtos;
7+
import com.google.protobuf.DescriptorProtos.DescriptorProto;
8+
import com.google.protobuf.DescriptorProtos.DescriptorProto.Builder;
9+
import com.google.protobuf.DescriptorProtos.FieldDescriptorProto;
10+
import com.google.protobuf.DescriptorProtos.FieldDescriptorProto.Label;
11+
import com.google.protobuf.DescriptorProtos.FieldDescriptorProto.Type;
612
import com.google.protobuf.Descriptors;
13+
import com.google.protobuf.Descriptors.FieldDescriptor;
714
import com.google.protobuf.DynamicMessage;
815
import com.google.protobuf.InvalidProtocolBufferException;
916
import lombok.SneakyThrows;
@@ -18,7 +25,28 @@
1825
* @author Fred Wulff
1926
*/
2027
public enum KeyFormat {
21-
INSTANCE;
28+
/** Normal one to use */
29+
INSTANCE {
30+
@Override
31+
protected Builder makeReferenceBuilder() {
32+
return super.makeReferenceBuilder()
33+
.addField(
34+
FieldDescriptorProto
35+
.newBuilder()
36+
.setLabel(Label.LABEL_OPTIONAL)
37+
.setType(Type.TYPE_STRING)
38+
.setName("name_space")
39+
.setNumber(20)
40+
.build()
41+
);
42+
}
43+
},
44+
45+
/**
46+
* Special version that does not include name_space in the descriptor, and therefore preserves the exact-to-the-byte
47+
* format of Objectify v6.0.7 and before.
48+
*/
49+
NAMESPACELESS;
2250

2351
// We build the descriptor for the App Engine Onestore Reference type in code
2452
// to avoid depending on the App Engine libraries.
@@ -31,11 +59,15 @@ public enum KeyFormat {
3159

3260
@SneakyThrows
3361
KeyFormat() {
34-
keyDescriptor = initializeFileDescriptor();
62+
this.keyDescriptor = Descriptors.FileDescriptor.buildFrom(DescriptorProtos.FileDescriptorProto.newBuilder()
63+
.addMessageType(makeElementBuilder().build())
64+
.addMessageType(makePathBuilder().build())
65+
.addMessageType(makeReferenceBuilder().build())
66+
.build(), new Descriptors.FileDescriptor[]{});
3567
}
3668

37-
private Descriptors.FileDescriptor initializeFileDescriptor() throws Descriptors.DescriptorValidationException {
38-
final DescriptorProtos.DescriptorProto elementDescriptor = DescriptorProtos.DescriptorProto.newBuilder()
69+
private Builder makeElementBuilder() {
70+
return DescriptorProtos.DescriptorProto.newBuilder()
3971
.setName("Element")
4072
.addField(
4173
DescriptorProtos.FieldDescriptorProto.newBuilder()
@@ -60,9 +92,11 @@ private Descriptors.FileDescriptor initializeFileDescriptor() throws Descriptors
6092
.setName("name")
6193
.setNumber(4)
6294
.build()
63-
).build();
95+
);
96+
}
6497

65-
final DescriptorProtos.DescriptorProto pathDescriptor = DescriptorProtos.DescriptorProto.newBuilder()
98+
private Builder makePathBuilder() {
99+
return DescriptorProtos.DescriptorProto.newBuilder()
66100
.setName("Path")
67101
.addField(
68102
DescriptorProtos.FieldDescriptorProto.newBuilder()
@@ -72,42 +106,29 @@ private Descriptors.FileDescriptor initializeFileDescriptor() throws Descriptors
72106
.setTypeName("Element")
73107
.setNumber(1)
74108
.build()
75-
).build();
109+
);
110+
}
76111

77-
final DescriptorProtos.DescriptorProto referenceDescriptor = DescriptorProtos.DescriptorProto.newBuilder()
112+
protected Builder makeReferenceBuilder() {
113+
return DescriptorProto.newBuilder()
78114
.setName("Reference")
79115
.addField(
80-
DescriptorProtos.FieldDescriptorProto
81-
.newBuilder()
82-
.setLabel(DescriptorProtos.FieldDescriptorProto.Label.LABEL_REQUIRED)
83-
.setType(DescriptorProtos.FieldDescriptorProto.Type.TYPE_STRING)
116+
FieldDescriptorProto.newBuilder()
117+
.setLabel(Label.LABEL_REQUIRED)
118+
.setType(Type.TYPE_STRING)
84119
.setName("app")
85120
.setNumber(13)
86-
.build())
87-
.addField(
88-
DescriptorProtos.FieldDescriptorProto
89-
.newBuilder()
90-
.setLabel(DescriptorProtos.FieldDescriptorProto.Label.LABEL_OPTIONAL)
91-
.setType(DescriptorProtos.FieldDescriptorProto.Type.TYPE_STRING)
92-
.setName("name_space")
93-
.setNumber(20)
94-
.build())
121+
.build()
122+
)
95123
.addField(
96-
DescriptorProtos.FieldDescriptorProto
97-
.newBuilder()
98-
.setLabel(DescriptorProtos.FieldDescriptorProto.Label.LABEL_REQUIRED)
99-
.setType(DescriptorProtos.FieldDescriptorProto.Type.TYPE_MESSAGE)
124+
FieldDescriptorProto.newBuilder()
125+
.setLabel(Label.LABEL_REQUIRED)
126+
.setType(Type.TYPE_MESSAGE)
100127
.setTypeName("Path")
101128
.setName("path")
102129
.setNumber(14)
103-
.build())
104-
.build();
105-
106-
return Descriptors.FileDescriptor.buildFrom(DescriptorProtos.FileDescriptorProto.newBuilder()
107-
.addMessageType(elementDescriptor)
108-
.addMessageType(pathDescriptor)
109-
.addMessageType(referenceDescriptor)
110-
.build(), new Descriptors.FileDescriptor[]{});
130+
.build()
131+
);
111132
}
112133

113134
public Key parseOldStyleAppEngineKey(final String urlsafeKey) throws InvalidProtocolBufferException {
@@ -165,7 +186,13 @@ public String formatOldStyleAppEngineKey(Key key) {
165186
fullProjectId = "s~" + fullProjectId;
166187
}
167188
keyMessageBuilder.setField(referenceDescriptor.findFieldByName("app"), fullProjectId);
168-
keyMessageBuilder.setField(referenceDescriptor.findFieldByName("name_space"), key.getNamespace());
189+
190+
if (key.getNamespace() != null) {
191+
final FieldDescriptor namespaceDescriptor = referenceDescriptor.findFieldByName("name_space");
192+
Preconditions.checkArgument(namespaceDescriptor != null, "This key formatter does not know how to handle namespaces");
193+
keyMessageBuilder.setField(namespaceDescriptor, key.getNamespace());
194+
}
195+
169196
final Descriptors.Descriptor elementDescriptor = keyDescriptor.findMessageTypeByName("Element");
170197

171198
final List<DynamicMessage> elementMessages = new ArrayList<>();

src/test/java/com/googlecode/objectify/util/KeyFormatTest.java

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,39 @@
44
import org.junit.jupiter.api.Test;
55

66
import static com.google.common.truth.Truth.assertThat;
7+
import static org.junit.jupiter.api.Assertions.assertThrows;
78

89
class KeyFormatTest {
910
@Test
1011
void parsesAndFormatsOldStyleGAEKey() throws Exception {
1112
// Both of these get converted to the second form
12-
final String urlsafeKey1 = "agxzfm1haWxmb29nYWVyKAsSDE9yZ2FuaXphdGlvbiIKc3RyZWFrLmNvbQwLEgRVc2VyGJneWww";
13-
final String urlsafeKey2 = "agxzfm1haWxmb29nYWVyKAsSDE9yZ2FuaXphdGlvbiIKc3RyZWFrLmNvbQwLEgRVc2VyGJneWwyiAQA";
13+
final String namespaceless = "agxzfm1haWxmb29nYWVyKAsSDE9yZ2FuaXphdGlvbiIKc3RyZWFrLmNvbQwLEgRVc2VyGJneWww";
14+
final String includesNamespace = "agxzfm1haWxmb29nYWVyKAsSDE9yZ2FuaXphdGlvbiIKc3RyZWFrLmNvbQwLEgRVc2VyGJneWwyiAQA";
1415

15-
final Key key = KeyFormat.INSTANCE.parseOldStyleAppEngineKey(urlsafeKey1);
16-
final String restringified = KeyFormat.INSTANCE.formatOldStyleAppEngineKey(key);
17-
assertThat(restringified).isEqualTo(urlsafeKey2);
16+
final Key key = KeyFormat.INSTANCE.parseOldStyleAppEngineKey(namespaceless);
17+
final String restringifiedWithNamespace = KeyFormat.INSTANCE.formatOldStyleAppEngineKey(key);
18+
assertThat(restringifiedWithNamespace).isEqualTo(includesNamespace);
1819

19-
final Key key2 = KeyFormat.INSTANCE.parseOldStyleAppEngineKey(urlsafeKey2);
20-
final String restringified2 = KeyFormat.INSTANCE.formatOldStyleAppEngineKey(key2);
21-
assertThat(restringified2).isEqualTo(urlsafeKey2);
20+
final Key key2 = KeyFormat.INSTANCE.parseOldStyleAppEngineKey(includesNamespace);
21+
assertThat(key).isEqualTo(key2);
22+
23+
final String restringifiedWithNamespace2 = KeyFormat.INSTANCE.formatOldStyleAppEngineKey(key2);
24+
assertThat(restringifiedWithNamespace2).isEqualTo(includesNamespace);
25+
26+
final String restringifiedWithoutNamespace = KeyFormat.NAMESPACELESS.formatOldStyleAppEngineKey(key2);
27+
assertThat(restringifiedWithoutNamespace).isEqualTo(namespaceless);
28+
}
29+
30+
@Test
31+
void parsesAndFormatsOldStyleGAEKeyWithANamespace() throws Exception {
32+
final String hasNamespace = "ajNzfnRlc3QtcHJvamVjdC0yYTNhMzM2MS0xMDA2LTQ3NGItYWNmNy05NzJhMmUzYmE2ZmJyGgsSB1RyaXZpYWwYAQwLEgZIYXNSZWYYtwQMogEA";
33+
34+
final Key key = KeyFormat.INSTANCE.parseOldStyleAppEngineKey(hasNamespace);
35+
final String restringifiedWithNamespace = KeyFormat.INSTANCE.formatOldStyleAppEngineKey(key);
36+
assertThat(restringifiedWithNamespace).isEqualTo(hasNamespace);
37+
38+
assertThrows(IllegalArgumentException.class, () -> {
39+
KeyFormat.NAMESPACELESS.formatOldStyleAppEngineKey(key);
40+
});
2241
}
2342
}

0 commit comments

Comments
 (0)