package com.sissi.io.write.jaxb;
import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.PropertyException;
import javax.xml.bind.annotation.XmlRootElement;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.sissi.commons.ScanUtil;
import com.sissi.commons.Trace;
import com.sissi.commons.apache.IOUtil;
import com.sissi.commons.apache.LineIterator;
import com.sissi.context.JIDContext;
import com.sissi.io.write.Writer;
import com.sissi.io.write.WriterFragement;
import com.sissi.io.write.WriterPart;
import com.sissi.protocol.Element;
import com.sissi.protocol.Stream;
import com.sun.xml.bind.marshaller.NamespacePrefixMapper;
/**
* @author Kim.shen 2013-10-16
*/
public class JAXBWriter implements Writer {
private final Map<Class<? extends Element>, WriterPart> parts = new HashMap<Class<? extends Element>, WriterPart>();
private final Log log = LogFactory.getLog(this.getClass());
private final String mapperProperty = "com.sun.xml.bind.namespacePrefixMapper";
/**
* 协议类基础包
*/
private final String protocol = "com.sissi.protocol";
private final NamespacePrefixMapper mapper;
private final JAXBContext context;
public JAXBWriter() {
this(XmppPrefixMapper.MAPPER);
}
public JAXBWriter(NamespacePrefixMapper mapper) {
super();
this.mapper = mapper;
try {
List<Class<?>> clazz = new ArrayList<Class<?>>();
for (Class<?> each : ScanUtil.getClasses(this.protocol)) {
if (each.getAnnotation(XmlRootElement.class) != null) {
clazz.add(each);
}
}
this.context = JAXBContext.newInstance(clazz.toArray(new Class[] {}));
this.log.info("All classes in JAXB Context: " + clazz);
} catch (Exception e) {
this.log.error(e.toString());
Trace.trace(this.log, e);
throw new RuntimeException("Can't init JAXB context", e);
}
}
public OutputStream write(JIDContext context, OutputStream output, Element element) throws IOException {
try {
try {
switch (this.getPart(element.getClass())) {
case NONE:
this.marshallerPart(context, output, element);
break;
case WITH_LAST:
this.marshallerPart(context, WriterPart.WITH_LAST, output, element);
break;
case WITHOUT_FIRST:
this.marshallerPart(context, WriterPart.WITHOUT_FIRST, output, element);
break;
case WITHOUT_LAST:
this.marshallerPart(context, WriterPart.WITHOUT_LAST, output, element);
break;
}
return output;
} catch (Exception e) {
this.log.error(e);
Trace.trace(this.log, e);
return output;
}
} finally {
IOUtil.closeQuietly(output);
}
}
/**
* 片段类型分析
*
* @param element
* @return
*/
private WriterPart getPart(Class<? extends Element> element) {
WriterPart part = this.parts.get(element);
if (part == null) {
WriterFragement fragement = element.getAnnotation(WriterFragement.class);
this.parts.put(element, (part = fragement != null ? fragement.part() : WriterPart.NONE));
}
return part;
}
/**
* 全文
*
* @param context
* @param output
* @param element
* @return
* @throws Exception
*/
private Element marshallerPart(JIDContext context, OutputStream output, Element element) throws Exception {
Marshaller marshaller = this.generateMarshaller(false, true);
if (this.log.isInfoEnabled()) {
StringBufferWriter buffer = new StringBufferWriter(new StringWriter());
marshaller.marshal(element, buffer);
buffer.flush();
String content = buffer.toString();
this.log.info("Write on " + context.jid().asString() + " " + content);
output.write(content.getBytes("UTF-8"));
} else {
marshaller.marshal(element, output);
}
return element;
}
/**
* 忽略指定部分
*
* @param context
* @param part
* @param output
* @param element
* @return
* @throws Exception
*/
private Element marshallerPart(JIDContext context, WriterPart part, OutputStream output, Element element) throws Exception {
Marshaller marshaller = this.generateMarshaller(true, part == WriterPart.WITHOUT_FIRST ? true : false);
String content = this.getPart(this.prepareToLines(marshaller, element), part);
this.log.info("Write on " + context.jid().asString() + " " + content);
output.write(content.getBytes("UTF-8"));
return element;
}
/**
* 拆分为行
*
* @param marshaller
* @param node
* @return
* @throws JAXBException
* @throws IOException
*/
private LinkedList<String> prepareToLines(Marshaller marshaller, Element node) throws JAXBException, IOException {
ByteArrayOutputStream source = new ByteArrayOutputStream();
BufferedOutputStream buffer = new BufferedOutputStream(source);
try {
marshaller.marshal(node, buffer);
buffer.flush();
LineIterator iterator = IOUtil.lineIterator(new ByteArrayInputStream(source.toByteArray()), "UTF-8");
LinkedList<String> contents = new LinkedList<String>();
while (iterator.hasNext()) {
String each = iterator.next().trim();
if (!each.isEmpty()) {
contents.add(each);
}
}
return contents;
} finally {
IOUtil.closeQuietly(buffer);
IOUtil.closeQuietly(source);
}
}
private JAXBWriter add(LinkedList<String> contents, StringBuffer parts) {
for (String content : contents) {
parts.append(content);
}
return this;
}
private Marshaller generateMarshaller(boolean format, boolean fragment) throws JAXBException, PropertyException {
Marshaller marshaller = this.context.createMarshaller();
marshaller.setProperty(this.mapperProperty, this.mapper);
marshaller.setProperty(Marshaller.JAXB_FRAGMENT, fragment);
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, format);
return marshaller;
}
private String getPart(LinkedList<String> contents, WriterPart part) {
StringBuffer parts = new StringBuffer();
switch (part) {
case WITH_LAST:
parts.append(contents.getLast());
break;
case WITHOUT_FIRST:
contents.removeFirst();
this.add(contents, parts);
break;
case WITHOUT_LAST:
contents.removeLast();
this.add(contents, parts);
break;
default:
}
return parts.toString();
}
private class StringBufferWriter extends BufferedWriter {
private StringWriter out;
public StringBufferWriter(StringWriter out) {
super(out);
this.out = out;
}
public String toString() {
return this.out.toString();
}
}
private static class XmppPrefixMapper extends NamespacePrefixMapper {
public final static NamespacePrefixMapper MAPPER = new XmppPrefixMapper();
private final Map<String, String> mapping = new HashMap<String, String>();
private XmppPrefixMapper() {
super();
this.mapping.put(Stream.XMLNS, Stream.NAME);
}
public String getPreferredPrefix(String namespaceUri, String suggestion, boolean requirePrefix) {
return this.mapping.get(namespaceUri);
}
}
}