org.chiknrice.djeng.MessageCodecConfig Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of djeng Show documentation
Show all versions of djeng Show documentation
Dynamic Java Encoder (NextGen)
The newest version!
/*
* Copyright (c) 2016 Ian Bondoc
*
* This file is part of Djeng
*
* Djeng is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of the License, or(at your option) any later version.
*
* Djeng is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program. If not, see
* .
*
*/
package org.chiknrice.djeng;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import static java.lang.String.format;
import static org.chiknrice.djeng.CoreAttributes.*;
import static org.chiknrice.djeng.XmlConfig.ElementName.CODECS;
import static org.chiknrice.djeng.XmlConfig.ElementName.CODEC_FILTER;
import static org.chiknrice.djeng.XmlConfig.ElementName.MESSAGE_ELEMENTS;
/**
* A {@code MessageCodecConfig} is the configuration required when creating a {@link MessageCodec}. The configuration
* requires at least a configuration xml and optional custom schemas and {@link AttributeTypeMapper}.
*
* @author Ian Bondoc
*/
public class MessageCodecConfig {
/**
* The argument is a path to the xmlConfig. The underlying implementation expects this config to exist in the
* classpath.
*
* @param xmlConfig TODO
* @return TODO
*/
public static MessageCodecConfigBuilder fromXml(String xmlConfig) {
try (InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(xmlConfig)) {
byte[] buf = new byte[8192];
ByteArrayOutputStream bos = new ByteArrayOutputStream();
int read;
while ((read = inputStream.read(buf)) != -1) {
bos.write(buf, 0, read);
}
return fromXml(new ByteArrayInputStream(bos.toByteArray()));
} catch (IOException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
/**
* Creates a config builder from an inputstream to the actual config xml, in this case, it is expected that the
* caller would be closing the resource after the method returns.
*
* @param xmlConfig TODO
* @return TODO
*/
public static MessageCodecConfigBuilder fromXml(InputStream xmlConfig) {
return new MessageCodecConfigBuilder(xmlConfig);
}
public static class MessageCodecConfigBuilder {
private final InputStream xmlConfig;
private final List customSchemas = new ArrayList<>();
private final List customTypeMappers = new ArrayList<>();
private int encodeBufferSize = 0x7FFF;
private MessageCodecConfigBuilder(InputStream xmlConfig) {
this.xmlConfig = xmlConfig;
}
public MessageCodecConfigBuilder withEncodeBufferSize(int bufferSize) {
this.encodeBufferSize = bufferSize;
return this;
}
public MessageCodecConfigBuilder withSchemas(String... schemas) {
for (String schema : schemas) {
customSchemas.add(schema);
}
return this;
}
public MessageCodecConfigBuilder withAttributeMappers(Class extends AttributeTypeMapper>... typeMapperClasses) {
for (Class extends AttributeTypeMapper> typeMapperClass : typeMapperClasses) {
try {
AttributeTypeMapper mapper = typeMapperClass.newInstance();
customTypeMappers.add(mapper);
} catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
}
}
return this;
}
public MessageCodecConfig build() {
return new MessageCodecConfig(xmlConfig, customSchemas, customTypeMappers, encodeBufferSize);
}
}
private final XmlConfig xmlConfig;
private final Map codecMap;
private final Codec rootCodec;
private final int encodeBufferSize;
private MessageCodecConfig(InputStream xmlConfigStream, List customSchemas, List customTypeMappers, int encodeBufferSize) {
// XmlConfig only closes the input streams that it creates, the xmlConfigStream is required to be closed by the caller if needed
try (XmlConfig xmlConfig = new XmlConfig(xmlConfigStream, customSchemas, customTypeMappers)) {
this.xmlConfig = xmlConfig;
codecMap = buildCodecMap();
final XmlConfig.XmlElement element = this.xmlConfig.getElement(MESSAGE_ELEMENTS);
rootCodec = (Codec) buildCodec(element);
configureComposite(element, rootCodec);
} catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
}
this.encodeBufferSize = encodeBufferSize;
}
public Codec getRootCodec() {
return rootCodec;
}
public int getEncodeBufferSize() {
return encodeBufferSize;
}
private final List elementIndexList = new LinkedList<>();
private final Stack indexStack = new Stack<>();
private Map buildCodecMap() {
Map codecMap = new HashMap<>();
XmlConfig.XmlElement codecsElement = xmlConfig.getElement(CODECS);
for (XmlConfig.XmlElement codecElement : codecsElement.getChildren()) {
String id = codecElement.getAttribute(ID);
Class> codecClass = codecElement.getAttribute(CLASS);
switch (codecElement.getName()) {
case CODEC_FILTER:
validateImplementation(CodecFilter.class, codecClass);
break;
case ELEMENT_CODEC:
validateImplementation(ElementCodec.class, codecClass);
break;
case COMPOSITE_CODEC:
validateImplementation(CompositeCodec.class, codecClass);
break;
default:
throw new RuntimeException("Unexpected element " + codecElement.getName().asString());
}
codecMap.put(id, codecElement);
}
return codecMap;
}
private void validateImplementation(Class> expected, Class> actual) {
if (!expected.isAssignableFrom(actual)) {
throw new RuntimeException(format("%s is not a valid implementation of %s", actual.getName(), expected.getName()));
}
}
private void configureComposite(XmlConfig.XmlElement compositeConfig, Codec codec) {
Map subElementCodecMap = new LinkedHashMap<>();
codec.setAttribute(SUB_ELEMENT_CODECS_MAP, subElementCodecMap);
for (XmlConfig.XmlElement subElementConfig : compositeConfig.getChildren()) {
Codec subElementCodec = buildCodec(subElementConfig);
String index = subElementConfig.getAttribute(INDEX);
indexStack.push(index);
switch (subElementConfig.getName()) {
case ELEMENT:
elementIndexList.add(getCurrentIndex());
break;
case COMPOSITE:
case MESSAGE_ELEMENTS:
configureComposite(subElementConfig, subElementCodec);
break;
default:
throw new RuntimeException("Unexpected element " + subElementConfig.getName().asString());
}
subElementCodecMap.put(index, subElementCodec);
indexStack.pop();
}
}
private String getCurrentIndex() {
StringBuilder sb = new StringBuilder(indexStack.firstElement());
for (int i = 1; i < indexStack.size(); i++) {
sb.append(".");
sb.append(indexStack.elementAt(i));
}
return sb.toString();
}
private Codec buildCodec(XmlConfig.XmlElement elementConfig) {
String elementCodec = elementConfig.getAttribute(CODEC);
XmlConfig.XmlElement codecConfig = codecMap.get(elementCodec);
Codec codec = buildObject(codecConfig.>getAttribute(CLASS));
// Codec attributes first
setAttributes(codecConfig, codec);
codec = wrap(codec, SectionRecordingFilter.class);
List filters = codecConfig.getChildren();
for (XmlConfig.XmlElement filter : filters) {
codecConfig = codecMap.get(filter.getAttribute(CODEC));
codec = wrap(codec, codecConfig.>getAttribute(CLASS));
// Filter attributes override codec attributes
setAttributes(codecConfig, codec);
}
// Element attributes override filter & codec attributes
setAttributes(elementConfig, codec);
return codec;
}
private Codec wrap(Codec codec, Class> filter) {
CodecFilter codecFilter = buildObject(filter);
codecFilter.setChain(codec);
return codecFilter;
}
private T buildObject(Class> clazz) {
if (clazz != null) {
try {
return (T) clazz.newInstance();
} catch (Exception e) {
throw new RuntimeException("Failed to create instance of " + clazz.getSimpleName(), e);
}
} else {
return null;
}
}
private void setAttributes(XmlConfig.XmlElement element, Codec codec) {
for (Map.Entry attributeEntry : element.getAttributes().entrySet()) {
Attribute key = attributeEntry.getKey();
Object value = attributeEntry.getValue();
// Filters should not override id and class attributes
if (CODEC_FILTER.equals(element.getName()) && (CoreAttributes.ID.equals(key) || CoreAttributes.CLASS.equals(key))) {
continue;
}
codec.setAttribute(key, value);
}
}
}