Skip to content

Commit

Permalink
Merge pull request #1608 from microsoft/fix/serialization-helper-back…
Browse files Browse the repository at this point in the history
…ing-store

Allow backing store serialization configuration in serialization helpers
  • Loading branch information
Ndiritu authored Oct 8, 2024
2 parents ce707fc + 6a8b996 commit d15e201
Show file tree
Hide file tree
Showing 10 changed files with 490 additions and 28 deletions.
1 change: 1 addition & 0 deletions components/abstractions/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ android {
disable "GradleDependency"
disable "NewerVersionAvailable"
disable "DuplicatePlatformClasses" // xpp3 added by azure-identity
disable "LambdaLast" // Wrongly enforced in KiotaJsonSerialization helpers
}
sourceSets {
main {
Expand Down
1 change: 1 addition & 0 deletions components/abstractions/gradle/dependencies.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.11.2'
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.11.2'
testImplementation 'org.mockito:mockito-core:5.14.1'
testImplementation project(':components:serialization:json')

// Use JUnit Jupiter Engine for testing.
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
Expand Down
6 changes: 2 additions & 4 deletions components/abstractions/spotBugsExcludeFilter.xml
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,11 @@ xsi:schemaLocation="https://github.com/spotbugs/filter/3.0.0 https://raw.githubu
<Bug pattern="RV_EXCEPTION_NOT_THROWN"/>
<Class name="com.microsoft.kiota.serialization.SerializationHelpersTest" />
</Match>
<Match>
<Bug pattern="EI_EXPOSE_REP" />
<Class name="com.microsoft.kiota.serialization.mocks.TestEntity" />
</Match>
<Match>
<Bug pattern="EI_EXPOSE_REP" />
<Or>
<Class name="com.microsoft.kiota.serialization.mocks.TestEntity" />
<Class name="com.microsoft.kiota.serialization.mocks.TestBackedModelEntity" />
<Class name="com.microsoft.kiota.TestEntity" />
<Class name="com.microsoft.kiota.BaseCollectionPaginationCountResponse" />
</Or>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,20 @@ private KiotaJsonSerialization() {}
return KiotaSerialization.serializeAsStream(CONTENT_TYPE, value);
}

/**
* Serializes the given value to a stream
* @param <T> the type of the value to serialize
* @param value the value to serialize
* @param serializeOnlyChangedValues whether to serialize all values in value if value is a BackedModel
* @return the serialized value as a stream
* @throws IOException when the stream cannot be closed or read.
*/
@Nonnull public static <T extends Parsable> InputStream serializeAsStream(
@Nonnull final T value, final boolean serializeOnlyChangedValues) throws IOException {
return KiotaSerialization.serializeAsStream(
CONTENT_TYPE, value, serializeOnlyChangedValues);
}

/**
* Serializes the given value to a string
* @param <T> the type of the value to serialize
Expand All @@ -38,6 +52,20 @@ private KiotaJsonSerialization() {}
return KiotaSerialization.serializeAsString(CONTENT_TYPE, value);
}

/**
* Serializes the given value to a string
* @param <T> the type of the value to serialize
* @param value the value to serialize
* @param serializeOnlyChangedValues whether to serialize all values in value if value is a BackedModel
* @return the serialized value as a string
* @throws IOException when the stream cannot be closed or read.
*/
@Nonnull public static <T extends Parsable> String serializeAsString(
@Nonnull final T value, final boolean serializeOnlyChangedValues) throws IOException {
return KiotaSerialization.serializeAsString(
CONTENT_TYPE, value, serializeOnlyChangedValues);
}

/**
* Serializes the given value to a stream
* @param <T> the type of the value to serialize
Expand All @@ -50,6 +78,21 @@ private KiotaJsonSerialization() {}
return KiotaSerialization.serializeAsStream(CONTENT_TYPE, values);
}

/**
* Serializes the given value to a stream
* @param <T> the type of the value to serialize
* @param values the values to serialize
* @param serializeOnlyChangedValues whether to serialize all values in value if value is a BackedModel
* @return the serialized value as a stream
* @throws IOException when the stream cannot be closed or read.
*/
@Nonnull public static <T extends Parsable> InputStream serializeAsStream(
@Nonnull final Iterable<T> values, final boolean serializeOnlyChangedValues)
throws IOException {
return KiotaSerialization.serializeAsStream(
CONTENT_TYPE, values, serializeOnlyChangedValues);
}

/**
* Serializes the given value to a string
* @param <T> the type of the value to serialize
Expand All @@ -62,6 +105,21 @@ private KiotaJsonSerialization() {}
return KiotaSerialization.serializeAsString(CONTENT_TYPE, values);
}

/**
* Serializes the given value to a string
* @param <T> the type of the value to serialize
* @param values the values to serialize
* @param serializeOnlyChangedValues whether to serialize all values in value if value is a BackedModel
* @return the serialized value as a string
* @throws IOException when the stream cannot be closed or read.
*/
@Nonnull public static <T extends Parsable> String serializeAsString(
@Nonnull final Iterable<T> values, final boolean serializeOnlyChangedValues)
throws IOException {
return KiotaSerialization.serializeAsString(
CONTENT_TYPE, values, serializeOnlyChangedValues);
}

/**
* Deserializes the given stream to a model object
* @param <T> the type of the value to deserialize
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
*/
public final class KiotaSerialization {
private static final String CHARSET_NAME = "UTF-8";
private static final boolean DEFAULT_SERIALIZE_ONLY_CHANGED_VALUES = true;

private KiotaSerialization() {}

Expand All @@ -29,7 +30,26 @@ private KiotaSerialization() {}
*/
@Nonnull public static <T extends Parsable> InputStream serializeAsStream(
@Nonnull final String contentType, @Nonnull final T value) throws IOException {
try (final SerializationWriter writer = getSerializationWriter(contentType, value)) {
return serializeAsStream(contentType, value, DEFAULT_SERIALIZE_ONLY_CHANGED_VALUES);
}

/**
* Serializes the given value to a stream and configures returned values by the backing store if available
* @param <T> the type of the value to serialize
* @param contentType the content type to use for serialization
* @param value the value to serialize
* @param serializeOnlyChangedValues whether to serialize all values in value if value is a BackedModel
* @return the serialized value as a stream
* @throws IOException when the stream cannot be closed or read.
*/
@Nonnull public static <T extends Parsable> InputStream serializeAsStream(
@Nonnull final String contentType,
@Nonnull final T value,
final boolean serializeOnlyChangedValues)
throws IOException {
Objects.requireNonNull(value);
try (final SerializationWriter writer =
getSerializationWriter(contentType, serializeOnlyChangedValues)) {
writer.writeObjectValue("", value);
return writer.getSerializedContent();
}
Expand All @@ -45,7 +65,27 @@ private KiotaSerialization() {}
*/
@Nonnull public static <T extends Parsable> String serializeAsString(
@Nonnull final String contentType, @Nonnull final T value) throws IOException {
try (final InputStream stream = serializeAsStream(contentType, value)) {
Objects.requireNonNull(value);
return serializeAsString(contentType, value, DEFAULT_SERIALIZE_ONLY_CHANGED_VALUES);
}

/**
* Serializes the given value to a string
* @param <T> the type of the value to serialize
* @param contentType the content type to use for serialization
* @param value the value to serialize
* @param serializeOnlyChangedValues whether to serialize all values in value if value is a BackedModel
* @return the serialized value as a string
* @throws IOException when the stream cannot be closed or read.
*/
@Nonnull public static <T extends Parsable> String serializeAsString(
@Nonnull final String contentType,
@Nonnull final T value,
final boolean serializeOnlyChangedValues)
throws IOException {
Objects.requireNonNull(value);
try (final InputStream stream =
serializeAsStream(contentType, value, serializeOnlyChangedValues)) {
return new String(Compatibility.readAllBytes(stream), CHARSET_NAME);
}
}
Expand All @@ -61,7 +101,27 @@ private KiotaSerialization() {}
@Nonnull public static <T extends Parsable> InputStream serializeAsStream(
@Nonnull final String contentType, @Nonnull final Iterable<T> values)
throws IOException {
try (final SerializationWriter writer = getSerializationWriter(contentType, values)) {
Objects.requireNonNull(values);
return serializeAsStream(contentType, values, DEFAULT_SERIALIZE_ONLY_CHANGED_VALUES);
}

/**
* Serializes the given value to a stream
* @param <T> the type of the value to serialize
* @param contentType the content type to use for serialization
* @param values the values to serialize
* @param serializeOnlyChangedValues whether to serialize all values in value if value is a BackedModel
* @return the serialized value as a stream
* @throws IOException when the stream cannot be closed or read.
*/
@Nonnull public static <T extends Parsable> InputStream serializeAsStream(
@Nonnull final String contentType,
@Nonnull final Iterable<T> values,
final boolean serializeOnlyChangedValues)
throws IOException {
Objects.requireNonNull(values);
try (final SerializationWriter writer =
getSerializationWriter(contentType, serializeOnlyChangedValues)) {
writer.writeCollectionOfObjectValues("", values);
return writer.getSerializedContent();
}
Expand All @@ -78,20 +138,39 @@ private KiotaSerialization() {}
@Nonnull public static <T extends Parsable> String serializeAsString(
@Nonnull final String contentType, @Nonnull final Iterable<T> values)
throws IOException {
try (final InputStream stream = serializeAsStream(contentType, values)) {
Objects.requireNonNull(values);
return serializeAsString(contentType, values, DEFAULT_SERIALIZE_ONLY_CHANGED_VALUES);
}

/**
* Serializes the given value to a string
* @param <T> the type of the value to serialize
* @param contentType the content type to use for serialization
* @param values the values to serialize
* @param serializeOnlyChangedValues whether to serialize all values in value if value is a BackedModel
* @return the serialized value as a string
* @throws IOException when the stream cannot be closed or read.
*/
@Nonnull public static <T extends Parsable> String serializeAsString(
@Nonnull final String contentType,
@Nonnull final Iterable<T> values,
final boolean serializeOnlyChangedValues)
throws IOException {
Objects.requireNonNull(values);
try (final InputStream stream =
serializeAsStream(contentType, values, serializeOnlyChangedValues)) {
return new String(Compatibility.readAllBytes(stream), CHARSET_NAME);
}
}

private static SerializationWriter getSerializationWriter(
@Nonnull final String contentType, @Nonnull final Object value) {
@Nonnull final String contentType, final boolean serializeOnlyChangedValues) {
Objects.requireNonNull(contentType);
Objects.requireNonNull(value);
if (contentType.isEmpty()) {
throw new NullPointerException("content type cannot be empty");
}
return SerializationWriterFactoryRegistry.defaultInstance.getSerializationWriter(
contentType);
contentType, serializeOnlyChangedValues);
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package com.microsoft.kiota.serialization;

import com.microsoft.kiota.store.BackingStoreSerializationWriterProxyFactory;

import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;

import java.util.HashMap;
import java.util.Objects;
Expand Down Expand Up @@ -32,24 +35,77 @@ public SerializationWriterFactoryRegistry() {

@Override
@Nonnull public SerializationWriter getSerializationWriter(@Nonnull final String contentType) {
return getSerializationWriter(contentType, true);
}

/**
* Get a Serialization Writer with backing store configured with serializeOnlyChangedValues
* @param contentType
* @param serializeOnlyChangedValues control backing store functionality
* @return the serialization writer
* @throws RuntimeException when no factory is found for content type
*/
@Nonnull public SerializationWriter getSerializationWriter(
@Nonnull final String contentType, final boolean serializeOnlyChangedValues) {
Objects.requireNonNull(contentType, "parameter contentType cannot be null");
if (contentType.isEmpty()) {
throw new NullPointerException("contentType cannot be empty");
}
final String vendorSpecificContentType = contentType.split(";")[0];
if (contentTypeAssociatedFactories.containsKey(vendorSpecificContentType)) {
return contentTypeAssociatedFactories
.get(vendorSpecificContentType)
.getSerializationWriter(vendorSpecificContentType);
String cleanedContentType = getVendorSpecificContentType(contentType);
SerializationWriterFactory factory = getSerializationWriterFactory(cleanedContentType);
if (factory == null) {
cleanedContentType = getCleanedVendorSpecificContentType(cleanedContentType);
factory =
getSerializationWriterFactory(
getCleanedVendorSpecificContentType(cleanedContentType));
if (factory == null) {
throw new RuntimeException(
"Content type "
+ contentType
+ " does not have a factory to be serialized");
}
}
final String cleanedContentType =
contentTypeVendorCleanupPattern.matcher(vendorSpecificContentType).replaceAll("");
if (contentTypeAssociatedFactories.containsKey(cleanedContentType)) {
return contentTypeAssociatedFactories
.get(cleanedContentType)
.getSerializationWriter(cleanedContentType);
if (!serializeOnlyChangedValues) {
if (factory instanceof BackingStoreSerializationWriterProxyFactory) {
return ((BackingStoreSerializationWriterProxyFactory) factory)
.getSerializationWriter(cleanedContentType, serializeOnlyChangedValues);
}
}
return factory.getSerializationWriter(cleanedContentType);
}

/**
* Gets a SerializationWriterFactory that is mapped to a cleaned content type string
* @param contentType wrapper object carrying initial content type and result of parsing it
* @return the serialization writer factory or null if no mapped factory is found
*/
@Nullable private SerializationWriterFactory getSerializationWriterFactory(
@Nonnull final String contentType) {
if (contentTypeAssociatedFactories.containsKey(contentType)) {
return contentTypeAssociatedFactories.get(contentType);
}
throw new RuntimeException(
"Content type " + contentType + " does not have a factory to be serialized");
return null;
}

/**
* Splits content type by ; and returns first segment or original contentType
* @param contentType
* @return vendor specific content type
*/
@Nonnull private String getVendorSpecificContentType(@Nonnull final String contentType) {
String[] split = contentType.split(";");
if (split.length >= 1) {
return split[0];
}
return contentType;
}

/**
* Does a regex match on the content type replacing special characters
* @param contentType
* @return cleaned content type
*/
@Nonnull private String getCleanedVendorSpecificContentType(@Nonnull final String contentType) {
return contentTypeVendorCleanupPattern.matcher(contentType).replaceAll("");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@
/** Proxy factory that allows the composition of before and after callbacks on existing factories. */
public abstract class SerializationWriterProxyFactory implements SerializationWriterFactory {
@Nonnull public String getValidContentType() {
return _concrete.getValidContentType();
return proxiedFactory.getValidContentType();
}

private final SerializationWriterFactory _concrete;
protected final SerializationWriterFactory proxiedFactory;
private final Consumer<Parsable> _onBefore;
private final Consumer<Parsable> _onAfter;
private final BiConsumer<Parsable, SerializationWriter> _onStart;
Expand All @@ -30,14 +30,14 @@ public SerializationWriterProxyFactory(
@Nullable final Consumer<Parsable> onBeforeSerialization,
@Nullable final Consumer<Parsable> onAfterSerialization,
@Nullable final BiConsumer<Parsable, SerializationWriter> onStartObjectSerialization) {
_concrete = Objects.requireNonNull(concrete);
proxiedFactory = Objects.requireNonNull(concrete);
_onBefore = onBeforeSerialization;
_onAfter = onAfterSerialization;
_onStart = onStartObjectSerialization;
}

@Nonnull public SerializationWriter getSerializationWriter(@Nonnull final String contentType) {
final SerializationWriter writer = _concrete.getSerializationWriter(contentType);
final SerializationWriter writer = proxiedFactory.getSerializationWriter(contentType);
final Consumer<Parsable> originalBefore = writer.getOnBeforeObjectSerialization();
final Consumer<Parsable> originalAfter = writer.getOnAfterObjectSerialization();
final BiConsumer<Parsable, SerializationWriter> originalStart =
Expand Down
Loading

0 comments on commit d15e201

Please sign in to comment.