/*******************************************************************************
* 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;
import java.io.File;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import javax.wsdl.Definition;
import javax.wsdl.Types;
import javax.wsdl.WSDLException;
import javax.wsdl.extensions.schema.Schema;
import javax.wsdl.factory.WSDLFactory;
import javax.wsdl.xml.WSDLReader;
import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.ebayopensource.turmeric.runtime.common.exceptions.SchemaExtractionException;
import org.ebayopensource.turmeric.runtime.common.impl.binding.jaxb.validation.util.SchemaExtractorUtil;
import org.w3c.dom.Attr;
import org.w3c.dom.Comment;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;
import com.ibm.wsdl.factory.WSDLFactoryImpl;
import com.ebay.kernel.logger.LogLevel;
import com.ebay.kernel.logger.Logger;
/**
* @author arajmony
*
*/
public class SchemaExtractor {
private static final Logger s_logLogger = Logger.getInstance(SchemaExtractor.class.getName());
private ClassLoader m_ClassLoader;
private String m_serviceAdminName;
private String m_basePathForSchema = "META-INF/soa/services/schema/";
private String m_wsdlPath;
private InputSource m_wsdlsInputSource;
private boolean isENS;
private static final String XML_URI= "http://www.w3.org/2001/XMLSchema";
private static final String XMLNS= "xmlns";
private static final String UPA_REMOVED_PREFIX = "UPA_REMOVED_";
Map<String, String> m_tnsAndFileNameMapForNormal = new HashMap<String, String>();
Map<String, String> m_tnsAndFileNameMapForUPAFree = new HashMap<String, String>();
Map<String, String> m_currTnsAndFileNameMap= null;
private enum NodeTypeEnum{
NORMAL, UPA_FREE;
}
public SchemaExtractor(String serviceAdminName){
m_ClassLoader = Thread.currentThread().getContextClassLoader();
m_serviceAdminName = serviceAdminName;
m_basePathForSchema += m_serviceAdminName + "/";
File file = new File(m_basePathForSchema);
boolean mkdirStatus = file.mkdirs();
if(!mkdirStatus)
s_logLogger.log(LogLevel.WARN,"status of mkdir im schemaextractor for service : " + m_serviceAdminName+ " is "+ mkdirStatus);
}
public SchemaBaseDetails getMasterSchemaFilesDetails ()
throws SchemaExtractionException{
SchemaBaseDetails schemaBaseDetails = new SchemaBaseDetails();
m_wsdlPath = getWSDLPathToBeUsed(m_serviceAdminName);
Definition wsdlDefinition = getWSDLsDefintion(m_wsdlPath);
//call the method twice one each for each node type
buildXMLFilesForSchemas(wsdlDefinition,NodeTypeEnum.NORMAL);
buildXMLFilesForSchemas(wsdlDefinition,NodeTypeEnum.UPA_FREE);
cleanTheGeneratedUPAFreeSchemas();
/*
* create the response
*/
schemaBaseDetails.setServiceAdminName(m_serviceAdminName);
schemaBaseDetails.setFilePathForStrictValidation(m_basePathForSchema + UPA_REMOVED_PREFIX + "master.xsd");
schemaBaseDetails.setIsEnableNSWSDL(isENS);
return schemaBaseDetails;
}
/**
* This method is used to clean the genearted UPA schemas. This it does by the following
*
* 1. sax parse the WSDL and identify those types which have xs:any. this list should have namespace
* aware elements
* 2. sax parse the doc and identify if any type depends on the types derived from step # 1.
* 3. remove xs:any only from those elements which are in the "being-referred" list
* @throws SchemaExtractionException
*/
private void cleanTheGeneratedUPAFreeSchemas() throws SchemaExtractionException{
List<QName> listOfTypesHavingXSAny;
List<QName> listOfInterestedTypesHavingXSAny;
Set<QName> setOfTypesReferringToXSAnyTypes;
try {
listOfTypesHavingXSAny = SchemaExtractorUtil.getListOFTypesHavingXSAny(m_wsdlPath);
listOfInterestedTypesHavingXSAny = removeNonSoaFwkBaseTypesFromList(listOfTypesHavingXSAny);
setOfTypesReferringToXSAnyTypes = SchemaExtractorUtil.getListOfTypesReferringTheXSAnyTypes(listOfInterestedTypesHavingXSAny,m_wsdlPath);
if(s_logLogger.isLogEnabled(LogLevel.INFO))
s_logLogger.log(LogLevel.INFO,"listOfTypesReferringToXSAnyTypes : " + setOfTypesReferringToXSAnyTypes);
Map<String,List<String>> mapOfTNSAndTypeName = new HashMap<String, List<String>>(setOfTypesReferringToXSAnyTypes.size());
for(QName currQName : setOfTypesReferringToXSAnyTypes){
String ns = currQName.getNamespaceURI();
String typeName = currQName.getLocalPart();
if(mapOfTNSAndTypeName.get(ns)== null){
ArrayList<String> list = new ArrayList<String>(3);
mapOfTNSAndTypeName.put(ns, list);
}
mapOfTNSAndTypeName.get(ns).add(typeName);
}
//types to be removed from the respective schema have now been identified and they reside inside the
// map mapOfTNSAndTypeName, where the key is the namespace and the value is a list of type names
Set<Entry<String, List<String>>> set = mapOfTNSAndTypeName.entrySet();
for(Entry<String, List<String>> currEntry : set){
String currSchemasTNS = currEntry.getKey();
String fileNameOfSchemaTobeUPAfreed = m_tnsAndFileNameMapForUPAFree.get(currSchemasTNS);
String xsdFileRelativePath = m_basePathForSchema + fileNameOfSchemaTobeUPAfreed;
SchemaExtractorUtil.removeTheTypesFromTheFile(currEntry.getValue(),xsdFileRelativePath);
}
} catch (Exception e) {
throw new SchemaExtractionException(e.getMessage());
}
}
private List<QName> removeNonSoaFwkBaseTypesFromList(
List<QName> listOfTypesHavingXSAny) {
List<QName> response = new ArrayList<QName>(2);
QName baseSvcReq = new QName("http://www.ebayopensource.org/turmeric/common/v1/types","BaseRequest");
QName baseSvcRes = new QName("http://www.ebayopensource.org/turmeric/common/v1/types","BaseResponse");
for(QName curr : listOfTypesHavingXSAny){
if(curr.equals(baseSvcReq) || curr.equals(baseSvcRes))
response.add(curr);
}
return response;
}
private Definition getWSDLsDefintion(String wsdlRelativePath)
throws SchemaExtractionException{
WSDLFactory factory = null;
try {
factory = WSDLFactoryImpl.newInstance();
} catch (WSDLException e) {
String errMsg = "Exception while trying to create WSDL factory : "+ e;
s_logLogger.log(LogLevel.ERROR, errMsg);
throw new SchemaExtractionException(errMsg,e);
}
WSDLReader reader = factory.newWSDLReader();
InputStream inputStream=null;
inputStream = m_ClassLoader.getResourceAsStream(wsdlRelativePath);
if(inputStream == null){
String msg = "InputStream could not be created for WSDL @ " + wsdlRelativePath;
s_logLogger.log(LogLevel.ERROR, msg);
throw new SchemaExtractionException(msg);
}
Definition definition = null;
m_wsdlsInputSource = new InputSource(inputStream);
try {
definition = reader.readWSDL(null,m_wsdlsInputSource);
} catch (WSDLException e) {
String errMsg = "Exception while trying to create WSDL Definition : "+ e;
s_logLogger.log(LogLevel.ERROR, errMsg);
throw new SchemaExtractionException(errMsg,e);
}
return definition;
}
private String getWSDLPathToBeUsed(String serviceAdminName)
throws SchemaExtractionException{
String wsdlRelativePath= "soa/services/wsdl/" + serviceAdminName + "_mns.wsdl";
URL url = m_ClassLoader.getResource(wsdlRelativePath);
if(url == null){
s_logLogger.log(LogLevel.WARN, "mns wsdl not found for WSDL with serviceAdminName " + serviceAdminName + ". Trying for normal WSDL...");
wsdlRelativePath = "META-INF/soa/services/wsdl/"+serviceAdminName+"/" + serviceAdminName + ".wsdl";
url = m_ClassLoader.getResource(wsdlRelativePath);
if(url == null){
String msg = "both normal and mns wsdl not found for WSDL with serviceAdminName " + serviceAdminName;
s_logLogger.log(LogLevel.ERROR,msg );
throw new SchemaExtractionException(msg);
}
}else
isENS = true;
if(s_logLogger.isLogEnabled(LogLevel.INFO))
s_logLogger.log(LogLevel.INFO, "WSDL for service admin name " + serviceAdminName + " found in path " + wsdlRelativePath);
return wsdlRelativePath;
}
private void buildXMLFilesForSchemas(Definition wsdlDefinition,NodeTypeEnum nodeType)
throws SchemaExtractionException{
if(nodeType == NodeTypeEnum.NORMAL)
m_currTnsAndFileNameMap = m_tnsAndFileNameMapForNormal;
else
m_currTnsAndFileNameMap = m_tnsAndFileNameMapForUPAFree;
Map<String, String> globalPrefixNSMap = wsdlDefinition.getNamespaces();
Types types = wsdlDefinition.getTypes();
List<Object> listOfElements = types.getExtensibilityElements();
if(s_logLogger.isLogEnabled(LogLevel.INFO))
s_logLogger.log(LogLevel.INFO, "# of schemas = " + listOfElements.size());
/*
* get the target namespace of each of the schemas
*/
List<String> allTNSList = new ArrayList<String>();
for (int k = 0; k < listOfElements.size(); k++) {
Schema schema = (Schema) listOfElements.get(k);
Element element = schema.getElement();
Attr attribute = element.getAttributeNode("targetNamespace");
allTNSList.add(attribute.getNodeValue());
}
for(int k = 0 ; k< allTNSList.size() ; k++){
String fileName = "schema_" + k + ".xsd";
if(nodeType == NodeTypeEnum.UPA_FREE)
fileName = UPA_REMOVED_PREFIX + fileName;
m_currTnsAndFileNameMap.put(allTNSList.get(k), fileName);
}
if(s_logLogger.isLogEnabled(LogLevel.INFO))
s_logLogger.log(LogLevel.INFO, "Target namespaces are " + allTNSList);
/*
* Creating one file each for each schema
*/
for (int k = 0; k < listOfElements.size(); k++) {
Schema schema = (Schema) listOfElements.get(k);
Element element = schema.getElement();
Attr attribute = element.getAttributeNode("targetNamespace");
String currTNS = attribute.getNodeValue();
if(s_logLogger.isLogEnabled(LogLevel.INFO))
s_logLogger.log(LogLevel.INFO, "processing schema section with target namespace as " + currTNS);
/*
* get all attributes in the current schema element
*/
NamedNodeMap schemaAttributes = element.getAttributes();
Map<String, String> currSchemasAttrMap = new HashMap<String, String>();
for(int l = 0 ; l < schemaAttributes.getLength(); l++){
Node node = schemaAttributes.item(l);
currSchemasAttrMap.put(node.getNodeName(),node.getNodeValue());
}
/*
* identify the attributes/prefixes which are defined in wsdl:definition but not in the schema.
* And then add the delta prefix-ns mappings to the new schema node
*/
HashMap<String, String> additonalAttrs= new HashMap<String, String>();
Set<Entry<String, String>> entrySet = globalPrefixNSMap.entrySet();
for(Entry<String, String> currEntry : entrySet){
if(currSchemasAttrMap.containsKey(currEntry.getKey())){
s_logLogger.log(LogLevel.FINE, "attribute already present in local schema so not overwriting : "+ currEntry.getKey());
}else{
additonalAttrs.put(currEntry.getKey(), currEntry.getValue());
}
}
/*
* 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_logLogger.log(LogLevel.ERROR, msg);
throw new SchemaExtractionException(msg,e);
}
// create an instance of DOM
Document document = db.newDocument();
//clone the node
Node clonedNode = document.importNode(element,true);
//add the proper prefixes and additional attributes in the schema element
addAdditionalPrefixesToClonedNode(clonedNode,additonalAttrs);
//add all the import stmts to the cloned schema element
addAdditionalImportStmtsToClonedNode(clonedNode,currTNS,m_currTnsAndFileNameMap,document,false);
//append the schema element to the document
document.appendChild(clonedNode);
/*
* Schema node is now created and stand alone. so lets write it to a file
*/
File fileOutput = new File(m_basePathForSchema + m_currTnsAndFileNameMap.get(currTNS));
if(s_logLogger.isLogEnabled(LogLevel.INFO))
s_logLogger.log(LogLevel.INFO, "schema file path : " + fileOutput.getAbsolutePath());
SchemaExtractorUtil.writeSchemaDocumentToFile(document,fileOutput);
}
/*
* creating the master_schema file
*/
createMasterSchemaFile(m_currTnsAndFileNameMap,nodeType);
}
private void createMasterSchemaFile(Map<String, String> tnsAndFileNameMap, NodeTypeEnum nodeType)
throws SchemaExtractionException{
if(s_logLogger.isLogEnabled(LogLevel.INFO))
s_logLogger.log(LogLevel.INFO, "creating the master schema file : " + tnsAndFileNameMap);
String tns = null;
String ouptutFilePath = null;
if(nodeType == NodeTypeEnum.NORMAL){
tns = "http://www.ebay.com/marketplace/schema/validation/" +m_serviceAdminName;
ouptutFilePath = m_basePathForSchema + "master.xsd";
}
else{
tns = "http://www.ebay.com/marketplace/schema/validation/upa/free/" +m_serviceAdminName;
ouptutFilePath = m_basePathForSchema + UPA_REMOVED_PREFIX + "master.xsd";
}
/*
* creating a new master 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_logLogger.log(LogLevel.ERROR, msg);
throw new SchemaExtractionException(msg,e);
}
// create an instance of DOM
Document document = db.newDocument();
Element root = document.createElement("schema");
root.setAttribute("targetNamespace", tns );
document.appendChild(root);
root.setAttribute("xmlns", XML_URI);
// create comment
Comment commentNode = document.createComment("Generated File. .... ");
root.appendChild(commentNode);
addAdditionalImportStmtsToClonedNode(root,null,tnsAndFileNameMap,document,true);
/*
* Schema node is now created and stand alone. so lets write it to a file
*/
File fileOutput = new File(ouptutFilePath);
if(s_logLogger.isLogEnabled(LogLevel.INFO))
s_logLogger.log(LogLevel.INFO, "schema file path : " + fileOutput.getAbsolutePath());
SchemaExtractorUtil.writeSchemaDocumentToFile(document,fileOutput);
}
/**
*
* @param clonedNode
* @param currTNS THIS CAN BE NULL see the current callers
* @param tnsAndFileNameMap
* @param document
* @param addAll - this is redundant , the same is already achieved by currTNs in a wy, but still to retain this as it is more clear
*/
private void addAdditionalImportStmtsToClonedNode(Node clonedNode,
String currTNS, Map<String, String> tnsAndFileNameMap, Document document, boolean addAll) {
// identify the first child since the import stmts should be added as the first childs
Node firstChildNode = clonedNode.getFirstChild();
Set<Entry<String, String>> set = tnsAndFileNameMap.entrySet();
for(Entry<String, String> setEntry : set){
if((!setEntry.getKey().equals(currTNS)) || addAll){
Element importElement = null;
importElement = document.createElementNS(XML_URI,"import");
importElement.setAttribute("namespace", setEntry.getKey());
importElement.setAttribute("schemaLocation", setEntry.getValue());
clonedNode.insertBefore(importElement, firstChildNode);
//clonedNode.appendChild(importElement);
}
}
}
private void addAdditionalPrefixesToClonedNode(
Node clonedNode, HashMap<String, String> additonalAttrs) {
if(s_logLogger.isLogEnabled(LogLevel.INFO))
s_logLogger.log(LogLevel.INFO, "additional prefixes to be added");
Element element = (Element) clonedNode;
Set<Entry<String, String>> entrySet = additonalAttrs.entrySet();
for(Entry<String, String> currEntry : entrySet){
if(currEntry.getKey() != null && !currEntry.getKey().trim().equals(""))
element.setAttribute(XMLNS + ":" + currEntry.getKey(), currEntry.getValue());
}
/*
* the xmlns attribute
* 1. should always be there in a schema
* 2. should always point to http://www.w3.org/2001/XMLSchema
*/
element.setAttribute(XMLNS, XML_URI);
}
}