- 12.0.3
- 6.0.0
+ 13.0.1
+ 7.0.0
4.3.0
4.7.0
4.7.0
- 1.3.3
+ 1.4.1
1.3.0
4.1.0
1.1.7
@@ -55,21 +55,19 @@
Please sort the packages alphabetically
-->
- 0.13.1
- 3.1.2
- 3.1.2
- 17.1.0
- 17.1.0
- 4.1.0
- 4.1.0
- 4.1.0
-
- 6.0.0
- 7.0.0-preview*
- 17.1.0
- 3.13.2
- 3.15.0
- 4.2.1
+ 0.13.2
+ 3.2.0
+ 3.2.0
+ 17.4.0
+ 17.4.0
+ 4.3.1
+ 4.3.1
+ 4.3.1
+ 7.0.1
+ 17.4.0
+ 3.13.3
+ 3.15.2
+ 4.3.0
1.1.118
diff --git a/lang/java/android/pom.xml b/lang/java/android/pom.xml
index 1ec1f8eb107..c05c14e5682 100644
--- a/lang/java/android/pom.xml
+++ b/lang/java/android/pom.xml
@@ -22,7 +22,7 @@
avro-parent
org.apache.avro
- 1.11.2-SNAPSHOT
+ 1.11.4-SNAPSHOT
../pom.xml
diff --git a/lang/java/archetypes/avro-service-archetype/pom.xml b/lang/java/archetypes/avro-service-archetype/pom.xml
index a1e160f450e..72a773bb627 100644
--- a/lang/java/archetypes/avro-service-archetype/pom.xml
+++ b/lang/java/archetypes/avro-service-archetype/pom.xml
@@ -23,7 +23,7 @@
avro-archetypes-parent
org.apache.avro
- 1.11.2-SNAPSHOT
+ 1.11.4-SNAPSHOT
../pom.xml
diff --git a/lang/java/archetypes/pom.xml b/lang/java/archetypes/pom.xml
index 922ff619612..cb35096f038 100644
--- a/lang/java/archetypes/pom.xml
+++ b/lang/java/archetypes/pom.xml
@@ -22,7 +22,7 @@
org.apache.avro
avro-parent
- 1.11.2-SNAPSHOT
+ 1.11.4-SNAPSHOT
../pom.xml
diff --git a/lang/java/avro/pom.xml b/lang/java/avro/pom.xml
index 0b148a140b8..ee7b525c94b 100644
--- a/lang/java/avro/pom.xml
+++ b/lang/java/avro/pom.xml
@@ -23,7 +23,7 @@
avro-parent
org.apache.avro
- 1.11.2-SNAPSHOT
+ 1.11.4-SNAPSHOT
../
diff --git a/lang/java/avro/src/main/java/org/apache/avro/Conversion.java b/lang/java/avro/src/main/java/org/apache/avro/Conversion.java
index 4ae75f4a5cb..934672e7d30 100644
--- a/lang/java/avro/src/main/java/org/apache/avro/Conversion.java
+++ b/lang/java/avro/src/main/java/org/apache/avro/Conversion.java
@@ -21,6 +21,9 @@
import java.nio.ByteBuffer;
import java.util.Collection;
import java.util.Map;
+import java.util.ServiceLoader;
+
+import org.apache.avro.generic.GenericData;
import org.apache.avro.generic.GenericEnumSymbol;
import org.apache.avro.generic.GenericFixed;
import org.apache.avro.generic.IndexedRecord;
@@ -28,23 +31,33 @@
/**
* Conversion between generic and logical type instances.
*
- * Instances of this class are added to GenericData to convert a logical type to
- * a particular representation.
+ * Instances of this class can be added to GenericData to convert a logical type
+ * to a particular representation. This can be done manually, using
+ * {@link GenericData#addLogicalTypeConversion(Conversion)}, or automatically.
+ * This last option uses the Java {@link ServiceLoader}, and requires the
+ * implementation to be a public class with a public no-arg constructor, be
+ * named in a file called {@code /META-INF/services/org.apache.avro.Conversion},
+ * and both must available in the classpath.
*
- * Implementations must provide: * {@link #getConvertedType()}: get the Java
- * class used for the logical type * {@link #getLogicalTypeName()}: get the
- * logical type this implements
+ * Implementations must provide:
+ *
+ * - {@link #getConvertedType()}: get the Java class used for the logical
+ * type
+ * - {@link #getLogicalTypeName()}: get the logical type this implements
+ *
*
- * Subclasses must also override all of the conversion methods for Avro's base
- * types that are valid for the logical type, or else risk causing
+ * Subclasses must also override the conversion methods for Avro's base types
+ * that are valid for the logical type, or else risk causing
* {@code UnsupportedOperationException} at runtime.
*
* Optionally, use {@link #getRecommendedSchema()} to provide a Schema that will
- * be used when a Schema is generated for the class returned by
- * {@code getConvertedType}.
+ * be used when generating a Schema for the class. This is useful when using
+ * {@code ReflectData} or {@code ProtobufData}, for example.
*
- * @param a Java type that generic data is converted to
+ * @param a Java type that can represent the named logical type
+ * @see ServiceLoader
*/
+@SuppressWarnings("unused")
public abstract class Conversion {
/**
@@ -65,9 +78,9 @@ public abstract class Conversion {
* Certain logical types may require adjusting the code within the "setter"
* methods to make sure the data that is set is properly formatted. This method
* allows the Conversion to generate custom setter code if required.
- *
- * @param varName
- * @param valParamName
+ *
+ * @param varName the name of the variable holding the converted value
+ * @param valParamName the name of the parameter with the new converted value
* @return a String for the body of the setter method
*/
public String adjustAndSetValue(String varName, String valParamName) {
@@ -102,7 +115,7 @@ public T fromCharSequence(CharSequence value, Schema schema, LogicalType type) {
throw new UnsupportedOperationException("fromCharSequence is not supported for " + type.getName());
}
- public T fromEnumSymbol(GenericEnumSymbol value, Schema schema, LogicalType type) {
+ public T fromEnumSymbol(GenericEnumSymbol> value, Schema schema, LogicalType type) {
throw new UnsupportedOperationException("fromEnumSymbol is not supported for " + type.getName());
}
@@ -150,7 +163,7 @@ public CharSequence toCharSequence(T value, Schema schema, LogicalType type) {
throw new UnsupportedOperationException("toCharSequence is not supported for " + type.getName());
}
- public GenericEnumSymbol toEnumSymbol(T value, Schema schema, LogicalType type) {
+ public GenericEnumSymbol> toEnumSymbol(T value, Schema schema, LogicalType type) {
throw new UnsupportedOperationException("toEnumSymbol is not supported for " + type.getName());
}
diff --git a/lang/java/avro/src/main/java/org/apache/avro/Conversions.java b/lang/java/avro/src/main/java/org/apache/avro/Conversions.java
index 1c28c9adb81..7d01fc62a37 100644
--- a/lang/java/avro/src/main/java/org/apache/avro/Conversions.java
+++ b/lang/java/avro/src/main/java/org/apache/avro/Conversions.java
@@ -106,11 +106,12 @@ public GenericFixed toFixed(BigDecimal value, Schema schema, LogicalType type) {
byte fillByte = (byte) (value.signum() < 0 ? 0xFF : 0x00);
byte[] unscaled = value.unscaledValue().toByteArray();
byte[] bytes = new byte[schema.getFixedSize()];
- int offset = bytes.length - unscaled.length;
+ int unscaledLength = unscaled.length;
+ int offset = bytes.length - unscaledLength;
- // Fill the front of the array and copy remaining with unscaled values
+ // Fill the front with the filler and copy the unscaled value into the remainder
Arrays.fill(bytes, 0, offset, fillByte);
- System.arraycopy(unscaled, 0, bytes, offset, bytes.length - offset);
+ System.arraycopy(unscaled, 0, bytes, offset, unscaledLength);
return new GenericData.Fixed(schema, bytes);
}
@@ -147,7 +148,7 @@ private static BigDecimal validate(final LogicalTypes.Decimal decimal, BigDecima
}
/**
- * Convert a underlying representation of a logical type (such as a ByteBuffer)
+ * Convert an underlying representation of a logical type (such as a ByteBuffer)
* to a higher level object (such as a BigDecimal).
*
* @param datum The object to be converted.
@@ -157,9 +158,9 @@ private static BigDecimal validate(final LogicalTypes.Decimal decimal, BigDecima
* @param conversion The tool used to finish the conversion. Cannot be null if
* datum is not null.
* @return The result object, which is a high level object of the logical type.
- * If a null datum is passed in, a null value will be returned.
- * @throws IllegalArgumentException if a null schema, type or conversion is
- * passed in while datum is not null.
+ * The null datum always converts to a null value.
+ * @throws IllegalArgumentException if datum is not null, but schema, type or
+ * conversion is.
*/
public static Object convertToLogicalType(Object datum, Schema schema, LogicalType type, Conversion> conversion) {
if (datum == null) {
@@ -176,9 +177,9 @@ public static Object convertToLogicalType(Object datum, Schema schema, LogicalTy
case RECORD:
return conversion.fromRecord((IndexedRecord) datum, schema, type);
case ENUM:
- return conversion.fromEnumSymbol((GenericEnumSymbol) datum, schema, type);
+ return conversion.fromEnumSymbol((GenericEnumSymbol>) datum, schema, type);
case ARRAY:
- return conversion.fromArray((Collection) datum, schema, type);
+ return conversion.fromArray((Collection>) datum, schema, type);
case MAP:
return conversion.fromMap((Map, ?>) datum, schema, type);
case FIXED:
@@ -201,13 +202,13 @@ public static Object convertToLogicalType(Object datum, Schema schema, LogicalTy
return datum;
} catch (ClassCastException e) {
throw new AvroRuntimeException(
- "Cannot convert " + datum + ":" + datum.getClass().getSimpleName() + ": expected generic type", e);
+ "Cannot convert " + datum + ':' + datum.getClass().getSimpleName() + ": expected generic type", e);
}
}
/**
* Convert a high level representation of a logical type (such as a BigDecimal)
- * to the its underlying representation object (such as a ByteBuffer)
+ * to its underlying representation object (such as a ByteBuffer)
*
* @param datum The object to be converted.
* @param schema The schema of datum. Cannot be null if datum is not null.
@@ -218,8 +219,8 @@ public static Object convertToLogicalType(Object datum, Schema schema, LogicalTy
* @return The result object, which is an underlying representation object of
* the logical type. If the input param datum is null, a null value will
* be returned.
- * @throws IllegalArgumentException if a null schema, type or conversion is
- * passed in while datum is not null.
+ * @throws IllegalArgumentException if datum is not null, but schema, type or
+ * conversion is.
*/
public static Object convertToRawType(Object datum, Schema schema, LogicalType type, Conversion conversion) {
if (datum == null) {
@@ -262,7 +263,7 @@ public static Object convertToRawType(Object datum, Schema schema, LogicalTy
return datum;
} catch (ClassCastException e) {
throw new AvroRuntimeException(
- "Cannot convert " + datum + ":" + datum.getClass().getSimpleName() + ": expected logical type", e);
+ "Cannot convert " + datum + ':' + datum.getClass().getSimpleName() + ": expected logical type", e);
}
}
diff --git a/lang/java/avro/src/main/java/org/apache/avro/Protocol.java b/lang/java/avro/src/main/java/org/apache/avro/Protocol.java
index ff996889517..e01a3c73ea9 100644
--- a/lang/java/avro/src/main/java/org/apache/avro/Protocol.java
+++ b/lang/java/avro/src/main/java/org/apache/avro/Protocol.java
@@ -274,15 +274,29 @@ public Protocol(Protocol p) {
public Protocol(String name, String doc, String namespace) {
super(PROTOCOL_RESERVED);
- this.name = name;
+ setName(name, namespace);
this.doc = doc;
- this.namespace = namespace;
}
public Protocol(String name, String namespace) {
this(name, null, namespace);
}
+ private void setName(String name, String namespace) {
+ int lastDot = name.lastIndexOf('.');
+ if (lastDot < 0) {
+ this.name = name;
+ this.namespace = namespace;
+ } else {
+ this.name = name.substring(lastDot + 1);
+ this.namespace = name.substring(0, lastDot);
+ }
+ if (this.namespace != null && this.namespace.isEmpty()) {
+ this.namespace = null;
+ }
+ types.space(this.namespace);
+ }
+
/** The name of this protocol. */
public String getName() {
return name;
@@ -488,20 +502,22 @@ private static Protocol parse(JsonParser parser) {
}
private void parse(JsonNode json) {
- parseNamespace(json);
- parseName(json);
+ parseNameAndNamespace(json);
parseTypes(json);
parseMessages(json);
parseDoc(json);
parseProps(json);
}
- private void parseNamespace(JsonNode json) {
- JsonNode nameNode = json.get("namespace");
- if (nameNode == null)
- return; // no namespace defined
- this.namespace = nameNode.textValue();
- types.space(this.namespace);
+ private void parseNameAndNamespace(JsonNode json) {
+ JsonNode nameNode = json.get("protocol");
+ if (nameNode == null) {
+ throw new SchemaParseException("No protocol name specified: " + json);
+ }
+ JsonNode namespaceNode = json.get("namespace");
+ String namespace = namespaceNode == null ? null : namespaceNode.textValue();
+
+ setName(nameNode.textValue(), namespace);
}
private void parseDoc(JsonNode json) {
@@ -515,13 +531,6 @@ private String parseDocNode(JsonNode json) {
return nameNode.textValue();
}
- private void parseName(JsonNode json) {
- JsonNode nameNode = json.get("protocol");
- if (nameNode == null)
- throw new SchemaParseException("No protocol name specified: " + json);
- this.name = nameNode.textValue();
- }
-
private void parseTypes(JsonNode json) {
JsonNode defs = json.get("types");
if (defs == null)
diff --git a/lang/java/avro/src/main/java/org/apache/avro/Schema.java b/lang/java/avro/src/main/java/org/apache/avro/Schema.java
index f6c3de7684e..ad6bf0ca61c 100644
--- a/lang/java/avro/src/main/java/org/apache/avro/Schema.java
+++ b/lang/java/avro/src/main/java/org/apache/avro/Schema.java
@@ -26,11 +26,13 @@
import com.fasterxml.jackson.databind.node.DoubleNode;
import com.fasterxml.jackson.databind.node.NullNode;
+import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.io.StringWriter;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -1285,8 +1287,7 @@ private static class FixedSchema extends NamedSchema {
public FixedSchema(Name name, String doc, int size) {
super(Type.FIXED, name, doc);
- if (size < 0)
- throw new IllegalArgumentException("Invalid fixed size: " + size);
+ SystemLimitException.checkMaxBytesLength(size);
this.size = size;
}
@@ -1429,7 +1430,7 @@ public boolean getValidateDefaults() {
* names known to this parser.
*/
public Schema parse(File file) throws IOException {
- return parse(FACTORY.createParser(file));
+ return parse(FACTORY.createParser(file), false);
}
/**
@@ -1437,7 +1438,7 @@ public Schema parse(File file) throws IOException {
* names known to this parser. The input stream stays open after the parsing.
*/
public Schema parse(InputStream in) throws IOException {
- return parse(FACTORY.createParser(in).disable(JsonParser.Feature.AUTO_CLOSE_SOURCE));
+ return parse(FACTORY.createParser(in).disable(JsonParser.Feature.AUTO_CLOSE_SOURCE), true);
}
/** Read a schema from one or more json strings */
@@ -1454,19 +1455,36 @@ public Schema parse(String s, String... more) {
*/
public Schema parse(String s) {
try {
- return parse(FACTORY.createParser(s));
+ return parse(FACTORY.createParser(s), false);
} catch (IOException e) {
throw new SchemaParseException(e);
}
}
- private Schema parse(JsonParser parser) throws IOException {
+ private Schema parse(JsonParser parser, boolean allowDanglingContent) throws IOException {
boolean saved = validateNames.get();
boolean savedValidateDefaults = VALIDATE_DEFAULTS.get();
try {
validateNames.set(validate);
VALIDATE_DEFAULTS.set(validateDefaults);
- return Schema.parse(MAPPER.readTree(parser), names);
+ JsonNode jsonNode = MAPPER.readTree(parser);
+ Schema schema = Schema.parse(jsonNode, names);
+ if (!allowDanglingContent) {
+ String dangling;
+ StringWriter danglingWriter = new StringWriter();
+ int numCharsReleased = parser.releaseBuffered(danglingWriter);
+ if (numCharsReleased == -1) {
+ ByteArrayOutputStream danglingOutputStream = new ByteArrayOutputStream();
+ parser.releaseBuffered(danglingOutputStream); // if input isnt chars above it must be bytes
+ dangling = new String(danglingOutputStream.toByteArray(), StandardCharsets.UTF_8).trim();
+ } else {
+ dangling = danglingWriter.toString().trim();
+ }
+ if (!dangling.isEmpty()) {
+ throw new SchemaParseException("dangling content after end of schema: " + dangling);
+ }
+ }
+ return schema;
} catch (JsonParseException e) {
throw new SchemaParseException(e);
} finally {
diff --git a/lang/java/avro/src/main/java/org/apache/avro/SystemLimitException.java b/lang/java/avro/src/main/java/org/apache/avro/SystemLimitException.java
new file mode 100644
index 00000000000..a96f812d84d
--- /dev/null
+++ b/lang/java/avro/src/main/java/org/apache/avro/SystemLimitException.java
@@ -0,0 +1,190 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.avro;
+
+import org.slf4j.LoggerFactory;
+
+/**
+ * Thrown to prevent making large allocations when reading potentially
+ * pathological input data from an untrusted source.
+ *
+ * The following system properties can be set to limit the size of bytes,
+ * strings and collection types to be allocated:
+ *
+ * - org.apache.avro.limits.byte.maxLength
limits the maximum
+ * size of byte types.
+ * - org.apache.avro.limits.collectionItems.maxLength
limits the
+ * maximum number of map and list items that can be read at
+ * once single sequence.
+ * - org.apache.avro.limits.string.maxLength
limits the maximum
+ * size of string types.
+ *
+ *
+ * The default is to permit sizes up to {@link #MAX_ARRAY_VM_LIMIT}.
+ */
+public class SystemLimitException extends AvroRuntimeException {
+
+ /**
+ * The maximum length of array to allocate (unless necessary). Some VMs reserve
+ * some header words in an array. Attempts to allocate larger arrays may result
+ * in {@code OutOfMemoryError: Requested array size exceeds VM limit}
+ *
+ * @see JDK-8246725
+ */
+ // VisibleForTesting
+ static final int MAX_ARRAY_VM_LIMIT = Integer.MAX_VALUE - 8;
+
+ public static final String MAX_BYTES_LENGTH_PROPERTY = "org.apache.avro.limits.bytes.maxLength";
+ public static final String MAX_COLLECTION_LENGTH_PROPERTY = "org.apache.avro.limits.collectionItems.maxLength";
+ public static final String MAX_STRING_LENGTH_PROPERTY = "org.apache.avro.limits.string.maxLength";
+
+ private static int maxBytesLength = MAX_ARRAY_VM_LIMIT;
+ private static int maxCollectionLength = MAX_ARRAY_VM_LIMIT;
+ private static int maxStringLength = MAX_ARRAY_VM_LIMIT;
+
+ static {
+ resetLimits();
+ }
+
+ public SystemLimitException(String message) {
+ super(message);
+ }
+
+ /**
+ * Get an integer value stored in a system property, used to configure the
+ * system behaviour of decoders
+ *
+ * @param property The system property to fetch
+ * @param defaultValue The value to use if the system property is not present or
+ * parsable as an int
+ * @return The value from the system property
+ */
+ private static int getLimitFromProperty(String property, int defaultValue) {
+ String o = System.getProperty(property);
+ int i = defaultValue;
+ if (o != null) {
+ try {
+ i = Integer.parseUnsignedInt(o);
+ } catch (NumberFormatException nfe) {
+ LoggerFactory.getLogger(SystemLimitException.class).warn("Could not parse property " + property + ": " + o,
+ nfe);
+ }
+ }
+ return i;
+ }
+
+ /**
+ * Check to ensure that reading the bytes is within the specified limits.
+ *
+ * @param length The proposed size of the bytes to read
+ * @return The size of the bytes if and only if it is within the limit and
+ * non-negative.
+ * @throws UnsupportedOperationException if reading the datum would allocate a
+ * collection that the Java VM would be
+ * unable to handle
+ * @throws SystemLimitException if the decoding should fail because it
+ * would otherwise result in an allocation
+ * exceeding the set limit
+ * @throws AvroRuntimeException if the length is negative
+ */
+ public static int checkMaxBytesLength(long length) {
+ if (length < 0) {
+ throw new AvroRuntimeException("Malformed data. Length is negative: " + length);
+ }
+ if (length > MAX_ARRAY_VM_LIMIT) {
+ throw new UnsupportedOperationException(
+ "Cannot read arrays longer than " + MAX_ARRAY_VM_LIMIT + " bytes in Java library");
+ }
+ if (length > maxBytesLength) {
+ throw new SystemLimitException("Bytes length " + length + " exceeds maximum allowed");
+ }
+ return (int) length;
+ }
+
+ /**
+ * Check to ensure that reading the specified number of items remains within the
+ * specified limits.
+ *
+ * @param existing The number of elements items read in the collection
+ * @param items The next number of items to read. In normal usage, this is
+ * always a positive, permitted value. Negative and zero values
+ * have a special meaning in Avro decoding.
+ * @return The total number of items in the collection if and only if it is
+ * within the limit and non-negative.
+ * @throws UnsupportedOperationException if reading the items would allocate a
+ * collection that the Java VM would be
+ * unable to handle
+ * @throws SystemLimitException if the decoding should fail because it
+ * would otherwise result in an allocation
+ * exceeding the set limit
+ * @throws AvroRuntimeException if the length is negative
+ */
+ public static int checkMaxCollectionLength(long existing, long items) {
+ long length = existing + items;
+ if (existing < 0) {
+ throw new AvroRuntimeException("Malformed data. Length is negative: " + existing);
+ }
+ if (items < 0) {
+ throw new AvroRuntimeException("Malformed data. Length is negative: " + items);
+ }
+ if (length > MAX_ARRAY_VM_LIMIT || length < existing) {
+ throw new UnsupportedOperationException(
+ "Cannot read collections larger than " + MAX_ARRAY_VM_LIMIT + " items in Java library");
+ }
+ if (length > maxCollectionLength) {
+ throw new SystemLimitException("Collection length " + length + " exceeds maximum allowed");
+ }
+ return (int) length;
+ }
+
+ /**
+ * Check to ensure that reading the string size is within the specified limits.
+ *
+ * @param length The proposed size of the string to read
+ * @return The size of the string if and only if it is within the limit and
+ * non-negative.
+ * @throws UnsupportedOperationException if reading the items would allocate a
+ * collection that the Java VM would be
+ * unable to handle
+ * @throws SystemLimitException if the decoding should fail because it
+ * would otherwise result in an allocation
+ * exceeding the set limit
+ * @throws AvroRuntimeException if the length is negative
+ */
+ public static int checkMaxStringLength(long length) {
+ if (length < 0) {
+ throw new AvroRuntimeException("Malformed data. Length is negative: " + length);
+ }
+ if (length > MAX_ARRAY_VM_LIMIT) {
+ throw new UnsupportedOperationException("Cannot read strings longer than " + MAX_ARRAY_VM_LIMIT + " bytes");
+ }
+ if (length > maxStringLength) {
+ throw new SystemLimitException("String length " + length + " exceeds maximum allowed");
+ }
+ return (int) length;
+ }
+
+ /** Reread the limits from the system properties. */
+ // VisibleForTesting
+ static void resetLimits() {
+ maxBytesLength = getLimitFromProperty(MAX_BYTES_LENGTH_PROPERTY, MAX_ARRAY_VM_LIMIT);
+ maxCollectionLength = getLimitFromProperty(MAX_COLLECTION_LENGTH_PROPERTY, MAX_ARRAY_VM_LIMIT);
+ maxStringLength = getLimitFromProperty(MAX_STRING_LENGTH_PROPERTY, MAX_ARRAY_VM_LIMIT);
+ }
+}
diff --git a/lang/java/avro/src/main/java/org/apache/avro/generic/GenericData.java b/lang/java/avro/src/main/java/org/apache/avro/generic/GenericData.java
index d33f8bbf018..875abc7d70d 100644
--- a/lang/java/avro/src/main/java/org/apache/avro/generic/GenericData.java
+++ b/lang/java/avro/src/main/java/org/apache/avro/generic/GenericData.java
@@ -26,15 +26,15 @@
import java.util.AbstractList;
import java.util.Arrays;
import java.util.Collection;
-import java.util.Collections;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.ServiceLoader;
import java.util.UUID;
-import java.util.WeakHashMap;
+import java.util.concurrent.ConcurrentMap;
import org.apache.avro.AvroMissingFieldException;
import org.apache.avro.AvroRuntimeException;
@@ -59,6 +59,9 @@
import org.apache.avro.util.internal.Accessor;
import com.fasterxml.jackson.databind.JsonNode;
+import org.apache.avro.util.springframework.ConcurrentReferenceHashMap;
+
+import static org.apache.avro.util.springframework.ConcurrentReferenceHashMap.ReferenceType.WEAK;
/**
* Utilities for generic Java data. See {@link GenericRecordBuilder} for a
@@ -115,6 +118,7 @@ public GenericData() {
/** For subclasses. GenericData does not use a ClassLoader. */
public GenericData(ClassLoader classLoader) {
this.classLoader = (classLoader != null) ? classLoader : getClass().getClassLoader();
+ loadConversions();
}
/** Return the class loader that's used (by subclasses). */
@@ -122,6 +126,17 @@ public ClassLoader getClassLoader() {
return classLoader;
}
+ /**
+ * Use the Java 6 ServiceLoader to load conversions.
+ *
+ * @see #addLogicalTypeConversion(Conversion)
+ */
+ private void loadConversions() {
+ for (Conversion> conversion : ServiceLoader.load(Conversion.class, classLoader)) {
+ addLogicalTypeConversion(conversion);
+ }
+ }
+
private Map> conversions = new HashMap<>();
private Map, Map>> conversionsByClass = new IdentityHashMap<>();
@@ -132,19 +147,17 @@ public Collection> getConversions() {
/**
* Registers the given conversion to be used when reading and writing with this
- * data model.
+ * data model. Conversions can also be registered automatically, as documented
+ * on the class {@link Conversion Conversion<T>}.
*
* @param conversion a logical type Conversion.
*/
public void addLogicalTypeConversion(Conversion> conversion) {
conversions.put(conversion.getLogicalTypeName(), conversion);
Class> type = conversion.getConvertedType();
- Map> conversions = conversionsByClass.get(type);
- if (conversions == null) {
- conversions = new LinkedHashMap<>();
- conversionsByClass.put(type, conversions);
- }
- conversions.put(conversion.getLogicalTypeName(), conversion);
+ Map> conversionsForClass = conversionsByClass.computeIfAbsent(type,
+ k -> new LinkedHashMap<>());
+ conversionsForClass.put(conversion.getLogicalTypeName(), conversion);
}
/**
@@ -185,11 +198,11 @@ public Conversion getConversionByClass(Class datumClass, LogicalType l
* @return the conversion for the logical type, or null
*/
@SuppressWarnings("unchecked")
- public Conversion