/*
* Copyright 2013, The Sporting Exchange Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.betfair.cougar.marshalling.impl.databinding.xml;
import com.betfair.cougar.core.api.exception.CougarException;
import com.betfair.cougar.core.api.exception.CougarFrameworkException;
import com.betfair.cougar.core.api.exception.CougarServiceException;
import com.betfair.cougar.core.api.exception.ServerFaultCode;
import com.betfair.cougar.core.api.fault.CougarFault;
import com.betfair.cougar.core.api.fault.FaultController;
import com.betfair.cougar.core.api.fault.FaultDetail;
import com.betfair.cougar.logging.CougarLogger;
import com.betfair.cougar.logging.CougarLoggingUtils;
import com.betfair.cougar.marshalling.api.databinding.FaultMarshaller;
import com.betfair.cougar.marshalling.api.databinding.Marshaller;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.logging.Level;
public class XMLMarshaller implements Marshaller, FaultMarshaller {
static {
System.setProperty("javax.xml.stream.XMLInputFactory", "com.ctc.wstx.stax.WstxInputFactory");
System.setProperty("javax.xml.stream.XMLOutputFactory", "com.ctc.wstx.stax.WstxOutputFactory");
System.setProperty("javax.xml.stream.XMLEventFactory", "com.ctc.wstx.stax.WstxEventFactory");
}
private static final XMLOutputFactory factory = XMLOutputFactory.newInstance();
private static ConcurrentMap<String,JAXBContext> jaxbContexts = new ConcurrentHashMap<String, JAXBContext>();
private static final CougarLogger logger = CougarLoggingUtils.getLogger(XMLMarshaller.class);
XMLMarshaller() {
}
@Override
public String getFormat() {
return "xml";
}
@Override
public void marshall(final OutputStream outputStream, final Object result, String encoding) {
XMLStreamWriter xmlWriter = null;
try {
xmlWriter = factory.createXMLStreamWriter(outputStream);
String resultPackage = result.getClass().getPackage().getName();
JAXBContext jc = getJAXBContext(resultPackage);
final javax.xml.bind.Marshaller marshaller = jc.createMarshaller();
if (encoding!=null) {
marshaller.setProperty(javax.xml.bind.Marshaller.JAXB_ENCODING, encoding);
}
marshaller.marshal(result, xmlWriter);
} catch (final XMLStreamException e) {
throw new CougarServiceException(getServerFaultCode(e), "Failed to stream object to XML", e);
} catch (final JAXBException e) {
throw new CougarServiceException(getServerFaultCode(e), "Failed to marshall object to XML", e);
} finally {
if (xmlWriter != null) {
try {
xmlWriter.close();
} catch (final XMLStreamException ignored) {}
}
}
}
private ServerFaultCode getServerFaultCode(Throwable e) {
Set<Throwable> seen = new HashSet<Throwable>();
while (e != null && !seen.contains(e)) {
if (e instanceof IOException) {
return ServerFaultCode.OutputChannelClosedCantWrite;
}
seen.add(e);
e = e.getCause();
}
return ServerFaultCode.XMLSerialisationFailure;
}
@Override
public void marshallFault(final OutputStream outputStream, final CougarFault fault, String encoding) {
XMLStreamWriter xmlWriter = null;
try {
xmlWriter = factory.createXMLStreamWriter(outputStream);
xmlWriter.writeStartDocument(encoding, "1.0");
xmlWriter.writeStartElement("fault");//start soap body
writeElement("faultcode",fault.getFaultCode().name(),xmlWriter);
writeElement("faultstring",fault.getErrorCode(),xmlWriter);
writeFaultDetail( fault, xmlWriter);
xmlWriter.writeEndElement();
xmlWriter.writeEndDocument();
} catch (final XMLStreamException e) {
throw new CougarServiceException(getServerFaultCode(e), "Failed to stream fault "+fault.getClass()+" to XML", e);
} catch (final JAXBException e) {
throw new CougarServiceException(getServerFaultCode(e), "Failed to marshall fault "+fault.getClass()+" to XML", e);
}
finally {
if (xmlWriter != null) {
try {
xmlWriter.close();
} catch (final XMLStreamException ignored) {}
}
}
}
private void writeElement(String name,String data, XMLStreamWriter xmlWriter) throws XMLStreamException, JAXBException {
xmlWriter.writeStartElement(name);
xmlWriter.writeCharacters(data);
xmlWriter.writeEndElement();
}
private void writeFaultDetail(CougarFault cougarFault, XMLStreamWriter xmlWriter) throws XMLStreamException, JAXBException {
xmlWriter.writeStartElement("detail");
FaultDetail detail = cougarFault.getDetail();
if(detail != null ) {
List<String[]> faultMessages = detail.getFaultMessages();
if (faultMessages != null) {
writeElement("exceptionname", detail.getFaultName(), xmlWriter);
xmlWriter.writeStartElement(detail.getFaultName());
for (String[] msg: faultMessages) {
writeElement(msg[0],msg[1],xmlWriter);
}
xmlWriter.writeEndElement();
}
if (FaultController.getInstance().isDetailedFaults()) {
writeElement("trace",detail.getStackTrace(),xmlWriter);
writeElement("message",detail.getDetailMessage(),xmlWriter);
}
}
xmlWriter.writeEndElement();
}
private static JAXBContext getJAXBContext(String namespaces) throws JAXBException {
JAXBContext jc = jaxbContexts.get(namespaces);
if(jc == null) {
jc = JAXBContext.newInstance(namespaces);
JAXBContext prev = jaxbContexts.putIfAbsent(namespaces, jc);
if (prev != null){
jc = prev;
}
}
return jc;
}
}