package org.infinispan.tools.jdbc.migrator.marshaller;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.infinispan.commons.CacheConfigurationException;
import org.infinispan.commons.io.ByteBufferImpl;
import org.infinispan.commons.io.UnsignedNumeric;
import org.infinispan.commons.marshall.AdvancedExternalizer;
import org.infinispan.commons.marshall.StreamingMarshaller;
import org.infinispan.commons.util.ImmutableListCopy;
import org.infinispan.commons.util.Immutables;
import org.infinispan.container.entries.ImmortalCacheEntry;
import org.infinispan.container.entries.ImmortalCacheValue;
import org.infinispan.container.entries.MortalCacheEntry;
import org.infinispan.container.entries.MortalCacheValue;
import org.infinispan.container.entries.TransientCacheEntry;
import org.infinispan.container.entries.TransientCacheValue;
import org.infinispan.container.entries.TransientMortalCacheEntry;
import org.infinispan.container.entries.TransientMortalCacheValue;
import org.infinispan.container.entries.metadata.MetadataImmortalCacheEntry;
import org.infinispan.container.entries.metadata.MetadataImmortalCacheValue;
import org.infinispan.container.entries.metadata.MetadataMortalCacheEntry;
import org.infinispan.container.entries.metadata.MetadataMortalCacheValue;
import org.infinispan.container.entries.metadata.MetadataTransientCacheEntry;
import org.infinispan.container.entries.metadata.MetadataTransientCacheValue;
import org.infinispan.container.entries.metadata.MetadataTransientMortalCacheEntry;
import org.infinispan.container.entries.metadata.MetadataTransientMortalCacheValue;
import org.infinispan.container.versioning.NumericVersion;
import org.infinispan.container.versioning.SimpleClusteredVersion;
import org.infinispan.marshall.core.MarshalledEntryImpl;
import org.infinispan.marshall.exts.EnumSetExternalizer;
import org.infinispan.marshall.exts.MapExternalizer;
import org.infinispan.metadata.EmbeddedMetadata;
import org.infinispan.metadata.impl.InternalMetadataImpl;
import org.infinispan.tools.jdbc.migrator.marshaller.externalizers.ArrayExternalizers;
import org.infinispan.tools.jdbc.migrator.marshaller.externalizers.ImmutableListCopyExternalizer;
import org.infinispan.tools.jdbc.migrator.marshaller.externalizers.LegacyIds;
import org.infinispan.tools.jdbc.migrator.marshaller.externalizers.ListExternalizer;
import org.infinispan.tools.jdbc.migrator.marshaller.externalizers.SetExternalizer;
import org.infinispan.tools.jdbc.migrator.marshaller.externalizers.SingletonListExternalizer;
import org.infinispan.util.KeyValuePair;
import org.infinispan.util.logging.Log;
import org.infinispan.util.logging.LogFactory;
import org.jboss.marshalling.ObjectTable;
import org.jboss.marshalling.Unmarshaller;
/**
* The externalizer table maintains information necessary to be able to map a particular type with the corresponding
* {@link org.infinispan.commons.marshall.AdvancedExternalizer} implementation that it marshall, and it also keeps
* information of which {@link org.infinispan.commons.marshall.AdvancedExternalizer} should be used to read data from a
* buffer given a particular {@link org.infinispan.commons.marshall.AdvancedExternalizer} identifier.
* <p>
* These tables govern how either internal Infinispan classes, or user defined classes, are marshalled to a given
* output, or how these are unmarshalled from a given input.
*
* @author Galder ZamarreƱo
* @since 5.0
*/
class ExternalizerTable implements ObjectTable {
private static final Log log = LogFactory.getLog(ExternalizerTable.class);
private static final int MAX_ID = 255;
private final Map<Integer, ExternalizerAdapter> readers = new HashMap<>();
private final StreamingMarshaller globalMarshaller;
ExternalizerTable(StreamingMarshaller globalMarshaller, Map<Integer, ? extends AdvancedExternalizer<?>> externalizerMap) {
this.globalMarshaller = globalMarshaller;
loadInternalMarshallables();
initForeignMarshallables(externalizerMap);
}
@Override
public Writer getObjectWriter(Object o) throws IOException {
return null;
}
@Override
public Object readObject(Unmarshaller input) throws IOException, ClassNotFoundException {
int readerIndex = input.readUnsignedByte();
int foreignId = -1;
if (readerIndex == MAX_ID) {
// User defined externalizer
foreignId = UnsignedNumeric.readUnsignedInt(input);
readerIndex = generateForeignReaderIndex(foreignId);
} else {
Integer legacyId = LegacyIds.LEGACY_MAP.get(readerIndex);
if (legacyId != null)
readerIndex = legacyId;
}
ExternalizerAdapter adapter = readers.get(readerIndex);
if (adapter == null) {
if (foreignId > 0)
throw log.missingForeignExternalizer(foreignId);
throw log.unknownExternalizerReaderIndex(readerIndex);
}
return adapter.externalizer.readObject(input);
}
private void loadInternalMarshallables() {
addInternalExternalizer(new ListExternalizer());
addInternalExternalizer(new MapExternalizer());
addInternalExternalizer(new SetExternalizer());
addInternalExternalizer(new EnumSetExternalizer());
addInternalExternalizer(new ArrayExternalizers.ListArray());
addInternalExternalizer(new SingletonListExternalizer());
addInternalExternalizer(new ImmutableListCopyExternalizer());
addInternalExternalizer(new Immutables.ImmutableMapWrapperExternalizer());
addInternalExternalizer(new ByteBufferImpl.Externalizer());
addInternalExternalizer(new NumericVersion.Externalizer());
addInternalExternalizer(new ByteBufferImpl.Externalizer());
addInternalExternalizer(new KeyValuePair.Externalizer());
addInternalExternalizer(new InternalMetadataImpl.Externalizer());
addInternalExternalizer(new MarshalledEntryImpl.Externalizer(globalMarshaller));
addInternalExternalizer(new ImmortalCacheEntry.Externalizer());
addInternalExternalizer(new MortalCacheEntry.Externalizer());
addInternalExternalizer(new TransientCacheEntry.Externalizer());
addInternalExternalizer(new TransientMortalCacheEntry.Externalizer());
addInternalExternalizer(new ImmortalCacheValue.Externalizer());
addInternalExternalizer(new MortalCacheValue.Externalizer());
addInternalExternalizer(new TransientCacheValue.Externalizer());
addInternalExternalizer(new TransientMortalCacheValue.Externalizer());
addInternalExternalizer(new SimpleClusteredVersion.Externalizer());
addInternalExternalizer(new MetadataImmortalCacheEntry.Externalizer());
addInternalExternalizer(new MetadataMortalCacheEntry.Externalizer());
addInternalExternalizer(new MetadataTransientCacheEntry.Externalizer());
addInternalExternalizer(new MetadataTransientMortalCacheEntry.Externalizer());
addInternalExternalizer(new MetadataImmortalCacheValue.Externalizer());
addInternalExternalizer(new MetadataMortalCacheValue.Externalizer());
addInternalExternalizer(new MetadataTransientCacheValue.Externalizer());
addInternalExternalizer(new MetadataTransientMortalCacheValue.Externalizer());
addInternalExternalizer(new EmbeddedMetadata.Externalizer());
}
private void addInternalExternalizer(AdvancedExternalizer<?> ext) {
int id = checkInternalIdLimit(ext.getId(), ext);
updateExtReadersWithTypes(new ExternalizerAdapter(id, ext));
}
private void updateExtReadersWithTypes(ExternalizerAdapter adapter) {
updateExtReadersWithTypes(adapter, adapter.id);
}
private void updateExtReadersWithTypes(ExternalizerAdapter adapter, int readerIndex) {
Set<Class<?>> typeClasses = adapter.externalizer.getTypeClasses();
if (typeClasses.size() > 0) {
for (Class<?> typeClass : typeClasses)
updateExtReaders(adapter, typeClass, readerIndex);
} else {
throw log.advanceExternalizerTypeClassesUndefined(adapter.externalizer.getClass().getName());
}
}
private void initForeignMarshallables(Map<Integer, ? extends AdvancedExternalizer<?>> externalizerMap) {
for (Map.Entry<Integer, ? extends AdvancedExternalizer<?>> entry : externalizerMap.entrySet()) {
AdvancedExternalizer<?> ext = entry.getValue();
Integer id = ext.getId();
if (entry.getKey() == null && id == null)
throw new CacheConfigurationException(String.format(
"No advanced externalizer identifier set for externalizer %s",
ext.getClass().getName()));
else if (entry.getKey() != null)
id = entry.getKey();
id = checkForeignIdLimit(id, ext);
updateExtReadersWithTypes(new ExternalizerAdapter(id, ext), generateForeignReaderIndex(id));
}
}
private void updateExtReaders(ExternalizerAdapter adapter, Class<?> typeClass, int readerIndex) {
ExternalizerAdapter prevReader = readers.put(readerIndex, adapter);
// Several externalizers might share same id (i.e. HashMap and TreeMap use MapExternalizer)
// but a duplicate is only considered when that particular index has already been entered
// in the readers map and the externalizers are different (they're from different classes)
if (prevReader != null && !prevReader.equals(adapter))
throw log.duplicateExternalizerIdFound(adapter.id, typeClass, prevReader.externalizer.getClass().getName(), readerIndex);
}
private int checkInternalIdLimit(int id, AdvancedExternalizer<?> ext) {
if (id >= MAX_ID)
throw log.internalExternalizerIdLimitExceeded(ext, id, MAX_ID);
return id;
}
private int checkForeignIdLimit(int id, AdvancedExternalizer<?> ext) {
if (id < 0)
throw log.foreignExternalizerUsingNegativeId(ext, id);
return id;
}
private int generateForeignReaderIndex(int foreignId) {
return 0x80000000 | foreignId;
}
private static class ExternalizerAdapter {
final int id;
final AdvancedExternalizer<Object> externalizer;
ExternalizerAdapter(int id, AdvancedExternalizer<?> externalizer) {
this.id = id;
this.externalizer = (AdvancedExternalizer<Object>) externalizer;
}
@Override
public String toString() {
// Each adapter is represented by the externalizer it delegates to, so just return the class name
return externalizer.getClass().getName();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ExternalizerAdapter that = (ExternalizerAdapter) o;
if (id != that.id) return false;
if (externalizer != null ? !externalizer.getClass().equals(that.externalizer.getClass()) : that.externalizer != null)
return false;
return true;
}
@Override
public int hashCode() {
int result = id;
result = 31 * result + (externalizer.getClass() != null ? externalizer.getClass().hashCode() : 0);
return result;
}
}
}