Feladat: Hasonlítsam össze mi változott az ügyfél két WS Interface-én a verzióváltás során. Tulajdonképpen a különböző alkalmazás verziók WSDL filejait kellene összediffelni. Mivel a WSDL file-ok rohadt nagyok, ráadásul generáltak, ezért kézzel (WinDiff, AraxisMerge) összehasonlítgatni elég unalmas lett volna. (Az egyik WSDL file-t a build-ben egy ant taskkal generálják axis segítségével, a másikat wiszont a JAX-WS -es wsgen taskkal.)

Mivel ezt minden verzióváltáskor el kell majd játszani (ugyanis a fejlesztők nem szeretnek Release Note-ot/Change Logot írni), igy célszerűnek tűnt írni rá egy kis programot.

Amit össze kell hasonlítani:

  • Az elérhető függvények listája (van-e új WS method, töröltek-e belőle)
  • Az egyes függvények paraméterei változtak-e?
  • Az WS által használt összetett típusok változtak-e?

1. WSDL file szerkezete:

Hivatalos leírás itt.

A számomra lényeges node-ok a WSDL file-ban:

  • portType node: Az operation-ok listáját tartalmazza.
    • operation nodes: A távolról meghívható függvény neve, egy imput és egy output message. (elvileg lehetne több output message is)
  • message nodes: Absztrakt leírása az infónak ami a kliens és a szerver között utazik. Az “függvények” imput és output üzenetei itt vannak kibontva. Az AXIS-os WSDL esetén itt már egyszrű vagy összetett típusok szerepelnek, míg a JAX-WS es WSDL-ben element hivatkozások a típusokra.
  • types node: Adat típus definíciók
    • complexType node: Az összetett adattípusok definíciója.

2. XPath használata:

Először is kell egy Document (org.w3c.dom.Document) object ami az XML-ünket fogja tartalmazni a memóriában.

DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder docBuilder = null;
try {
docBuilder = docBuilderFactory.newDocumentBuilder();
xmlDocument = docBuilder.parse(ClassLoader.getSystemResourceAsStream(filePath));
} catch (ParserConfigurationException e) {
log.error("ParserConfigurationException ", e);
} catch (FileNotFoundException e) {
log.error("FileNotFoundException ", e);
} catch (SAXException e) {
log.error("SAXException ", e);
} catch (IOException e) {
log.error("IOException ", e);
}

Ezután kell egy XPath (javax.xml.xpath.XPath) object amiben a kereső kifejezést definiáljuk. (Megjegyzés: Az xsd namespace-t azért definiáljuk mert a komplex type-ok ebben a namespace-ben vannak. Ha nem adjuk meg az XPath-nak, nem fogja megtalálni. Bővebb leírás a NamespaceContext-ről itt.)

XPath xpath = XPathFactory.newInstance().newXPath();
xpath.setNamespaceContext(new NamespaceContext() {
@SuppressWarnings("unchecked")
@Override
public Iterator getPrefixes(String namespaceURI) {
return null;
}
@Override
public String getPrefix(String namespaceURI) {
return "xsd";
}
@Override
public String getNamespaceURI(String prefix) {
return "http://www.w3.org/2001/XMLSchema";
}
});

Végül szükség van egy XPathExpression (javax.xml.xpath.XPathExpression) objectre amit az XPath segítségével hozunk létre és kiértékelünk az XML documentünkön:

try {
XPathExpression  expr = xpath.compile("/definitions/portType/operation/@name");
Object exprResult = expr.evaluate(xmlDocument, XPathConstants.NODESET);
} catch (XPathExpressionException e) {
log.error("XPath error!", e);
}
NodeList methodNameNodes = (NodeList) exprResult;

Az XPathExpression evaluate() methodusa egy objecttel tér vissza. Ennek az objectnek a típusát az evaluate method második paraméterében tudjuk meghatározni (boolean, string, nodeset, …). A mi esetünkben ezt símán kasztolhatjuk NodeList-re, amit aztán egy for ciklussal végigjárhatunk.

3. WS Methodok kilistázása a WSDL file-ból:

A fenti példa épp ezt csinálja. A “/definitions/portType/operation/@name” xpath kifejezés kilistázza a wsdl fileból az összes elérhető WS method nevét.

4. WS methodok Input és Output paraméterei

A portType node-on belül a method absztrakt definíciója a következőképpen néz ki:

<operation name="findAllActivePacks">
<input message="tns:findAllActivePacks" />
<output message="tns:findAllActivePacksResponse" />
</operation>

Itt van leírva, hogy az adott WS method meghívásakor a kliens milyen input messaget küld a szervernek és a szerver milyen output üzenettel válaszol. A konkrét paraméterlista viszont a megfelelő messagek-ben van ledefiniálva (az AXIS esetén), vagy hivatkozva elementként a Type definícióknál (a JAX-WS -es WSDL-nél).

Példa Axis-os message definíció:

<message name="removeUserFromGroup">
<part xmlns:partns="http://www.w3.org/2001/XMLSchema"
type="partns:string" name="string" />
<part xmlns:partns="http://www.w3.org/2001/XMLSchema"
type="partns:string" name="string0" />
</message>

Példa JAX-WS message definíció:

<message name="login">
<part xmlns:partns="atlas" name="parameters" element="partns:login" />
</message>

Ahol az element a types-on belül van definiálva:

<xsd:element name="login">
<xsd:complexType>
<xsd:sequence>
<xsd:element xmlns:tp="java:com.company.auth.ws"
name="authWSReq" type="tp:AuthWSReq" />
</xsd:sequence>
</xsd:complexType>
</xsd:element>

Tehát először kikeressük a message nevét a portType/operation-ból, majd ezzel a névvel kikeressük az üzenetet. (JAX-WS-nél az üzenet neve ugyanaz mint az element-é amire az adott message hivatkozik, így a message helyett rögtön kereshetjük az elementet, ezzel egy lépést megspórolva.) Példa egy függvény imput paramétereinek kikeresésére:

private ArrayList<String> getInputParameters(String wsMethodName) {
ArrayList<String> resultInputParameters = null;
String expression = "/definitions/portType/operation[@name='" + wsMethodName + "']/input/@message";
try {
expr = xpath.compile(expression);
exprResult = expr.evaluate(xmlDocument, XPathConstants.NODESET);
} catch (XPathExpressionException e) {
log.error("Can't find input message for this method!", e);
}
NodeList inputParamMessageNames = (NodeList) exprResult;
if (inputParamMessageNames.getLength() > 0) {
// it has only one input message definition under the portType node
String inputParamMessageName = inputParamMessageNames.item(0).getNodeValue();
// cut off the tns: namespace
inputParamMessageName = inputParamMessageName.substring(4);
if (isAxisWSDL) {
expression = "/definitions/message[@name='" + inputParamMessageName + "']/part/@type";
} else {
expression = "/definitions/types/schema/element[@name='" + inputParamMessageName
+ "']/complexType/sequence/element/@type";
}
try {
expr = xpath.compile(expression);
exprResult = expr.evaluate(xmlDocument, XPathConstants.NODESET);
} catch (XPathExpressionException e) {
log.error("Can't find message deffinition for this type!", e);
}
NodeList inputMessageParameterTypes = (NodeList) exprResult;
if (inputMessageParameterTypes.getLength() > 0) {
String paramType = null;
resultInputParameters = new ArrayList<String>();
for (int i = 0; i < inputMessageParameterTypes.getLength(); i++) {
paramType = inputMessageParameterTypes.item(i).getNodeValue();
if (isAxisWSDL) {
// cut off the partns: namespace value
paramType = paramType.substring(7);
} else {
// cut off the "tp:" namespace value
paramType = paramType.substring(3);
}
resultInputParameters.add(paramType);
}
}
}
return resultInputParameters;
}

Megjegyzés: Az, hogy substringgel levágom a namespace-t az egyes attribútumokról, csúnya megoldás. Megfelelően kellene kezelni a namespace-eket. Viszont a célnak éppen pont megfelelt.

5. Complex type-ok összehasonlítása

Az összetett típusok a definitions/types/xsd:schema xml node alatt vannak definiálva. Például:

<xsd:complexType name="Pack">
<xsd:sequence>
<xsd:element type="xsd:boolean" name="active"
minOccurs="1" maxOccurs="1" />
<xsd:element type="xsd:string" name="level"
minOccurs="1" nillable="true" maxOccurs="1" />
<xsd:element type="xsd:string" name="packName"
minOccurs="1" nillable="true" maxOccurs="1" />
<xsd:element type="xsd:string" name="packId"
minOccurs="1" nillable="true" maxOccurs="1" />
</xsd:sequence>
</xsd:complexType>

Itt 2 dolog fontos:

  • Változott-e a komplex type-ok listája (jött-e új, vagy töröltek-e meglévőt)?
  • Az egyes komplex type-ok property-jei változtak-e?

Tehát egyrészt kell az összetett típusok neveinek a listája, amelyet a következő xpath kifejezéssel kérhetünk le:

"/definitions/types/schema/complexType/@name"

Ezen kívül minden egyes komplex típusra meg kell néznünk a property-ket. Először lekérjük a property-k listáját:

String expression = "/definitions/types/schema/complexType[@name='"
+ complexTypeName + "']/sequence/element";
try {
expr = xpath.compile(expression);
exprResult = expr.evaluate(xmlDocument, XPathConstants.NODESET);
} catch (XPathExpressionException e) {
log.error("XPathExpressionException: ", e);
}
NodeList nodes = (NodeList) exprResult;

Majd beolvassuk a property-k tulajdonságait egy általunk definiált osztályba (ParameterObject), hogy később össze tudjuk hasonlítgatni őket:

String name = null;
String type = null;
String minOccurs = null;
String maxOccurs = null;
boolean nillable = false;
for (int i = 0; i < nodes.getLength(); i++) {
name = nodes.item(i).getAttributes().getNamedItem(
PARAMETER_NAME_PROPERTY).getNodeValue();
type = nodes.item(i).getAttributes().getNamedItem(
PARAMETER_TYPE_PROPERTY).getNodeValue();
minOccurs = nodes.item(i).getAttributes().getNamedItem(
PARAMETER_MINOCCURS_PROPERTY).getNodeValue();
maxOccurs = nodes.item(i).getAttributes().getNamedItem(
PARAMETER_MAXOCCURS_PROPERTY).getNodeValue();
if (nodes.item(i).getAttributes().getNamedItem(PARAMETER_NILLABLE_PROPERTY) != null) {
nillable = Boolean.parseBoolean(nodes.item(i).getAttributes().
getNamedItem("nillable").getNodeValue());
}
resultMap.put(name, new ParameterObject(name, type, minOccurs, maxOccurs, nillable));
}

Szinte készen is vagyunk. Már ami a WSDL feldolgozását illeti. Az összehasonlítgatást (method paraméterek, complex type property-k esetén) én az erre definiált osztályok equals() methodjának felüldefiniálásával oldottam meg…

Follow

Get every new post delivered to your Inbox.