/** * 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.aegis.type.encoded; import javax.xml.namespace.QName; import org.apache.cxf.aegis.Context; import org.apache.cxf.aegis.DatabindingException; import org.apache.cxf.aegis.type.AegisType; import org.apache.cxf.aegis.type.basic.BeanType; import org.apache.cxf.aegis.type.basic.BeanTypeInfo; import org.apache.cxf.aegis.xml.MessageReader; import org.apache.cxf.aegis.xml.MessageWriter; /** * StructType is a small extension of the BeanType which can properly read and write SOAP encoded structs. The * modifications are: * <ul> * <li>Nested elements MUST be unqualified</li> * <li>Nested elements MAY contain a SOAP ref attribute instead of an inline value</li> * <li>Struct MAY contain a SOAP id attribute</li> * </ul> * </p> * When writting, the class will always write the struct in the following canonical format: * <ul> * <li>Struct will contain a SOAP id</li> * <li>Nested structs will be written as SOAP references (with SoapRefType)</li> * </ul> */ public class StructType extends BeanType { public StructType() { } public StructType(BeanTypeInfo info) { super(info); } //soap-encoding always allow xsi:nil=true to be set protected boolean alwaysAllowNillables() { return true; } /** * Gets the BeanTypeInfo using an unqualified name. * @param name the unqualified name of the element * @return the BeanTypeInfo containing a property with the specified unqualified name */ @Override protected BeanTypeInfo getBeanTypeInfoWithProperty(QName name) { // nested elements use unqualified names name = qualifyName(name); return super.getBeanTypeInfoWithProperty(name); } /** * Returns a SoapRefType wrapping the actual type. */ @Override protected AegisType getElementType(QName name, BeanTypeInfo beanTypeInfo, MessageReader reader, Context context) { // nested elements use unqualified names name = qualifyName(name); AegisType type = super.getElementType(name, beanTypeInfo, reader, context); if (type != null) { type = new SoapRefType(type); } return type; } /** * Adds special handeling for SoapRefs */ @Override protected void writeProperty(QName name, Object object, Object property, Class<?> impl, BeanTypeInfo inf) throws DatabindingException { // nested elements use unqualified names name = qualifyName(name); if (property instanceof SoapRef) { SoapRef soapRef = (SoapRef) property; // register an action with the ref that will set the bean property // if the reference has already been resolved the action will be // invoked immedately soapRef.setAction(new WritePropertyAction(name, object, impl, inf)); } else { // normal property super.writeProperty(name, object, property, impl, inf); } } /** * Writes a nested element with an unqualified name. */ @Override protected void writeElement(QName name, Object value, AegisType type, MessageWriter writer, Context context) { // Nested elements are unqualified name = new QName("", name.getLocalPart()); MessageWriter cwriter = writer.getElementWriter(name); if (type instanceof BeanType || type instanceof SoapArrayType) { String refId = MarshalRegistry.get(context).getInstanceId(value); SoapEncodingUtil.writeRef(cwriter, refId); } else { type.writeObject(value, cwriter, context); } cwriter.close(); } /** * Gets the qualified name of a nested element. Soap encoded structs contain unqualified elements so * the method searches for a property matching the local part of the unqualified name. */ private QName qualifyName(QName name) { // is the name already qualified, we're done if (!"".equals(name.getNamespaceURI())) { return name; } // find the matching property and get it's name for (BeanType sooper = this; sooper != null; sooper = superBeanType(sooper)) { QName qualifiedName = new QName(sooper.getTypeInfo().getDefaultNamespace(), name.getLocalPart()); if (sooper.getTypeInfo().getType(qualifiedName) != null) { return qualifiedName; } } return name; } private BeanType superBeanType(AegisType t) { if (t instanceof BeanType) { BeanType bt = (BeanType)t; AegisType supertype = bt.getSuperType(); if (supertype instanceof BeanType) { return (BeanType)supertype; } } return null; } /** * When the SoapRef is resolved write the matching property on the target object. */ private final class WritePropertyAction implements SoapRef.Action { private final QName name; private final Object targetObject; private final Class<?> targetClass; private final BeanTypeInfo beanTypeInfo; private WritePropertyAction(QName name, Object targetObject, Class<?> targetClass, BeanTypeInfo beanTypeInfo) { this.name = name; this.targetObject = targetObject; this.targetClass = targetClass; this.beanTypeInfo = beanTypeInfo; } public void onSet(SoapRef ref) { writeProperty(name, targetObject, ref.get(), targetClass, beanTypeInfo); } } }