2525import org .apache .avro .generic .GenericData ;
2626import org .apache .avro .generic .GenericFixed ;
2727
28+ import java .lang .reflect .Constructor ;
2829import java .util .Map ;
2930
3031/**
@@ -42,12 +43,12 @@ public class ConvertingGenericData extends GenericData {
4243 private static final TimeConversions .TimeMicrosConversion TIME_MICROS_CONVERSION = new TimeConversions .TimeMicrosConversion ();
4344 private static final TimeConversions .TimestampMicrosConversion TIMESTAMP_MICROS_CONVERSION = new TimeConversions .TimestampMicrosConversion ();
4445
45- // NOTE: Those are not supported in Avro 1.8.2
46- // TODO re-enable upon upgrading to 1.10
47- private static final TimeConversions . TimestampMillisConversion TIMESTAMP_MILLIS_CONVERSION = new TimeConversions . TimestampMillisConversion ( );
48- private static final TimeConversions . TimeMillisConversion TIME_MILLIS_CONVERSION = new TimeConversions . TimeMillisConversion ( );
49- private static final TimeConversions . LocalTimestampMillisConversion LOCAL_TIMESTAMP_MILLIS_CONVERSION = new TimeConversions . LocalTimestampMillisConversion ( );
50- private static final TimeConversions . LocalTimestampMicrosConversion LOCAL_TIMESTAMP_MICROS_CONVERSION = new TimeConversions . LocalTimestampMicrosConversion ( );
46+ // NOTE: Those are not supported in Avro 1.8.2 (used by Spark 2)
47+ // Use reflection to conditionally initialize them only if available
48+ private static final Object TIMESTAMP_MILLIS_CONVERSION = createConversionIfAvailable ( "org.apache.avro.data.TimeConversions$ TimestampMillisConversion" );
49+ private static final Object TIME_MILLIS_CONVERSION = createConversionIfAvailable ( "org.apache.avro.data.TimeConversions$ TimeMillisConversion" );
50+ private static final Object LOCAL_TIMESTAMP_MILLIS_CONVERSION = createConversionIfAvailable ( "org.apache.avro.data.TimeConversions$ LocalTimestampMillisConversion" );
51+ private static final Object LOCAL_TIMESTAMP_MICROS_CONVERSION = createConversionIfAvailable ( "org.apache.avro.data.TimeConversions$ LocalTimestampMicrosConversion" );
5152 public static final GenericData INSTANCE = new ConvertingGenericData ();
5253
5354 private ConvertingGenericData () {
@@ -56,11 +57,20 @@ private ConvertingGenericData() {
5657 addLogicalTypeConversion (DATE_CONVERSION );
5758 addLogicalTypeConversion (TIME_MICROS_CONVERSION );
5859 addLogicalTypeConversion (TIMESTAMP_MICROS_CONVERSION );
59- // NOTE: Those are not supported in Avro 1.8.2
60- addLogicalTypeConversion (TIME_MILLIS_CONVERSION );
61- addLogicalTypeConversion (TIMESTAMP_MILLIS_CONVERSION );
62- addLogicalTypeConversion (LOCAL_TIMESTAMP_MILLIS_CONVERSION );
63- addLogicalTypeConversion (LOCAL_TIMESTAMP_MICROS_CONVERSION );
60+ // NOTE: Those are not supported in Avro 1.8.2 (used by Spark 2)
61+ // Only add conversions if they're available
62+ if (TIME_MILLIS_CONVERSION != null ) {
63+ addLogicalTypeConversionReflectively (TIME_MILLIS_CONVERSION );
64+ }
65+ if (TIMESTAMP_MILLIS_CONVERSION != null ) {
66+ addLogicalTypeConversionReflectively (TIMESTAMP_MILLIS_CONVERSION );
67+ }
68+ if (LOCAL_TIMESTAMP_MILLIS_CONVERSION != null ) {
69+ addLogicalTypeConversionReflectively (LOCAL_TIMESTAMP_MILLIS_CONVERSION );
70+ }
71+ if (LOCAL_TIMESTAMP_MICROS_CONVERSION != null ) {
72+ addLogicalTypeConversionReflectively (LOCAL_TIMESTAMP_MICROS_CONVERSION );
73+ }
6474 }
6575
6676 @ Override
@@ -123,12 +133,31 @@ public boolean validate(Schema schema, Object datum) {
123133 return isInteger (datum )
124134 || DATE_CONVERSION .getConvertedType ().isInstance (datum );
125135 case LONG :
126- return isLong (datum )
127- || TIME_MICROS_CONVERSION .getConvertedType ().isInstance (datum )
128- || TIMESTAMP_MICROS_CONVERSION .getConvertedType ().isInstance (datum )
129- || TIMESTAMP_MILLIS_CONVERSION .getConvertedType ().isInstance (datum )
130- || LOCAL_TIMESTAMP_MICROS_CONVERSION .getConvertedType ().isInstance (datum )
131- || LOCAL_TIMESTAMP_MILLIS_CONVERSION .getConvertedType ().isInstance (datum );
136+ if (isLong (datum )) {
137+ return true ;
138+ }
139+ if (TIME_MICROS_CONVERSION .getConvertedType ().isInstance (datum )
140+ || TIMESTAMP_MICROS_CONVERSION .getConvertedType ().isInstance (datum )) {
141+ return true ;
142+ }
143+ // Check optional conversions that may not be available in Avro 1.8.2
144+ Class <?> convertedType ;
145+ if (TIMESTAMP_MILLIS_CONVERSION != null
146+ && (convertedType = getConvertedType (TIMESTAMP_MILLIS_CONVERSION )) != null
147+ && convertedType .isInstance (datum )) {
148+ return true ;
149+ }
150+ if (LOCAL_TIMESTAMP_MICROS_CONVERSION != null
151+ && (convertedType = getConvertedType (LOCAL_TIMESTAMP_MICROS_CONVERSION )) != null
152+ && convertedType .isInstance (datum )) {
153+ return true ;
154+ }
155+ if (LOCAL_TIMESTAMP_MILLIS_CONVERSION != null
156+ && (convertedType = getConvertedType (LOCAL_TIMESTAMP_MILLIS_CONVERSION )) != null
157+ && convertedType .isInstance (datum )) {
158+ return true ;
159+ }
160+ return false ;
132161 case FLOAT :
133162 return isFloat (datum );
134163 case DOUBLE :
@@ -141,5 +170,43 @@ public boolean validate(Schema schema, Object datum) {
141170 return false ;
142171 }
143172 }
173+
174+ /**
175+ * Creates a conversion instance using reflection if the class is available.
176+ * Returns null if the class doesn't exist (e.g., in Avro 1.8.2).
177+ */
178+ private static Object createConversionIfAvailable (String className ) {
179+ try {
180+ Class <?> clazz = Class .forName (className );
181+ Constructor <?> constructor = clazz .getConstructor ();
182+ return constructor .newInstance ();
183+ } catch (ClassNotFoundException | NoSuchMethodException | InstantiationException
184+ | IllegalAccessException | java .lang .reflect .InvocationTargetException e ) {
185+ // Class doesn't exist or can't be instantiated (e.g., Avro 1.8.2)
186+ return null ;
187+ }
188+ }
189+
190+ /**
191+ * Gets the converted type from a conversion object using reflection.
192+ */
193+ private static Class <?> getConvertedType (Object conversion ) {
194+ try {
195+ return (Class <?>) conversion .getClass ().getMethod ("getConvertedType" ).invoke (conversion );
196+ } catch (Exception e ) {
197+ // Should not happen if conversion is valid, but handle gracefully
198+ return null ;
199+ }
200+ }
201+
202+ /**
203+ * Adds a logical type conversion using unchecked cast to avoid compile-time dependency
204+ * on classes that may not exist in older Avro versions.
205+ */
206+ private void addLogicalTypeConversionReflectively (Object conversion ) {
207+ // Cast to Conversion<?> since we know it's a Conversion if not null
208+ // This avoids compile-time dependency on specific Conversion subclasses
209+ addLogicalTypeConversion ((org .apache .avro .Conversion <?>) conversion );
210+ }
144211}
145212
0 commit comments