/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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 org.apache.cxf.jaxb.io; import java.lang.annotation.Annotation; import java.lang.reflect.Array; import java.lang.reflect.Method; import java.nio.charset.StandardCharsets; import java.util.Collection; import java.util.Map; import java.util.Map.Entry; import java.util.logging.Level; import java.util.logging.Logger; import javax.xml.bind.JAXBException; import javax.xml.bind.MarshalException; import javax.xml.bind.Marshaller; import javax.xml.bind.PropertyException; import javax.xml.bind.ValidationEvent; import javax.xml.bind.ValidationEventHandler; import javax.xml.bind.annotation.adapters.XmlAdapter; import javax.xml.bind.attachment.AttachmentMarshaller; import org.apache.cxf.common.i18n.Message; import org.apache.cxf.common.jaxb.JAXBUtils; import org.apache.cxf.common.logging.LogUtils; import org.apache.cxf.common.util.ReflectionUtil; import org.apache.cxf.databinding.DataWriter; import org.apache.cxf.interceptor.Fault; import org.apache.cxf.jaxb.JAXBDataBase; import org.apache.cxf.jaxb.JAXBDataBinding; import org.apache.cxf.jaxb.JAXBEncoderDecoder; import org.apache.cxf.jaxb.MarshallerEventHandler; import org.apache.cxf.jaxb.attachment.JAXBAttachmentMarshaller; import org.apache.cxf.message.MessageUtils; import org.apache.cxf.service.model.MessagePartInfo; import org.apache.ws.commons.schema.XmlSchemaElement; public class DataWriterImpl<T> extends JAXBDataBase implements DataWriter<T> { private static final Logger LOG = LogUtils.getLogger(JAXBDataBinding.class); ValidationEventHandler veventHandler; boolean setEventHandler = true; private JAXBDataBinding databinding; public DataWriterImpl(JAXBDataBinding binding) { super(binding.getContext()); databinding = binding; } public void write(Object obj, T output) { write(obj, null, output); } public void setProperty(String prop, Object value) { if (prop.equals(org.apache.cxf.message.Message.class.getName())) { org.apache.cxf.message.Message m = (org.apache.cxf.message.Message)value; veventHandler = getValidationEventHandler(m, JAXBDataBinding.WRITER_VALIDATION_EVENT_HANDLER); if (veventHandler == null) { veventHandler = databinding.getValidationEventHandler(); } setEventHandler = MessageUtils.getContextualBoolean(m, JAXBDataBinding.SET_VALIDATION_EVENT_HANDLER, true); } } private static class MtomValidationHandler implements ValidationEventHandler { ValidationEventHandler origHandler; JAXBAttachmentMarshaller marshaller; MtomValidationHandler(ValidationEventHandler v, JAXBAttachmentMarshaller m) { origHandler = v; marshaller = m; } public boolean handleEvent(ValidationEvent event) { // CXF-1194 this hack is specific to MTOM, so pretty safe to leave in here before calling the origHandler. String msg = event.getMessage(); if (msg.startsWith("cvc-type.3.1.2") && msg.contains(marshaller.getLastMTOMElementName().getLocalPart())) { return true; } if (origHandler != null) { return origHandler.handleEvent(event); } return false; } } public Marshaller createMarshaller(Object elValue, MessagePartInfo part) { Class<?> cls = null; if (part != null) { cls = part.getTypeClass(); } if (cls == null) { cls = null != elValue ? elValue.getClass() : null; } if (cls != null && cls.isArray() && elValue instanceof Collection) { Collection<?> col = (Collection<?>)elValue; elValue = col.toArray((Object[])Array.newInstance(cls.getComponentType(), col.size())); } Marshaller marshaller; try { marshaller = context.createMarshaller(); marshaller.setProperty(Marshaller.JAXB_ENCODING, StandardCharsets.UTF_8.name()); marshaller.setProperty(Marshaller.JAXB_FRAGMENT, true); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.FALSE); marshaller.setListener(databinding.getMarshallerListener()); if (setEventHandler) { ValidationEventHandler h = veventHandler; if (veventHandler == null) { h = new ValidationEventHandler() { public boolean handleEvent(ValidationEvent event) { //continue on warnings only return event.getSeverity() == ValidationEvent.WARNING; } }; } marshaller.setEventHandler(h); } final Map<String, String> nspref = databinding.getDeclaredNamespaceMappings(); final Map<String, String> nsctxt = databinding.getContextualNamespaceMap(); // set the prefix mapper if either of the prefix map is configured if (nspref != null || nsctxt != null) { Object mapper = JAXBUtils.setNamespaceMapper(nspref != null ? nspref : nsctxt, marshaller); if (nsctxt != null) { setContextualNamespaceDecls(mapper, nsctxt); } } if (databinding.getMarshallerProperties() != null) { for (Map.Entry<String, Object> propEntry : databinding.getMarshallerProperties().entrySet()) { try { marshaller.setProperty(propEntry.getKey(), propEntry.getValue()); } catch (PropertyException pe) { LOG.log(Level.INFO, "PropertyException setting Marshaller properties", pe); } } } marshaller.setSchema(schema); AttachmentMarshaller atmarsh = getAttachmentMarshaller(); marshaller.setAttachmentMarshaller(atmarsh); if (schema != null && atmarsh instanceof JAXBAttachmentMarshaller) { //we need a special even handler for XOP attachments marshaller.setEventHandler(new MtomValidationHandler(marshaller.getEventHandler(), (JAXBAttachmentMarshaller)atmarsh)); } } catch (JAXBException ex) { if (ex instanceof javax.xml.bind.MarshalException) { javax.xml.bind.MarshalException marshalEx = (javax.xml.bind.MarshalException)ex; Message faultMessage = new Message("MARSHAL_ERROR", LOG, marshalEx.getLinkedException() .getMessage()); throw new Fault(faultMessage, ex); } else { throw new Fault(new Message("MARSHAL_ERROR", LOG, ex.getMessage()), ex); } } for (XmlAdapter<?, ?> adapter : databinding.getConfiguredXmlAdapters()) { marshaller.setAdapter(adapter); } return marshaller; } //REVISIT should this go into JAXBUtils? private static void setContextualNamespaceDecls(Object mapper, Map<String, String> nsctxt) { try { Method m = ReflectionUtil.getDeclaredMethod(mapper.getClass(), "setContextualNamespaceDecls", new Class<?>[]{String[].class}); String[] args = new String[nsctxt.size() * 2]; int ai = 0; for (Entry<String, String> nsp : nsctxt.entrySet()) { args[ai++] = nsp.getValue(); args[ai++] = nsp.getKey(); } m.invoke(mapper, new Object[]{args}); } catch (Exception e) { // ignore LOG.log(Level.WARNING, "Failed to set the contextual namespace map", e); } } public void write(Object obj, MessagePartInfo part, T output) { boolean honorJaxbAnnotation = honorJAXBAnnotations(part); if (part != null && !part.isElement() && part.getTypeClass() != null) { honorJaxbAnnotation = true; } checkPart(part, obj); if (obj != null || !(part.getXmlSchema() instanceof XmlSchemaElement)) { if (obj instanceof Exception && part != null && Boolean.TRUE.equals(part.getProperty(JAXBDataBinding.class.getName() + ".CUSTOM_EXCEPTION"))) { JAXBEncoderDecoder.marshallException(createMarshaller(obj, part), (Exception)obj, part, output); onCompleteMarshalling(); } else { Annotation[] anns = getJAXBAnnotation(part); if (!honorJaxbAnnotation || anns.length == 0) { JAXBEncoderDecoder.marshall(createMarshaller(obj, part), obj, part, output); onCompleteMarshalling(); } else if (honorJaxbAnnotation && anns.length > 0) { //RpcLit will use the JAXB Bridge to marshall part message when it is //annotated with @XmlList,@XmlAttachmentRef,@XmlJavaTypeAdapter //TODO:Cache the JAXBRIContext JAXBEncoderDecoder.marshalWithBridge(part.getConcreteName(), part.getTypeClass(), anns, databinding.getContextClasses(), obj, output, getAttachmentMarshaller()); } } } else if (needToRender(part)) { JAXBEncoderDecoder.marshallNullElement(createMarshaller(null, part), output, part); onCompleteMarshalling(); } } private void checkPart(MessagePartInfo part, Object object) { if (part == null || part.getTypeClass() == null || object == null) { return; } Class<?> typeClass = part.getTypeClass(); if (typeClass == null) { return; } if (typeClass.isPrimitive()) { if (typeClass == Long.TYPE) { typeClass = Long.class; } else if (typeClass == Integer.TYPE) { typeClass = Integer.class; } else if (typeClass == Short.TYPE) { typeClass = Short.class; } else if (typeClass == Byte.TYPE) { typeClass = Byte.class; } else if (typeClass == Character.TYPE) { typeClass = Character.class; } else if (typeClass == Double.TYPE) { typeClass = Double.class; } else if (typeClass == Float.TYPE) { typeClass = Float.class; } else if (typeClass == Boolean.TYPE) { typeClass = Boolean.class; } } else if (typeClass.isArray() && object instanceof Collection) { //JAXB allows a pseudo [] <--> List equivalence return; } if (!typeClass.isInstance(object)) { throw new IllegalArgumentException("Part " + part.getName() + " should be of type " + typeClass.getName() + ", not " + object.getClass().getName()); } } private boolean needToRender(MessagePartInfo part) { if (part != null && part.getXmlSchema() instanceof XmlSchemaElement) { XmlSchemaElement element = (XmlSchemaElement)part.getXmlSchema(); return element.isNillable() && element.getMinOccurs() > 0; } return false; } private void onCompleteMarshalling() { if (setEventHandler && veventHandler instanceof MarshallerEventHandler) { try { ((MarshallerEventHandler) veventHandler).onMarshalComplete(); } catch (MarshalException e) { if (e.getLinkedException() != null) { throw new Fault(new Message("MARSHAL_ERROR", LOG, e.getLinkedException().getMessage()), e); } else { throw new Fault(new Message("MARSHAL_ERROR", LOG, e.getMessage()), e); } } } } }