/* * Copyright 2014, 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 javax.xml.bind.*; import javax.xml.bind.util.ValidationEventCollector; import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLStreamReader; import javax.xml.validation.*; import java.io.*; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.logging.Level; import com.betfair.cougar.api.fault.FaultCode; import com.betfair.cougar.core.api.exception.CougarException; import com.betfair.cougar.core.api.exception.CougarMarshallingException; import com.betfair.cougar.core.api.exception.CougarValidationException; import com.betfair.cougar.core.api.exception.ServerFaultCode; import com.betfair.cougar.core.api.fault.CougarFault; import com.betfair.cougar.core.api.fault.FaultDetail; import com.betfair.cougar.core.api.transcription.ParameterType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.betfair.cougar.marshalling.api.databinding.FaultUnMarshaller; import com.betfair.cougar.marshalling.api.databinding.UnMarshaller; import org.xml.sax.SAXParseException; public class XMLUnMarshaller implements UnMarshaller, FaultUnMarshaller { private final static Logger LOGGER = LoggerFactory.getLogger(XMLUnMarshaller.class); // todo: make schema validation configurable for rescript/xml (already done for soap) private static final ConcurrentMap<Class<?>,JAXBContext> jaxbContexts = new ConcurrentHashMap<>(); private static final ConcurrentMap<JAXBContext,Schema> schemas = new ConcurrentHashMap<>(); private SchemaValidationFailureParser schemaValidationFailureParser; public XMLUnMarshaller(SchemaValidationFailureParser schemaValidationFailureParser) { this.schemaValidationFailureParser = schemaValidationFailureParser; } @Override public String getFormat() { return "xml"; } @Override public Object unmarshall(InputStream inputStream, ParameterType parameterType, String encoding, boolean client) { //It would be possible to change the way this marshaller works //entirely to use the XMLTranscription approach - could be done - //see SoapTransportCommandProcessor return new UnsupportedOperationException("This XML UnMarshaller does not [yet] support unmarshalling by parameterType"); } @Override public CougarFault unMarshallFault(InputStream inputStream, String encoding) { //noinspection unchecked final HashMap<String,Object> faultMap = (HashMap<String,Object>) unmarshall(inputStream, HashMap.class, encoding, true); final String faultString = (String)faultMap.get("faultstring"); final FaultCode faultCode = FaultCode.valueOf((String) faultMap.get("faultcode")); //noinspection unchecked final HashMap<String, Object> detailMap = (HashMap<String, Object>)faultMap.get("detail"); String exceptionName = (String)detailMap.get("exceptionname"); List<String[]> faultParams = Collections.emptyList(); if (exceptionName != null) { faultParams = new ArrayList<>(); //noinspection unchecked Map<String, Object> paramMap = (Map<String, Object>) detailMap.get(exceptionName); for(Map.Entry e:paramMap.entrySet()){ String[] nvpair=new String[] { (String)e.getKey(), e.getValue().toString() }; faultParams.add(nvpair); } } final FaultDetail fd=new FaultDetail(faultString, faultParams); return new CougarFault() { @Override public String getErrorCode() { return faultString; } @Override public FaultCode getFaultCode() { return faultCode; } @Override public FaultDetail getDetail() { return fd; } }; } @Override public Object unmarshall(InputStream inputStream, Class<?> clazz, String encoding, boolean client) { try { XMLInputFactory factory = XMLInputFactory.newInstance(); factory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false); XMLStreamReader reader = factory.createXMLStreamReader(new BufferedReader(new InputStreamReader(inputStream,encoding))); JAXBContext jc = jaxbContexts.get(clazz); if (jc == null) { jc = JAXBContext.newInstance(clazz); JAXBContext prev = jaxbContexts.putIfAbsent(clazz, jc); if (prev != null){ jc = prev; } Schema schema = XMLUtils.getSchema(jc); schemas.putIfAbsent(jc, schema); } Unmarshaller u = jc.createUnmarshaller(); ValidationEventCollector validationHandler = new ValidationEventCollector(); u.setEventHandler(validationHandler); setSchema(jc, u); Object obj = u.unmarshal(reader); if (!clazz.isAssignableFrom(obj.getClass())) { throw CougarMarshallingException.unmarshallingException("xml", "Deserialised object was not of class "+clazz.getName(),false); } validate(validationHandler); return obj; } catch (UnmarshalException e) { Throwable linkedException = e.getLinkedException(); if(linkedException!=null) { LOGGER.debug(linkedException.getMessage()); if (linkedException instanceof SAXParseException) { CougarException ce = schemaValidationFailureParser.parse((SAXParseException)linkedException, getFormat(), client); if (ce != null) { throw ce; } } throw CougarMarshallingException.unmarshallingException("xml",linkedException,client); } throw CougarMarshallingException.unmarshallingException("xml",e,client); } catch (CougarMarshallingException e) { throw e; } catch (Exception e) { throw CougarMarshallingException.unmarshallingException(getFormat(), "Unable to deserialise REST/XML request", e, client); } } private void setSchema(JAXBContext jc, Unmarshaller u) { Schema schema = schemas.get(jc); if(schema!=null) { u.setSchema(schema); } } private void validate(ValidationEventCollector handler) throws ValidationException { if(handler.hasEvents()) { ValidationEvent[] events = handler.getEvents(); StringBuilder sb = new StringBuilder(); for(ValidationEvent event : events) { sb.append(event.getMessage()); } throw new ValidationException(sb.toString()); } } }