/* * Copyright (c) 2014 Oculus Info Inc. * http://www.oculusinfo.com/ * * Released under the MIT License. * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in * the Software without restriction, including without limitation the rights to * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies * of the Software, and to permit persons to whom the Software is furnished to do * so, subject to the following conditions: * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.oculusinfo.binning.io.serialization.impl; import java.io.IOException; import java.nio.ByteBuffer; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Set; import org.apache.avro.Schema; import org.apache.avro.file.CodecFactory; import org.apache.avro.generic.GenericRecord; import com.oculusinfo.binning.io.serialization.GenericAvroSerializer; import com.oculusinfo.binning.util.TypeDescriptor; /** * A serializer to serialize tiles whose bin values are any Avro primitive type. * * The Avro primitives are: boolean, int, long, float, double, bytes, and string, * which correspond to Java Boolean, Integer, Long, Float, Double, * java.nio.ByteBuffer, and org.apache.avro.util.Utf8. We do a bit of a hack to * allow use of the much simpler String instead of Utf8, but the others stand as * is. Attempting to create a version with any other class will result in a * run-time error. */ public class PrimitiveAvroSerializer<T> extends GenericAvroSerializer<T> { private static final long serialVersionUID = 4949141562108321166L; private static final Map<Class<?>, String> VALID_PRIMITIVE_TYPES = Collections.unmodifiableMap(new HashMap<Class<?>, String>() { private static final long serialVersionUID = 1L; { put(Boolean.class, "boolean"); put(Integer.class, "int"); put(Long.class, "long"); put(Float.class, "float"); put(Double.class, "double"); put(ByteBuffer.class, "bytes"); put(String.class, "string"); } }); public static final Set<Class<?>> PRIMITIVE_TYPES = VALID_PRIMITIVE_TYPES.keySet(); /** * Determines if a class represents a valid Avro primitive type */ static <T> boolean isValidPrimitive (Class<? extends T> type) { return VALID_PRIMITIVE_TYPES.containsKey(type); } /** * Get the avro string type of a valid primitive type */ public static <T> String getAvroType (Class<? extends T> type) { return VALID_PRIMITIVE_TYPES.get(type); } /** * Wrap a primitive type in a type descriptor, making sure while doing so that it * actually is a primitive type. */ static <T> TypeDescriptor getPrimitiveTypeDescriptor (Class<? extends T> type) { if (!isValidPrimitive(type)) throw new IllegalArgumentException("Invalid type "+type+ ": Not one of the prescribed primitives allowed."); return new TypeDescriptor(type); } private static PatternedSchemaStore __schemaStore = new PatternedSchemaStore( "{\n" + " \"name\":\"recordType\",\n" + " \"namespace\":\"ar.avro\",\n" + " \"type\":\"record\",\n" + " \"fields\":[\n" + " {\"name\":\"value\", \"type\":\"%s\"}\n" + " ]\n" + "}"); private Class<? extends T> _type; private transient Schema _schema = null; // A bit of a hack to handle string tiles as strings rather than Utf8s private boolean _toString; public PrimitiveAvroSerializer (Class<? extends T> type, CodecFactory compressionCodec) { super(compressionCodec, getPrimitiveTypeDescriptor(type)); _type = type; _toString = (String.class.equals(type)); } @Override protected String getRecordSchemaFile () { throw new UnsupportedOperationException("Primitive types have standard schema; schema files should not be required."); } @Override protected Schema createRecordSchema () throws IOException { if (null == _schema) { String typeName = getAvroType(_type); _schema = __schemaStore.getSchema(_type, typeName); } return _schema; } // This doesn't need to be checked because // (a) One can't create a serializer for which it theoreticallly won't work. // (b) It is possible to use the wrong serializer for a given tile, in which // case it will fail - but it should fail in that case. @SuppressWarnings("unchecked") @Override protected T getValue (GenericRecord bin) { if (_toString) { // A bit of a hack to handle string tiles as strings rather than Utf8s return (T) bin.get("value").toString(); } else { if (null == bin || null == bin.get("value")) { return null; } return (T) bin.get("value"); } } @Override protected void setValue (GenericRecord bin, T value) throws IOException { if (null == value) throw new IOException("Null value for bin"); bin.put("value", value); } }