Skip to content

Commit 638a9a3

Browse files
authored
DEV-23981: Make public release b2-sdk-java (#193)
* DEV-23981: Make public release b2-sdk-java Added support for Event Notifications Lifecycle rule for cancelling unfinished large files Performance improvements
1 parent d46d348 commit 638a9a3

File tree

82 files changed

+5104
-515
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

82 files changed

+5104
-515
lines changed

CHANGELOG.md

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,49 @@
11
# Changelog
22

3-
## [Unreleased] - TBD
3+
### [6.2.0] - 2024-04-15
4+
* Added support to specify B2Json union types using annotations. Annotation support for union types is required because
5+
Java records do not support inheritance. Example usage:
6+
```java
7+
@B2Json.union(typeField = "type")
8+
@B2Json.unionSubtypes({
9+
@B2Json.unionSubtypes.type(name = "email", clazz = Email.class),
10+
@B2Json.unionSubtypes.type(name = "sms", clazz = Sms.class)
11+
})
12+
sealed interface Message permits Email, Sms {
13+
String subject();
14+
}
15+
16+
@B2Json.type
17+
private record Email(@B2Json.required String subject, @B2Json.required String email) implements Message {
18+
}
19+
20+
@B2Json.type
21+
private record Sms(@B2Json.required String subject, @B2Json.required String phoneNumber) implements Message {
22+
}
23+
```
24+
* Added `@B2Json.type` annotation that can be used with Java records. Using `@B2Json.type` allows for the implicit
25+
Java constructor of Java records to not require the `@B2Json.constructor` annotation. Example usage:
26+
```java
27+
@B2Json.type
28+
record Point(@B2Json.required int x, @B2Json.required int y) { }
29+
```
30+
* Optimized B2DateTimeUtil.formatFguidDateTime
31+
* Reduced memory allocation for small input when deserializing byte[] to JSON
32+
* Reduced lock contention in B2Clock
33+
* Added support for B2 Event Notifications
34+
* Added B2Json `fromJson` methods that take a `java.io.Reader` as input for JSON
35+
* Updated B2Json `fromJson` methods to utilize a BufferedReader when deserializing JSON for performance improvement
36+
* Added B2StorageClient.storePartsForLargeFile
37+
* Added support for daysFromStartingToCancelingUnfinishedLargeFiles to B2LifecycleRule
38+
* Reduced lock contention in B2AccountAuthorizationCache
39+
* Added the `serializedName` annotation to rename the serialized Json member name
40+
* Added support for AtomicLongArray in B2Json
41+
* Reduced lock contention in B2Json
42+
* Updated internal python for building to python3
43+
* Added support for custom upload timestamps
44+
45+
### Fixed
46+
* Fixed union types to ignore extra and discarded fields when deserializing JSON to Java objects
447

548
## [6.1.1] - 2022-11-10
649
### Added
@@ -11,8 +54,9 @@
1154
* Fixed B2ListFilesIterableBase assuming a response with 0 results was the end. It now looks for
1255
`nextFileName` being null to indicate the end.
1356

14-
## [6.1.0] - 2022-09-19
57+
### [6.1.0] - 2022-09-19
1558
### Added
59+
* Added support for custom upload timestamps
1660
* Added support for Java 8's `-parameters` option so constructor parameters do not need to be reiterated in `B2Json.constructor#params`
1761
* Added `fileLockEnabled` to `B2UpdateBucketRequest` to support enabling file lock on existing buckets
1862

build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// License https://www.backblaze.com/using_b2_code.html
33

44
plugins {
5-
id("io.github.gradle-nexus.publish-plugin") version "1.1.0"
5+
id("io.github.gradle-nexus.publish-plugin") version "1.3.0"
66
}
77

88
nexusPublishing {

buildSrc/src/main/kotlin/b2sdk.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ tasks.build {
7878
val checkCode by tasks.registering(Exec::class) {
7979
val script = rootProject.layout.projectDirectory.file("check_code").asFile.absolutePath
8080
val targetDir = layout.projectDirectory.dir("src/main").asFile.absolutePath
81-
commandLine("python", script, targetDir)
81+
commandLine("python3", script, targetDir)
8282
}
8383
tasks.classes {
8484
dependsOn(checkCode)

check_code

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#!/usr/bin/env python
1+
#!/usr/bin/env python3
22

33
######################################################################
44
#

core-test-jdk17/build.gradle.kts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Copyright 2024, Backblaze Inc. All Rights Reserved.
2+
// License https://www.backblaze.com/using_b2_code.html
3+
4+
plugins {
5+
`java-library`
6+
b2sdk
7+
idea
8+
}
9+
10+
description = "JDK 17 testing of b2-sdk-core"
11+
12+
b2sdk {
13+
pomName.set("JDK 17 testing of b2-sdk-core")
14+
description.set(project.description)
15+
}
16+
17+
dependencies {
18+
testImplementation(projects.b2SdkCore)
19+
}
20+
21+
java {
22+
toolchain {
23+
languageVersion.set(JavaLanguageVersion.of(17))
24+
}
25+
}
26+
27+
tasks.withType<JavaCompile>().configureEach {
28+
options.release.set(17)
29+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/*
2+
* Copyright 2024, Backblaze Inc. All Rights Reserved.
3+
* License https://www.backblaze.com/using_b2_code.html
4+
*/
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright 2024, Backblaze Inc. All Rights Reserved.
3+
* License https://www.backblaze.com/using_b2_code.html
4+
*/
5+
package com.backblaze.b2.json;
6+
7+
import com.backblaze.b2.util.B2BaseTest;
8+
import org.junit.Test;
9+
10+
import java.lang.reflect.Constructor;
11+
12+
import static org.junit.Assert.*;
13+
14+
public class B2JsonDeserializationUtilTest extends B2BaseTest {
15+
16+
@Test
17+
public void findConstructor_withJavaRecord() throws B2JsonException {
18+
final Constructor<B2JsonRecord> constructor = B2JsonDeserializationUtil.findConstructor(B2JsonRecord.class);
19+
assertNotNull(constructor);
20+
}
21+
22+
@Test
23+
public void findConstructor_withJavaRecordWithoutB2JsonTypeAnnotation() {
24+
final B2JsonException exception = assertThrows(B2JsonException.class, () -> B2JsonDeserializationUtil.findConstructor(B2JsonRecordWithoutTypeAnnotation.class));
25+
assertTrue(exception.getMessage().contains("has no constructor annotated with B2Json.constructor"));
26+
}
27+
28+
@B2Json.type
29+
public record B2JsonRecord(@B2Json.required String name) {
30+
31+
}
32+
33+
public record B2JsonRecordWithoutTypeAnnotation(@B2Json.required String name) {
34+
35+
}
36+
}
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
/*
2+
* Copyright 2024, Backblaze Inc. All Rights Reserved.
3+
* License https://www.backblaze.com/using_b2_code.html
4+
*/
5+
6+
package com.backblaze.b2.json;
7+
8+
import com.backblaze.b2.util.B2BaseTest;
9+
import org.junit.Test;
10+
11+
import java.io.IOException;
12+
import java.nio.charset.StandardCharsets;
13+
import java.util.Set;
14+
15+
import static org.junit.Assert.*;
16+
17+
/**
18+
* Unit tests for B2Json utilizing Java 17 features.
19+
*/
20+
public class B2JsonTest extends B2BaseTest {
21+
22+
private static final B2Json b2Json = B2Json.get();
23+
24+
@Test
25+
public void testRecord() throws B2JsonException {
26+
final String json = """
27+
{
28+
"@d": "goodbye",
29+
"a": 41,
30+
"b": "hello"
31+
}""";
32+
final RecordContainer obj = new RecordContainer(41, "hello", "goodbye");
33+
assertEquals(json, b2Json.toJson(obj));
34+
assertEquals(obj, b2Json.fromJson(json, RecordContainer.class));
35+
36+
final String alternateJson = """
37+
{
38+
"a": 41,
39+
"b": "hello",
40+
"\\u0040d": "goodbye"
41+
}
42+
""";
43+
assertEquals(obj, b2Json.fromJson(alternateJson, RecordContainer.class));
44+
}
45+
46+
@Test
47+
public void testRecordUnionWithTypeFieldLast() throws IOException, B2JsonException {
48+
final String json = """
49+
{
50+
"a": 5,
51+
"b": null,
52+
"type": "a"
53+
}""";
54+
checkDeserializeSerialize(json, UnionRecordAZ.class);
55+
}
56+
57+
@B2Json.union(typeField = "type")
58+
interface UnionRecordWithOutSubtypes {
59+
}
60+
61+
@Test
62+
public void testRecordUnionWithNoSubtypes() {
63+
final String json = """
64+
{
65+
"a": 5,
66+
"b": null,
67+
"type": "a"
68+
}""";
69+
final B2JsonException exception = assertThrows(B2JsonException.class, () -> b2Json.fromJson(json, UnionRecordWithOutSubtypes.class));
70+
assertEquals("union base class interface com.backblaze.b2.json.B2JsonTest$UnionRecordWithOutSubtypes does not have a method getUnionTypeMap", exception.getMessage());
71+
}
72+
73+
@B2Json.union(typeField = "type")
74+
sealed interface UnionRecordNoSubtypes permits SubtypeRecord {
75+
}
76+
77+
@B2Json.type
78+
private record SubtypeRecord(@B2Json.required int a,
79+
@B2Json.optional Set<Integer> b) implements UnionRecordNoSubtypes {
80+
}
81+
82+
@Test
83+
public void testRecordUnionWithNoSubtypes_toJson() {
84+
final B2JsonException exception = assertThrows(B2JsonException.class, () -> b2Json.toJson(new SubtypeRecord(5, null)));
85+
assertEquals("interface com.backblaze.b2.json.B2JsonTest$UnionRecordNoSubtypes has B2Json.union annotation, but does not have @B2Json.unionSubtypes annotation", exception.getMessage());
86+
}
87+
88+
@B2Json.union(typeField = "type")
89+
@B2Json.unionSubtypes({
90+
@B2Json.unionSubtypes.type(name = "a", clazz = UnionRecordWithUnknownType.definedSubtype.class)
91+
})
92+
interface UnionRecordWithUnknownType {
93+
@B2Json.type
94+
record definedSubtype(@B2Json.required int a,
95+
@B2Json.optional Set<Integer> b) implements UnionRecordWithUnknownType {
96+
}
97+
98+
@B2Json.type
99+
record UndefinedSubtype(@B2Json.required int a,
100+
@B2Json.optional Set<Integer> b) implements UnionRecordWithUnknownType {
101+
102+
}
103+
}
104+
105+
@Test
106+
public void testRecordUnionWithNoMatchingSubtype_fromJson() {
107+
final String json = """
108+
{
109+
"a": 5,
110+
"b": null,
111+
"type": "b"
112+
}""";
113+
final B2JsonException exception = assertThrows(B2JsonException.class, () -> b2Json.fromJson(json, UnionRecordWithUnknownType.class));
114+
assertEquals("unknown 'type' in UnionRecordWithUnknownType: 'b'", exception.getMessage());
115+
}
116+
117+
@Test
118+
public void testRecordUnionWithNoMatchingSubtype_toJson() {
119+
final B2JsonException exception = assertThrows(B2JsonException.class, () -> b2Json.toJson(new UnionRecordWithUnknownType.UndefinedSubtype(5, null)));
120+
assertEquals("interface com.backblaze.b2.json.B2JsonTest$UnionRecordWithUnknownType does not contain mapping for class com.backblaze.b2.json.B2JsonTest$UnionRecordWithUnknownType$UndefinedSubtype in the @B2Json.unionSubtypes annotation", exception.getMessage());
121+
}
122+
123+
@B2Json.union(typeField = "type")
124+
@B2Json.unionSubtypes({
125+
})
126+
interface UnionRecordWithEmptySubtypes {
127+
}
128+
129+
@B2Json.type
130+
private record SubclassRecordWithEmptySubtypes(@B2Json.required int a,
131+
@B2Json.optional Set<Integer> b) implements UnionRecordWithEmptySubtypes {
132+
}
133+
134+
@Test
135+
public void testRecordUnionWithEmptySubtypes_fromJson() {
136+
final String json = """
137+
{
138+
"a": 5,
139+
"b": null,
140+
"type": "b"
141+
}""";
142+
final B2JsonException exception = assertThrows(B2JsonException.class, () -> b2Json.fromJson(json, SubclassRecordWithEmptySubtypes.class));
143+
assertEquals("UnionRecordWithEmptySubtypes - at least one type must be configured set in @B2Json.unionSubtypes", exception.getMessage());
144+
}
145+
146+
@Test
147+
public void testRecordUnionWithEmptySubtypes_toJson() {
148+
final B2JsonException exception = assertThrows(B2JsonException.class, () -> b2Json.toJson(new SubclassRecordWithEmptySubtypes(5, null)));
149+
assertEquals("UnionRecordWithEmptySubtypes - at least one type must be configured set in @B2Json.unionSubtypes", exception.getMessage());
150+
}
151+
152+
@B2Json.union(typeField = "type")
153+
@B2Json.unionSubtypes({
154+
@B2Json.unionSubtypes.type(name = "a", clazz = SubclassRecordA.class),
155+
@B2Json.unionSubtypes.type(name = "z", clazz = SubclassRecordZ.class)
156+
})
157+
sealed interface UnionRecordAZ permits SubclassRecordA, SubclassRecordZ {
158+
}
159+
160+
@B2Json.type
161+
private record SubclassRecordA(@B2Json.required int a, @B2Json.optional Set<Integer> b) implements UnionRecordAZ {
162+
}
163+
164+
@B2Json.type
165+
private record SubclassRecordZ(@B2Json.required String z) implements UnionRecordAZ {
166+
}
167+
168+
@Test
169+
public void testRecordContainingRecord() throws B2JsonException, IOException {
170+
final String json = """
171+
{
172+
"age": 10,
173+
"name": "Sam",
174+
"record": {
175+
"@d": "b",
176+
"a": 5,
177+
"b": "test"
178+
}
179+
}""";
180+
checkDeserializeSerialize(json, RecordContainingRecord.class);
181+
}
182+
183+
@B2Json.type
184+
record RecordContainingRecord(@B2Json.required int age,
185+
@B2Json.optional String name,
186+
@B2Json.optional RecordContainer record) {
187+
}
188+
189+
record RecordContainer(@B2Json.required int a,
190+
@B2Json.optional String b,
191+
@B2Json.ignored int c,
192+
@B2Json.optional @B2Json.serializedName(value = "@d") String d) {
193+
@B2Json.constructor
194+
RecordContainer(int a, String b, String d) {
195+
this(a, b, 5, d);
196+
}
197+
}
198+
199+
private <T> void checkDeserializeSerialize(String json, Class<T> clazz) throws IOException, B2JsonException {
200+
final T obj = b2Json.fromJson(json, clazz);
201+
assertEquals(json, b2Json.toJson(obj));
202+
203+
final byte[] bytes = json.getBytes(StandardCharsets.UTF_8);
204+
final T obj2 = b2Json.fromJson(bytes, clazz);
205+
assertArrayEquals(bytes, b2Json.toJsonUtf8Bytes(obj2));
206+
207+
final T obj3 = b2Json.fromJson(bytes, clazz);
208+
final byte[] bytesWithNewline = (json + "\n").getBytes(StandardCharsets.UTF_8);
209+
assertArrayEquals(bytesWithNewline, b2Json.toJsonUtf8BytesWithNewline(obj3));
210+
}
211+
}

0 commit comments

Comments
 (0)