/* * Copyright 2013 Guidewire Software, Inc. */ package gw.internal.xml.ws.server.marshal; import gw.internal.schema.gw.xsd.w3c.wsdl.anonymous.elements.TDefinitions_Import; import gw.internal.schema.gw.xsd.w3c.xmlschema.Import; import gw.internal.schema.gw.xsd.w3c.xmlschema.types.complex.LocalElement; import gw.internal.xml.ws.server.WsiServiceInfo; import gw.internal.xml.XmlConstants; import gw.internal.xml.xsd.typeprovider.*; import gw.internal.xml.xsd.typeprovider.simplevaluefactory.XmlSimpleValueFactory; import gw.lang.reflect.IEnumType; import gw.lang.reflect.IPropertyInfo; import gw.lang.reflect.IType; import gw.lang.reflect.TypeSystem; import gw.lang.reflect.gs.IGosuClass; import gw.lang.reflect.java.JavaTypes; import gw.util.Pair; import gw.util.concurrent.LockingLazyVar; import gw.xml.XmlElement; import java.net.URI; import java.net.URISyntaxException; import java.util.Comparator; import javax.xml.XMLConstants; import javax.xml.namespace.QName; /** * This is what the marshaller does, marshal, unmarshal, valid types, create schemas</desc> * * @author dandrews */ public class XmlMarshaller { public static final String DEFAULT_NAMESPACE_PREFIX = "http://example.com/"; // since this is a constant, assume that it always has a '/' private static final LockingLazyVar<IType> _xmlElementType = new LockingLazyVar<IType>() { @Override protected IType init() { return TypeSystem.get( XmlElement.class ); } }; private static final LockingLazyVar<IType> _anyTypeType = new LockingLazyVar<IType>() { @Override protected IType init() { return TypeSystem.getByFullName( "gw.xsd.w3c.xmlschema.types.complex.AnyType" ); } }; private static final LockingLazyVar<IType> _anySimpleTypeType = new LockingLazyVar<IType>() { @Override protected IType init() { return TypeSystem.getByFullName( "gw.xsd.w3c.xmlschema.types.simple.AnySimpleType" ); } }; private XmlMarshaller() { } public static String createTargetNamespace( String prefix, IType type ) { return createTargetNamespace( prefix, type.getName().replace( '.', '/' ) ); } public static String createTargetNamespace( String prefix, String path ) { if ( prefix == null ) { prefix = DEFAULT_NAMESPACE_PREFIX; } return prefix + path; } /** an attribute with the schema type of the property */ public static QName XSI_TYPE = new QName( XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, "type", "xsi"); protected static final Comparator<IPropertyInfo> COMPARATOR = new Comparator<IPropertyInfo>() { @Override public int compare(IPropertyInfo o1, IPropertyInfo o2) { return o1.getName().compareTo(o2.getName()); } }; public static MarshalInfo addType( IType type, LocalElement element, WsiServiceInfo serviceInfo ) throws Exception { MarshalInfo marshalInfo = getMarshalInfo( type, false, serviceInfo ); if ( ! type.isValid() ) { throw new RuntimeException( "Type " + type + " is not valid" ); } else if (marshalInfo == null) { throw new IllegalStateException( "XmlMarshaller doesn't know how to handle " + type.getName() ); } else { marshalInfo.addType( element, serviceInfo ); } return marshalInfo; } public static MarshalInfo getMarshalInfo( IType type, WsiServiceInfo serviceInfo ) { return getMarshalInfo( type, false, serviceInfo ); // only component types should use nillable for null } private static MarshalInfo getMarshalInfo( IType type, boolean isComponent, WsiServiceInfo serviceInfo ) { if (type == null) { return null; } if ( type.isEnum() && serviceInfo != null && ! serviceInfo.getExposeEnumAsStringTypes().contains( type ) ) { return new EnumMarshalInfo((IEnumType)type, isComponent); } Pair<QName, XmlSimpleValueFactory> pair = XmlSchemaTypeToGosuTypeMappings.gosuToSchemaIfValid( type ); if ( pair != null ) { QName qName = pair.getFirst(); XmlSimpleValueFactory simpleValueFactory = pair.getSecond(); String gwType = null; XmlSimpleValueFactory reverseSimpleValueFactory = XmlSchemaTypeToGosuTypeMappings.schemaToGosu( qName ); IType boxType = type.isPrimitive() ? TypeSystem.getBoxType( type ) : type; if ( reverseSimpleValueFactory == null || ! reverseSimpleValueFactory.getGosuValueType().equals( boxType ) ) { if ( ! type.isEnum() ) { // don't expose enum types, since they might not match on the remote side gwType = simpleValueFactory.getGosuValueType().getName(); } } return new SimpleValueMarshalInfo( type, qName, simpleValueFactory, gwType, isComponent ); } else if ( type.isArray() ) { // Array MarshalInfo componentMarshalInfo = getMarshalInfo( type.getComponentType(), true, serviceInfo ); if ( componentMarshalInfo == null ) { return null; } return new ArrayMarshalInfo( type.getComponentType(), componentMarshalInfo, isComponent ); } else if ( JavaTypes.LIST().isAssignableFrom( type ) ) { final IType[] parameters = type.getTypeParameters(); if (parameters == null || parameters.length != 1) { return null; } MarshalInfo componentMarshalInfo = getMarshalInfo( parameters[0], true, serviceInfo ); if ( componentMarshalInfo == null ) { return null; } return new ListMarshalInfo( type.getTypeParameters()[0], componentMarshalInfo, isComponent ); } else if ( _xmlElementType.get().isAssignableFrom( type ) ) { if ( type instanceof IXmlType ) { IXmlType xmlType = (IXmlType) type; IXmlSchemaElementTypeData typeData = (IXmlSchemaElementTypeData) xmlType.getTypeData(); if ( typeData.isAnonymous() ) { return null; // can't take or return anonymous elements } } return new XmlElementMarshalInfo( type, isComponent ); } else if ( _anyTypeType.get().isAssignableFrom( type ) && ! ( _anySimpleTypeType.get().isAssignableFrom( type ) ) ) { IXmlType xmlType = (IXmlType) type; IXmlSchemaTypeInstanceTypeData typeData = (IXmlSchemaTypeInstanceTypeData) xmlType.getTypeData(); if ( typeData.isAnonymous() ) { return null; // can't take or return anonymous type instances } return new XmlTypeInstanceMarshalInfo( type, isComponent ); } else if (type instanceof IGosuClass && ExportableMarshalInfo.isExportable(type)) { return new ExportableMarshalInfo(type, isComponent); } else if (RemotableMarshalInfo.isExportable(type)) { return new RemotableMarshalInfo(type, isComponent); } return null; } /** * This will either find the schema in the import section of the schema or add the import. * * * * @param schemaIndex the schema that we are looking for * @param serviceInfo info on the wsdl being created * @param namespaceURI * @throws java.net.URISyntaxException on error */ public static void findOrImportSchema( XmlSchemaIndex schemaIndex, WsiServiceInfo serviceInfo, String namespaceURI ) throws URISyntaxException { XmlSchemaImportInfo importInfo = schemaIndex.getImportInfo(); String declaredNamespace = importInfo.getTargetNamespace(); // The reason for preserving the difference between a lack of a namespace and an empty string namespace is to // work around an issue with Apache XMLBeans String namespaceToDeclare = declaredNamespace == null ? XMLConstants.NULL_NS_URI : declaredNamespace; // first add wsdl import if needed if ( importInfo.isWsdl() ) { boolean needWsdlImport = true; for ( TDefinitions_Import child : serviceInfo.getWsdl().Import() ) { String childNamespace = child.Namespace() == null ? XMLConstants.NULL_NS_URI : child.Namespace().toString(); if ( childNamespace.equals( namespaceToDeclare ) ) { needWsdlImport = false; } } if ( needWsdlImport ) { String location = getOrCreateLocation( schemaIndex, serviceInfo.getXsdRootURL() ); serviceInfo.getSchemas().add( schemaIndex ); TDefinitions_Import imprt = new TDefinitions_Import(); imprt.setNamespace$( new URI( declaredNamespace == null ? XmlConstants.NULL_NS_URI : declaredNamespace ) ); imprt.setLocation$( new URI( location ) ); serviceInfo.getWsdl().getChildren().add( 0, imprt ); serviceInfo.getWsdl().declareNamespace( new URI( namespaceToDeclare ), "imported" ); } if ( namespaceURI == null ) { return; // just need the wsdl:import } namespaceToDeclare = namespaceURI; declaredNamespace = namespaceURI; } // now add schema import for ( Import child : serviceInfo.getSchema().Import() ) { String childNamespace = child.Namespace() == null ? XMLConstants.NULL_NS_URI : child.Namespace().toString(); if ( childNamespace.equals( namespaceToDeclare ) ) { return; } } serviceInfo.getSchemas().add( schemaIndex ); Import imprt = new Import(); if ( declaredNamespace != null ) { imprt.setNamespace$( new URI( declaredNamespace ) ); } if ( ! importInfo.isWsdl() ) { String location = getOrCreateLocation( schemaIndex, serviceInfo.getXsdRootURL() ); imprt.setSchemaLocation$( new URI( location ) ); } serviceInfo.getSchema().getChildren().add( 0, imprt ); serviceInfo.getSchema().declareNamespace( new URI( namespaceToDeclare ), "imported" ); } /** creates the schemaLocation * * @param schema the schema * @param xsdRootURI the root for the URL * @return the path */ public static String getOrCreateLocation( XmlSchemaIndex schema, String xsdRootURI ) { return xsdRootURI + ( xsdRootURI.length() == 0 || xsdRootURI.endsWith("/") ? "" : "/" ) + schema.getXSDSourcePath(); } }