/*
* soapUI, copyright (C) 2004-2011 eviware.com
*
* soapUI is free software; you can redistribute it and/or modify it under the
* terms of version 2.1 of the GNU Lesser General Public License as published by
* the Free Software Foundation.
*
* soapUI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details at gnu.org.
*/
package com.eviware.soapui.impl.wadl.inference.schema.types;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.xml.namespace.QName;
import org.apache.xmlbeans.SchemaTypeLoader;
import org.apache.xmlbeans.SchemaTypeSystem;
import org.apache.xmlbeans.XmlBeans;
import org.apache.xmlbeans.XmlCursor;
import org.apache.xmlbeans.XmlException;
import org.apache.xmlbeans.XmlObject;
import com.eviware.soapui.impl.wadl.inference.ConflictHandler;
import com.eviware.soapui.impl.wadl.inference.schema.Content;
import com.eviware.soapui.impl.wadl.inference.schema.Context;
import com.eviware.soapui.impl.wadl.inference.schema.Particle;
import com.eviware.soapui.impl.wadl.inference.schema.Schema;
import com.eviware.soapui.impl.wadl.inference.schema.Settings;
import com.eviware.soapui.impl.wadl.inference.schema.Type;
import com.eviware.soapui.impl.wadl.inference.schema.content.EmptyContent;
import com.eviware.soapui.inferredSchema.ComplexTypeConfig;
import com.eviware.soapui.inferredSchema.ParticleConfig;
import com.eviware.soapui.inferredSchema.TypeReferenceConfig;
/**
* ComplexType corresponds to an xs:complexType. It's definition is displayed in
* the schema for which it belongs.
*
* @author Dain Nilsson
*/
public class ComplexType implements Type
{
private String name;
private Schema schema;
private Map<QName, Particle> attributes;
private Content content;
private boolean mixed = false;
private boolean completed = false;
public ComplexType( Schema schema, String name, boolean completed )
{
this.schema = schema;
this.name = name;
this.completed = completed;
content = new EmptyContent( schema, completed );
attributes = new HashMap<QName, Particle>();
schema.addType( this );
}
public ComplexType( ComplexTypeConfig xml, Schema schema )
{
this.schema = schema;
name = xml.getName();
completed = xml.getCompleted();
mixed = xml.getMixed();
content = Content.Factory.parse( xml.getContent(), schema );
attributes = new HashMap<QName, Particle>();
for( ParticleConfig item : xml.getAttributeList() )
{
Particle p = Particle.Factory.parse( item, schema );
attributes.put( new QName( "", p.getName().getLocalPart() ), p );
}
schema.addType( this );
}
public void save( ComplexTypeConfig xml )
{
xml.setName( name );
xml.setCompleted( completed );
xml.setMixed( mixed );
List<ParticleConfig> particleList = new ArrayList<ParticleConfig>();
for( Particle item : attributes.values() )
particleList.add( item.save() );
xml.setAttributeArray( particleList.toArray( new ParticleConfig[0] ) );
xml.setContent( content.save() );
}
public TypeReferenceConfig save()
{
TypeReferenceConfig xml = TypeReferenceConfig.Factory.newInstance();
xml.setReference( new QName( schema.getNamespace(), name ) );
return xml;
}
public void setContent( Content content )
{
this.content = content;
}
public Type validate( Context context ) throws XmlException
{
XmlCursor cursor = context.getCursor();
List<QName> seen = new ArrayList<QName>();
cursor.push();
if( !mixed && isMixed( context ) )
{
// TODO: Check with ConflictHandler
mixed = true;
}
cursor.pop();
cursor.push();
if( cursor.toFirstAttribute() )
{
do
{
QName qname = cursor.getName();
if( attributes.containsKey( qname ) )
{
attributes.get( qname ).validate( context );
}
else if( qname.getNamespaceURI().equals( Settings.xsins ) )
{
// Ignore
}
else if( context.getHandler().callback( ConflictHandler.Event.CREATION, ConflictHandler.Type.ATTRIBUTE,
new QName( schema.getNamespace(), qname.getLocalPart() ), context.getPath(), "Undeclared attribute." ) )
{
if( qname.getNamespaceURI().equals( schema.getNamespace() ) || qname.getNamespaceURI().equals( "" ) )
{
newAttribute( qname ).validate( context );
}
else
{
Schema otherSchema = context.getSchemaSystem().getSchemaForNamespace( qname.getNamespaceURI() );
schema.putPrefixForNamespace( qname.getPrefix(), qname.getNamespaceURI() );
if( otherSchema == null )
{
otherSchema = context.getSchemaSystem().newSchema( qname.getNamespaceURI() );
}
Particle ref = otherSchema.getParticle( qname.getLocalPart() );
if( ref == null )
{
ref = otherSchema.newAttribute( qname.getLocalPart() );
}
if( completed )
ref.setAttribute( "use", "optional" );
Particle newAttribute = Particle.Factory.newReferenceInstance( schema, ref );
attributes.put( qname, newAttribute );
newAttribute.validate( context );
}
}
else
throw new XmlException( "Illegal attribute!" );
seen.add( qname );
}
while( cursor.toNextAttribute() );
}
// Make sure all attributes have been accounted for
for( QName item : attributes.keySet() )
{
if( !seen.contains( item ) && !attributes.get( item ).getAttribute( "use" ).equals( "optional" ) )
{
if( context.getHandler().callback( ConflictHandler.Event.MODIFICATION, ConflictHandler.Type.ATTRIBUTE,
item, context.getPath(), "Required attribute missing." ) )
{
attributes.get( item ).setAttribute( "use", "optional" );
}
else
throw new XmlException( "Required attribute missing!" );
}
}
cursor.pop();
if( !cursor.toFirstChild() )
cursor.toFirstContentToken();
if( !context.getAttribute( "nil" ).equals( "true" ) )
validateContent( context );
completed = true;
return this;
}
private void validateContent( Context context ) throws XmlException
{
context.getCursor().push();
context.putAttribute( "typeName", name );
Content newContent = content.validate( context );
context.clearAttribute( "typeName" );
if( content != newContent )
{
String problem = "Illegal content for complexType '" + name + "'.";
if( context.getHandler().callback( ConflictHandler.Event.MODIFICATION, ConflictHandler.Type.TYPE,
new QName( schema.getNamespace(), name ), context.getPath(), "Illegal complex content." ) )
{
content = newContent;
context.getCursor().pop();
validateContent( context );
return;
}
else
throw new XmlException( problem );
}
context.getCursor().pop();
}
private boolean isMixed( Context context )
{
QName name = context.getCursor().getName();
SchemaTypeSystem sts;
try
{
sts = XmlBeans.compileXsd( new XmlObject[] { XmlObject.Factory
.parse( "<xs:schema xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns=\"" + name.getNamespaceURI()
+ "\" targetNamespace=\"" + name.getNamespaceURI() + "\">" + "<xs:element name=\""
+ name.getLocalPart() + "\"><xs:complexType><xs:sequence>"
+ "<xs:any processContents=\"skip\" minOccurs=\"0\" maxOccurs=\"unbounded\" /></xs:sequence>"
+ "<xs:anyAttribute processContents=\"skip\"/></xs:complexType></xs:element></xs:schema>" ) },
XmlBeans.getBuiltinTypeSystem(), null );
SchemaTypeLoader stl = XmlBeans
.typeLoaderUnion( new SchemaTypeLoader[] { sts, XmlBeans.getBuiltinTypeSystem() } );
if( !stl.parse( context.getCursor().xmlText(), null, null ).validate() )
return true;
}
catch( XmlException e )
{
// TODO Auto-generated catch block
e.printStackTrace();
}
return false;
}
public String getName()
{
return name;
}
public Schema getSchema()
{
return schema;
}
public void setSchema( Schema schema )
{
this.schema = schema;
}
@Override
public String toString()
{
String xsdns = schema.getPrefixForNamespace( Settings.xsdns );
StringBuilder s = new StringBuilder( "<" + xsdns + ":complexType name=\"" + name + "\"" );
if( mixed )
s.append( " mixed=\"true\"" );
s.append( ">" );
StringBuilder attrs = new StringBuilder();
for( Particle item : attributes.values() )
{
attrs.append( item );
}
s.append( content.toString( attrs.toString() ) );
s.append( "</" + xsdns + ":complexType>" );
return s.toString();
}
private Particle newAttribute( QName qname )
{
Particle p = Particle.Factory.newAttributeInstance( schema, qname.getLocalPart() );
attributes.put( qname, p );
if( completed )
p.setAttribute( "use", "optional" );
return p;
}
}