/*******************************************************************************
* Copyright (c) 2006-2010 eBay Inc. All Rights Reserved.
* Licensed 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
*******************************************************************************/
/**
*
*/
package org.ebayopensource.turmeric.runtime.common.impl.binding.jaxb.validation.util;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.ebayopensource.turmeric.runtime.common.exceptions.SchemaExtractionException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import com.ebay.kernel.logger.LogLevel;
import com.ebay.kernel.logger.Logger;
/**
* @author arajmony
*
*/
public class SchemaExtractorUtil {
private static final Logger s_logger = Logger.getInstance(SchemaExtractorUtil.class.getName());
/**
* Given the WSDL's relative path, this method returns a list of QNames of those types
* which have a xs:any element inside it.
* @param inputSource
* @return
* @throws Exception
*/
public static List<QName> getListOFTypesHavingXSAny(String wsdlRelativePath)
throws Exception{
List<QName> listOfQNames = new ArrayList<QName>();
class MyHandler extends DefaultHandler{
private List<QName> refListOfQNames;
private String currTNS;
private String currTypeName;
private String lastElementName;
public MyHandler(List<QName> list) {
refListOfQNames = list;
}
@Override
public void startElement(String uri, String localName,
String qName, Attributes attributes) throws SAXException {
String elementName = ("".equals(localName)) ? qName : localName;
if (elementName.equals("schema")
|| elementName.contains(":schema")) {
currTNS = attributes.getValue("targetNamespace");
}
//search for element's name also. This will come in handy for anonymous complex types.
if (elementName.equals("element")
|| elementName.contains(":element")) {
lastElementName = attributes.getValue("name");
}
if (elementName.equals("complexType")
|| elementName.contains(":complexType")) {
currTypeName = attributes.getValue("name");
//support for anonymous types
if(currTypeName == null)
currTypeName = lastElementName;
}
if (elementName.equals("any")
|| elementName.contains(":any")) {
QName currTypesQName = new QName(currTNS,currTypeName);
refListOfQNames.add(currTypesQName);
}
}
}
try {
SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
InputStream inputStream=null;
inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(wsdlRelativePath);
if(inputStream == null){
String msg = "InputStream could not be created for WSDL @ " + wsdlRelativePath;
s_logger.log(LogLevel.ERROR, msg);
throw new SchemaExtractionException(msg);
}
parser.parse(inputStream, new MyHandler(listOfQNames));
} catch (ParserConfigurationException e) {
String msg = "ParserConfigurationException at getListOFTypesHavingXSAny : " + e;
s_logger.log(LogLevel.ERROR, msg);
throw new SchemaExtractionException(msg,e);
} catch (SAXException e) {
String msg = "SAXException at getListOFTypesHavingXSAny : " + e;
s_logger.log(LogLevel.ERROR, msg);
throw new SchemaExtractionException(msg,e);
}
if(s_logger.isLogEnabled(LogLevel.INFO))
s_logger.log(LogLevel.INFO, "list of types having xs:any for WSDL : " + wsdlRelativePath +
" is " + listOfQNames);
return listOfQNames;
}
/**
* Given the WSDLs relative path and the list of QNames of the types which has xs:any this method would return
* a set of QNames of those types which cause UPA issue bcos of xs:any.This is identified by the logic
* 1. If a type using xs:any is being extended by some other type or being referred as a composite element, then
* UPA is true for this case.
* @param listOFTypesHavingXSAny
* @param wsdlRelativePath
* @return
*/
public static Set<QName> getListOfTypesReferringTheXSAnyTypes(List<QName> listOFTypesHavingXSAny,String wsdlRelativePath)
throws Exception{
Set<QName> setOfQNames = new HashSet<QName>();
Map<String, QName> mapOfNamesAndQNames = new HashMap<String, QName>();
/*
* get the local name only from the list of QNames as
* 1. it is easier while parsing the xml doc to identify such elements
* 2. it is also legal, since as of today (SOA 2.7) the fwk does not support two types of the same name
* even if they belong to a different namespace //TODO correct this once this stmt # 2 becomes invalid.
*/
List<String> onlyTypeNamesOfTypesHavingXSAny = new ArrayList<String>(listOFTypesHavingXSAny.size());
for(QName currQName : listOFTypesHavingXSAny){
onlyTypeNamesOfTypesHavingXSAny.add(currQName.getLocalPart());
mapOfNamesAndQNames.put(currQName.getLocalPart(), currQName);
}
class MyHandler extends DefaultHandler{
Set<QName> refQNameSet;
Map<String, QName> refMap;
private String currTypeName;
private String lastElementName;
MyHandler(Set<QName> arg1,Map<String, QName> arg2){
refQNameSet = arg1;
refMap = arg2;
}
@Override
public void startElement(String uri, String localName,
String qName, Attributes attributes) throws SAXException {
String elementName = ("".equals(localName)) ? qName : localName;
//search for element's name also. This will come in handy for anonymous complex types.
if (elementName.equals("element")
|| elementName.contains(":element")) {
lastElementName = attributes.getValue("name");//this is recorded for supporting anonymous types
String referredTypeName = attributes.getValue("type");
if(referredTypeName == null)
return;
int index = referredTypeName.indexOf(":");
if(index > 0){
referredTypeName = referredTypeName.substring(index+1);
}
if(refMap.get(referredTypeName) != null){
refQNameSet.add(refMap.get(referredTypeName));
}
}
if (elementName.equals("complexType")
|| elementName.contains(":complexType")) {
if(attributes != null)
currTypeName = attributes.getValue("name");
//support for anonymous types
if(currTypeName == null)
currTypeName = lastElementName;
}
//<xs:extension base="tns:BaseServiceRequest">
if (elementName.equals("extension")
|| elementName.contains(":extension")) {
String base = attributes.getValue("base");
int index = base.indexOf(":");
if(index > 0){
base = base.substring(index+1);
}
if(refMap.get(base) != null){
refQNameSet.add(refMap.get(base));
}
}
}
}
try {
SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
InputStream inputStream=null;
inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(wsdlRelativePath);
if(inputStream == null){
String msg = "InputStream could not be created for WSDL @ " + wsdlRelativePath;
s_logger.log(LogLevel.ERROR, msg);
throw new SchemaExtractionException(msg);
}
parser.parse(inputStream, new MyHandler(setOfQNames,mapOfNamesAndQNames));
} catch (ParserConfigurationException e) {
String msg = "ParserConfigurationException at getListOFTypesHavingXSAny : " + e;
s_logger.log(LogLevel.ERROR, msg);
throw new SchemaExtractionException(msg,e);
} catch (SAXException e) {
String msg = "SAXException at getListOFTypesHavingXSAny : " + e;
s_logger.log(LogLevel.ERROR, msg);
throw new SchemaExtractionException(msg,e);
}
if(s_logger.isLogEnabled(LogLevel.INFO))
s_logger.log(LogLevel.INFO, "list of types referring to types having xs:any for WSDL : " + wsdlRelativePath +
" is " + setOfQNames);
return setOfQNames;
}
public static void removeTheTypesFromTheFile(List<String> value,
String xsdFileRelativePath) throws Exception {
/*
* creating a new schema file
*/
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
// get an instance of builder
DocumentBuilder db=null;
try {
db = dbf.newDocumentBuilder();
} catch (ParserConfigurationException e) {
String msg = "Parser configuration exception : " + e;
s_logger.log(LogLevel.ERROR, msg);
throw new SchemaExtractionException(msg,e);
}
// create an instance of DOM from the schema file
Document document = db.parse(xsdFileRelativePath);
NodeList nodeList = document.getChildNodes();
Element schemaElement = null;
for(int i=0; i < nodeList.getLength(); i++){
Element currElement = (Element)nodeList.item(i);
String nodeName = currElement.getNodeName();
if(nodeName.equals("schema") || nodeName.endsWith(":schema")){
schemaElement = currElement;
break;
}
}
boolean nodeDeleted = false;
if(schemaElement != null){
NodeList childNodesOfSchemaElement = schemaElement.getChildNodes();
for(int i=0; i < childNodesOfSchemaElement.getLength() ; i++){
Node currNode = childNodesOfSchemaElement.item(i);
if( ! (nodeNameEquals("complexType",currNode) || nodeNameEquals("element",currNode))){
continue;
}
//find the name of the complex type or element, iff the name matches with one of those in the input param value
//the code should try and remove the xs:any from such types/elements(anonymous types) only.
NamedNodeMap attributes = currNode.getAttributes();
if(attributes == null)
continue;
String typeOrAnyonymousElementName = attributes.getNamedItem("name").getNodeValue();
if(value.contains(typeOrAnyonymousElementName)){
removeXSAnyNodeFromThisNode(currNode);
nodeDeleted = true;
}
}
}
/*
* re-write the file, since xs:any if present would have been removed
* but do it only if file has been modified
*/
if(nodeDeleted){
if(s_logger.isLogEnabled(LogLevel.INFO))
s_logger.log(LogLevel.INFO, "deletable xs:any was found and removed from " + xsdFileRelativePath);
File outputFile = new File(xsdFileRelativePath);
writeSchemaDocumentToFile(document,outputFile);
}
}
public static boolean nodeNameEquals(String nameToMatch, Node currNode) {
if(currNode == null)
return false;
String nodeName = currNode.getNodeName();
if(nodeName == null)
return false;
if(nodeName.equals(nameToMatch) || nodeName.endsWith(":"+nameToMatch))
return true;
return false;
}
private static void removeXSAnyNodeFromThisNode(
Node currNode) {
Node parentNode = currNode.getParentNode();
String nodeName = currNode.getNodeName();
if(nodeName.equals("any") || nodeName.endsWith(":any")){
parentNode.removeChild(currNode);
s_logger.log(LogLevel.FINE, "removed xs:any from node "+ nodeName);
}else{
NodeList nodesChNodeList = currNode.getChildNodes();
if(nodesChNodeList != null){
for(int i =0; i<nodesChNodeList.getLength() ; i++){
removeXSAnyNodeFromThisNode(nodesChNodeList.item(i));
}
}
}
}
public static void writeSchemaDocumentToFile(Document document, File fileOutput) throws SchemaExtractionException{
FileOutputStream output = null;
try {
output = new FileOutputStream(fileOutput);
} catch (FileNotFoundException e) {
closeOutputStreamSilently(output);
String msg = "Error while trying to write to schema file : " + fileOutput.getAbsolutePath();
s_logger.log(LogLevel.ERROR,msg );
throw new SchemaExtractionException(msg,e);
}
// Use a Transformer for output
TransformerFactory tFactory = TransformerFactory.newInstance();
Transformer transformer = null;
try {
transformer = tFactory.newTransformer();
} catch (TransformerConfigurationException e) {
closeOutputStreamSilently(output);
String msg = "Error while trying to create a transformer : "+ e;
s_logger.log(LogLevel.ERROR,msg );
throw new SchemaExtractionException(msg,e);
}
DOMSource source = new DOMSource(document);
StreamResult result = new StreamResult(output);
try {
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.transform(source, result);
} catch (TransformerException e) {
String msg = "Error while trying to transform the schema to a file : " + e;
s_logger.log(LogLevel.ERROR,msg );
throw new SchemaExtractionException(msg,e);
}finally{
closeOutputStreamSilently(output);
}
if(s_logger.isLogEnabled(LogLevel.INFO))
s_logger.log(LogLevel.INFO, "file " + fileOutput.getAbsolutePath() + " successfully written ");
}
public static void closeOutputStreamSilently(FileOutputStream output) {
try {
if(output != null)
output.close();
} catch (IOException e) {
s_logger.log(LogLevel.WARN, "could not close output stream " + output);
}
}
}