/*
* SoapUI, Copyright (C) 2004-2016 SmartBear Software
*
* Licensed under the EUPL, Version 1.1 or - as soon as they will be approved by the European Commission - subsequent
* versions of the EUPL (the "Licence");
* You may not use this work except in compliance with the Licence.
* You may obtain a copy of the Licence at:
*
* http://ec.europa.eu/idabc/eupl
*
* Unless required by applicable law or agreed to in writing, software distributed under the Licence is
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the Licence for the specific language governing permissions and limitations
* under the Licence.
*/
package com.eviware.soapui.impl.wadl.inference.schema.types;
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;
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 javax.xml.namespace.QName;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 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;
}
}