001/* 002 * This file is part of the Kompics component model runtime. 003 * 004 * Copyright (C) 2009 Swedish Institute of Computer Science (SICS) 005 * Copyright (C) 2009 Royal Institute of Technology (KTH) 006 * 007 * This program is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU General Public License 009 * as published by the Free Software Foundation; either version 2 010 * of the License, or (at your option) any later version. 011 * 012 * This program is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 015 * GNU General Public License for more details. 016 * 017 * You should have received a copy of the GNU General Public License 018 * along with this program; if not, write to the Free Software 019 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 020 */ 021package se.sics.kompics.network.netty.serialization; 022 023import java.util.Optional; 024import com.google.common.io.Closer; 025import io.netty.buffer.ByteBuf; 026import io.netty.buffer.ByteBufInputStream; 027import io.netty.buffer.ByteBufOutputStream; 028import java.io.IOException; 029import java.util.concurrent.ConcurrentHashMap; 030import java.util.concurrent.ConcurrentMap; 031import java.util.concurrent.ConcurrentSkipListMap; 032import org.apache.avro.Schema; 033import org.apache.avro.file.DataFileStream; 034import org.apache.avro.file.DataFileWriter; 035import org.apache.avro.generic.GenericContainer; 036import org.apache.avro.io.BinaryDecoder; 037import org.apache.avro.io.BinaryEncoder; 038import org.apache.avro.io.DatumReader; 039import org.apache.avro.io.DatumWriter; 040import org.apache.avro.io.DecoderFactory; 041import org.apache.avro.io.EncoderFactory; 042import org.apache.avro.reflect.ReflectData; 043import org.apache.avro.reflect.ReflectDatumReader; 044import org.apache.avro.reflect.ReflectDatumWriter; 045import org.apache.avro.specific.SpecificDatumReader; 046import org.apache.avro.specific.SpecificDatumWriter; 047import org.slf4j.Logger; 048import org.slf4j.LoggerFactory; 049import se.sics.kompics.network.netty.serialization.SpecialSerializers.BitBuffer; 050 051/** 052 * 053 * @author lkroll 054 */ 055public class AvroSerializer implements Serializer { 056 057 private static final Logger LOG = LoggerFactory.getLogger(AvroSerializer.class); 058 059 private static final int MY_ID = 7; 060 061 private static final ConcurrentMap<Integer, SchemaEntry> idMap = new ConcurrentSkipListMap<Integer, SchemaEntry>(); 062 private static final ConcurrentMap<String, SchemaEntry> classMap = new ConcurrentHashMap<String, SchemaEntry>(); 063 064 private static final ReflectData rData = ReflectData.get(); 065 066 public static synchronized void register(int id, Class<?> type) throws KeyExistsException, InvalidKeyException { 067 register(id, type, false); 068 } 069 070 public static synchronized void register(int id, Class<?> type, boolean force) 071 throws KeyExistsException, InvalidKeyException { 072 String typeName = type.getName(); 073 if (!force) { 074 // Check first once, since schema generation is expensive 075 if (idMap.containsKey(id)) { 076 throw new KeyExistsException(id); 077 } 078 if (classMap.containsKey(typeName)) { 079 throw new KeyExistsException(typeName); 080 } 081 } 082 if (id < 0) { 083 throw new InvalidKeyException(id); 084 } 085 Schema s = rData.getSchema(type); 086 SchemaEntry se = new SchemaEntry(s, type, id, false); 087 idMap.put(id, se); 088 classMap.put(typeName, se); 089 090 Serializers.register(type, MY_ID); 091 } 092 093 public static synchronized void register(int id, Class<?> type, Schema schema) 094 throws KeyExistsException, InvalidKeyException { 095 register(id, type, schema, false); 096 } 097 098 public static synchronized void register(int id, Class<?> type, Schema schema, boolean force) 099 throws KeyExistsException, InvalidKeyException { 100 String typeName = type.getName(); 101 if (!force) { 102 // Check first once, since schema generation is expensive 103 if (idMap.containsKey(id)) { 104 throw new KeyExistsException(id); 105 } 106 if (classMap.containsKey(typeName)) { 107 throw new KeyExistsException(typeName); 108 } 109 } 110 if (id < 0) { 111 throw new InvalidKeyException(id); 112 } 113 SchemaEntry se = new SchemaEntry(schema, type, id, true); 114 idMap.put(id, se); 115 classMap.put(typeName, se); 116 117 Serializers.register(type, MY_ID); 118 } 119 120 @Override 121 public int identifier() { 122 return MY_ID; 123 } 124 125 @Override 126 public void toBinary(Object o, ByteBuf buf) { 127 Class<?> type = o.getClass(); 128 String typeName = type.getName(); 129 SchemaEntry se = classMap.get(typeName); 130 if (se == null) { 131 toBinaryNoSchema(o, type, buf); 132 } else { 133 toBinaryWithSchema(o, se, buf); 134 } 135 } 136 137 private void toBinaryWithSchema(Object o, SchemaEntry se, ByteBuf buf) { 138 BitBuffer flags; 139 if (se.generated) { 140 flags = BitBuffer.create(true, true); // with schema and generated 141 } else { 142 flags = BitBuffer.create(true, false); // with schema but not generated 143 } 144 byte[] flagsB = flags.finalise(); 145 buf.writeBytes(flagsB); 146 buf.writeInt(se.id); 147 try { 148 Closer closer = Closer.create(); // Did I mention how much java6 sucks? 149 try { 150 ByteBufOutputStream out = closer.register(new ByteBufOutputStream(buf)); 151 BinaryEncoder encoder = EncoderFactory.get().directBinaryEncoder(out, null); 152 DatumWriter<Object> writer; 153 if (se.generated) { 154 writer = new SpecificDatumWriter<Object>(se.schema); 155 } else { 156 writer = new ReflectDatumWriter<Object>(se.schema); 157 } 158 writer.write(o, encoder); 159 encoder.flush(); 160 } catch (Throwable ex) { 161 LOG.error("Couldn't serialise object.", ex); 162 closer.rethrow(ex); 163 } finally { 164 closer.close(); 165 } 166 } catch (IOException ex) { 167 LOG.error("Couldn't serialise object.", ex); 168 } 169 } 170 171 private void toBinaryNoSchema(Object o, Class<?> type, ByteBuf buf) { 172 LOG.info( 173 "Prepending schema to object of type {}. This is not efficient. It's recommended to register the class instead.", 174 type); 175 Schema s; 176 BitBuffer flags; 177 DatumWriter<Object> refWriter; 178 if (o instanceof GenericContainer) { 179 GenericContainer ag = (GenericContainer) o; 180 s = ag.getSchema(); 181 flags = BitBuffer.create(false, true); // no schema and generated 182 refWriter = new SpecificDatumWriter<>(s); 183 } else { 184 s = rData.getSchema(type); 185 flags = BitBuffer.create(false, false); // no schema and not generated 186 refWriter = new ReflectDatumWriter<>(s); 187 } 188 byte[] flagsB = flags.finalise(); 189 buf.writeBytes(flagsB); 190 try { 191 Closer closer = Closer.create(); // Did I mention how much java6 sucks? 192 try { 193 ByteBufOutputStream out = closer.register(new ByteBufOutputStream(buf)); 194 @SuppressWarnings("resource") 195 DataFileWriter<Object> writer = closer.register(new DataFileWriter<>(refWriter).create(s, out)); 196 writer.append(o); 197 writer.flush(); 198 } catch (Throwable ex) { 199 LOG.error("Couldn't serialise object.", ex); 200 closer.rethrow(ex); 201 } finally { 202 closer.close(); 203 } 204 } catch (IOException ex) { 205 LOG.error("Couldn't serialise object.", ex); 206 } 207 } 208 209 @Override 210 public Object fromBinary(ByteBuf buf, Optional<Object> hint) { 211 byte[] flagsB = new byte[1]; 212 buf.readBytes(flagsB); 213 boolean[] flags = BitBuffer.extract(2, flagsB); 214 boolean registered = flags[0]; 215 boolean generated = flags[1]; 216 if (registered) { 217 int id = buf.readInt(); 218 SchemaEntry se = idMap.get(id); 219 if (se == null) { 220 LOG.warn("Could not deserialize object for id {}! Not registered!", id); 221 return null; 222 } 223 return fromBinaryWithSchema(buf, se, generated); 224 } else { 225 return fromBinaryNoSchema(buf, generated); 226 } 227 } 228 229 private Object fromBinaryNoSchema(ByteBuf buf, boolean generated) { 230 DatumReader<Object> refReader; 231 if (generated) { 232 refReader = new SpecificDatumReader<>(); 233 } else { 234 refReader = new ReflectDatumReader<>(); 235 } 236 try { 237 Closer closer = Closer.create(); 238 try { 239 ByteBufInputStream in = closer.register(new ByteBufInputStream(buf)); 240 DataFileStream<Object> reader = closer.register(new DataFileStream<>(in, refReader)); 241 return reader.next(); // there should only be one 242 } catch (Throwable ex) { 243 LOG.error("Couldn't deserialise object.", ex); 244 closer.rethrow(ex); 245 return null; 246 } finally { 247 closer.close(); 248 } 249 } catch (IOException ex) { 250 LOG.error("Couldn't deserialise object.", ex); 251 return null; 252 } 253 } 254 255 private Object fromBinaryWithSchema(ByteBuf buf, SchemaEntry se, boolean generated) { 256 DatumReader<Object> refReader; 257 if (generated) { 258 refReader = new SpecificDatumReader<>(se.schema); 259 } else { 260 refReader = new ReflectDatumReader<>(se.schema); 261 } 262 try { 263 Closer closer = Closer.create(); 264 try { 265 ByteBufInputStream in = closer.register(new ByteBufInputStream(buf)); 266 BinaryDecoder decoder = DecoderFactory.get().directBinaryDecoder(in, null); 267 return refReader.read(null, decoder); // there should only be one 268 } catch (Throwable ex) { 269 LOG.error("Couldn't deserialise object.", ex); 270 closer.rethrow(ex); 271 return null; 272 } finally { 273 closer.close(); 274 } 275 } catch (IOException ex) { 276 LOG.error("Couldn't deserialise object.", ex); 277 return null; 278 } 279 } 280 281 private static class SchemaEntry { 282 283 public final Schema schema; 284 @SuppressWarnings("unused") 285 public final Class<?> type; 286 public final int id; 287 public final boolean generated; 288 289 public SchemaEntry(Schema schema, Class<?> type, int id, boolean generated) { 290 this.schema = schema; 291 this.type = type; 292 this.id = id; 293 this.generated = generated; 294 } 295 } 296 297 @SuppressWarnings("serial") 298 public static class KeyExistsException extends Exception { 299 300 private final Object key; 301 302 public KeyExistsException(Object key) { 303 this.key = key; 304 } 305 306 @Override 307 public String getMessage() { 308 return "Key " + key + " already exists!"; 309 } 310 } 311 312 @SuppressWarnings("serial") 313 public static class InvalidKeyException extends Exception { 314 315 private final int key; 316 317 public InvalidKeyException(int key) { 318 this.key = key; 319 } 320 321 @Override 322 public String getMessage() { 323 return "Key " + key + " is invalid! Must be positive integer."; 324 } 325 } 326}