package io.muoncore.codec;
import io.muoncore.exception.MuonEncodingException;
import lombok.extern.slf4j.Slf4j;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Type;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
@Slf4j
public class DelegatingCodecs implements Codecs {
private Map<String, MuonCodec> codecLookup = new HashMap<>();
private Map<String, Codecs> codecsLookup = new HashMap<>();
public DelegatingCodecs withCodecs(Codecs codecs) {
for (String content : codecs.getAvailableCodecs()) {
codecsLookup.put(content, codecs);
}
return this;
}
public DelegatingCodecs withCodec(MuonCodec codecs) {
codecLookup.put(codecs.getContentType(), codecs);
return this;
}
@Override
public <T> EncodingResult encode(T object, String[] acceptableContentTypes) {
log.trace("Encoding {}/{} with content type {}", object, object.getClass(), acceptableContentTypes);
for (int i = acceptableContentTypes.length - 1; i >= 0; i--) {
MuonCodec specificCodec = codecLookup.get(acceptableContentTypes[i]);
if(specificCodec != null && specificCodec.canEncode(object.getClass())) {
try {
return new EncodingResult(specificCodec.encode(object), specificCodec.getContentType());
} catch (UnsupportedEncodingException e) {
log.error("Error encoding " + object + " using codec " + specificCodec, e);
}
}
}
for (int i = acceptableContentTypes.length - 1; i >= 0; i--) {
Codecs delegateCodecs = codecsLookup.get(acceptableContentTypes[i]);
if(delegateCodecs != null) {
return delegateCodecs.encode(object, acceptableContentTypes);
}
}
return new EncodingResult(new MuonEncodingException("Unable to encode object of type " + object.getClass() + ", no codec can handle " + Arrays.asList(acceptableContentTypes)));
}
@Override
public <T> T decode(byte[] source, String contentType, Type type) throws DecodingFailureException {
log.trace("Decoding {} with content type {} {}", type, contentType, source);
return getCodec(contentType, muonCodec -> muonCodec.decode(source, type), codecs -> codecs.decode(source, contentType, type));
}
private <T> T getCodec(String contentType, Function<MuonCodec, T> execWithCodec, Function<Codecs, T> codecs) {
MuonCodec specificCodec = codecLookup.get(contentType);
if (specificCodec != null) {
return execWithCodec.apply(specificCodec);
}
Codecs delegateCodecs = codecsLookup.get(contentType);
if (delegateCodecs != null) {
return codecs.apply(delegateCodecs);
}
log.error("Unable to decode content type {}, this is a serious misconfiguration and data is being lost", contentType);
return null;
}
@Override
public String[] getAvailableCodecs() {
Set<String> codecs = codecLookup.values().stream().map(MuonCodec::getContentType).collect(Collectors.toSet());
codecs.addAll(codecsLookup.values().stream().map((Codecs::getAvailableCodecs)).flatMap(Arrays::stream).collect(Collectors.toSet()));
return codecs.toArray(new String[codecs.size()]);
}
@Override
public Optional<SchemaInfo> getSchemaFor(Class type) {
Optional<MuonCodec> codec = codecLookup.values().stream().filter(muonCodec -> muonCodec.hasSchemasFor(type)).findAny();
log.trace
("Getting schema for {}, found {}", type, codec);
return codec.map(muonCodec -> muonCodec.getSchemaInfoFor(type));
}
}