io.github.microcks.util.SoapMessageValidator Maven / Gradle / Ivy
/*
* Licensed to Laurent Broudoux (the "Author") under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. Author 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 io.github.microcks.util;
import com.eviware.soapui.impl.wsdl.support.wsdl.WsdlContext;
import com.eviware.soapui.support.xml.XmlUtils;
import org.apache.xmlbeans.SchemaGlobalElement;
import org.apache.xmlbeans.SchemaType;
import org.apache.xmlbeans.XmlCursor;
import org.apache.xmlbeans.XmlError;
import org.apache.xmlbeans.XmlException;
import org.apache.xmlbeans.XmlLineNumber;
import org.apache.xmlbeans.XmlObject;
import org.apache.xmlbeans.XmlOptions;
import org.apache.xmlbeans.XmlValidationError;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import javax.xml.namespace.QName;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Helper class for validating Soap Messages against their WSDL. Code here is mainly extracted and adapted from
* SoapUI WsdlValidator class (see https://github.com/SmartBear/soapui/blob/master/soapui/src/main/java/com/eviware/soapui/impl/wsdl/support/wsdl/WsdlValidator.java
* for more details)
* @author laurent
*/
public class SoapMessageValidator {
/** A commons logger for diagnostic messages. */
private static Logger log = LoggerFactory.getLogger(SoapMessageValidator.class);
/**
* Validate a soap message accordingly to its WSDL and linked XSD resources. The validation is
* done for a specified message part (maybe be the input, output or fault of an operation).
* @param partName The name of the part to validate ie. name of the input, output or fault part (ex: sayHello)
* @param partNamespace The namespace of the part to validate (ex: http://www.mma.fr/test/service)
* @param message The full soap message as a string
* @param wsdlUrl The URL where we can resolve service and operation WSDL
* @param validateMessageBody Should we validate also the body ? If false, only Soap envelope is validated.
* @return The list of validation failures. If empty, message is valid !
* @throws org.apache.xmlbeans.XmlException if given message is not a valid Xml document
*/
public static List validateSoapMessage(String partName, String partNamespace, String message, String wsdlUrl, boolean validateMessageBody)
throws XmlException {
//
WsdlContext ctx = new WsdlContext(wsdlUrl);
List errors = new ArrayList();
ctx.getSoapVersion().validateSoapEnvelope(message, errors);
log.debug("SoapEnvelope validation errors: " + errors.size());
if (validateMessageBody){
// Create XmlBeans object for the soap message.
XmlOptions xmlOptions = new XmlOptions();
xmlOptions.setLoadLineNumbers();
xmlOptions.setLoadLineNumbers( XmlOptions.LOAD_LINE_NUMBERS_END_ELEMENT );
XmlObject xml = XmlUtils.createXmlObject(message, xmlOptions);
// Build the QName string of the part name. Ex: {http://www.github.com/lbroudoux/service}sayHello
String fullPartName = "{" + partNamespace + "}" + partName;
// Extract the corresponding part from soap body.
XmlObject[] paths = xml.selectPath( "declare namespace env='" + ctx.getSoapVersion().getEnvelopeNamespace() + "';"
+ "declare namespace ns='" + partNamespace + "';" + "$this/env:Envelope/env:Body/ns:" + partName);
SchemaGlobalElement elm;
try {
elm = ctx.getSchemaTypeLoader().findElement(QName.valueOf(fullPartName));
} catch (Exception e) {
log.error("Exception while loading schema information for " + fullPartName, e);
throw new XmlException("Exception while loading schema information for " + fullPartName, e);
}
if ( elm != null ){
validateMessageBody(ctx, errors, elm.getType(), paths[0]);
// Ensure no other elements in body.
NodeList children = XmlUtils.getChildElements((Element) paths[0].getDomNode().getParentNode());
for (int c = 0; c < children.getLength(); c++){
QName childName = XmlUtils.getQName(children.item(c));
// Compare child QName to full part QName.
if (!fullPartName.equals(childName.toString())){
XmlCursor cur = paths[0].newCursor();
cur.toParent();
cur.toChild( childName );
errors.add( XmlError.forCursor( "Invalid element [" + childName + "] in SOAP Body", cur ) );
cur.dispose();
}
}
}
log.debug("SoapBody validation errors: " + errors.size());
}
return errors;
}
/** Helper message for validating the body of a Soap message. */
private static void validateMessageBody(WsdlContext ctx, List errors, SchemaType type, XmlObject msg) throws XmlException {
// Need to create new body element of correct type from xml text since we want to retain line-numbers.
XmlOptions xmlOptions = new XmlOptions();
xmlOptions.setLoadLineNumbers();
xmlOptions.setLoadLineNumbers(XmlOptions.LOAD_LINE_NUMBERS_END_ELEMENT);
XmlCursor cur = msg.newCursor();
Map map = new HashMap();
while (cur.hasNextToken()) {
if (cur.toNextToken().isNamespace())
map.put(cur.getName().getLocalPart(), cur.getTextValue());
}
xmlOptions.setUseDefaultNamespace();
xmlOptions.setSaveOuter();
// Problem: prefixes might get redefined/changed when saving which can cause xsi:type refs to
// reference wrong/non-existing namespace.. solution would probably be to manually walk through
// document and update xsi:type refs with new prefix. The setUseDefaultNamespace() above helps
// here but is not a definitive fix.
String xmlText = msg.copy().changeType(type).xmlText(xmlOptions);
xmlOptions.setLoadAdditionalNamespaces(map);
XmlObject obj = type.getTypeSystem().parse(xmlText, type, xmlOptions);
obj = obj.changeType(type);
// Create internal error list.
ArrayList
© 2015 - 2025 Weber Informatics LLC | Privacy Policy