/** * */ package org.ebayopensource.turmeric.tools.codegen.fastserformat.protobuf.validator; import java.util.HashMap; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import javax.xml.namespace.QName; import org.ebayopensource.turmeric.runtime.codegen.common.SchemaNode; import org.ebayopensource.turmeric.runtime.codegen.common.SchemaNodeAttribute; import org.ebayopensource.turmeric.runtime.codegen.common.SchemaNodeLibraryInfo; import org.ebayopensource.turmeric.runtime.common.impl.utils.LogManager; import org.ebayopensource.turmeric.tools.codegen.exception.CodeGenFailedException; import org.ebayopensource.turmeric.tools.codegen.util.CodeGenUtil; import org.xml.sax.Attributes; import org.xml.sax.Locator; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; /** * This class is the handler for SAX parser which FastSerFormatValidationHandler uses to validate the wsdl or xsd. * On each event, it creates a SchemaNode object and delegates to FastSerFormatValidationHandler to * do the validation. * * Apart from delegating, it captures all the node of the schema and caches in a tree structure SchemaNode. * This tree structure is used later by FastSerFormatValidationHandler to do further validations like polymorphism. * * @author rkulandaivel * */ public class SchemaParserEventHandler extends DefaultHandler implements SchemaConstuctConstants { private FastSerFormatValidationHandler m_validator = null; private SchemaNode m_currentSchemaNode = null; private SchemaNodeRepresentationByType m_mapOfAllNodes = null; private Locator locator = null; private String m_targetNamespace = null; private String m_fileName = null; private Map<String, NamespaceNode> m_prefix2NamespaceLink = new HashMap<String, NamespaceNode>(); private static Logger s_logger = LogManager .getInstance(SchemaParserEventHandler.class); private static Logger getLogger() { return s_logger; } public SchemaParserEventHandler(FastSerFormatValidationHandler validator, SchemaNode rootSchemaNode, SchemaNodeRepresentationByType mapOfAllNodes ) { this.m_validator = validator; m_currentSchemaNode = rootSchemaNode; this.m_mapOfAllNodes = mapOfAllNodes; } /** * The method used to store the locator which is used to retrive the line numbers * on each event. */ public void setDocumentLocator(Locator locator) { this.locator = locator; } public void setFileName(String fileName) { this.m_fileName = fileName; } @Override public void endPrefixMapping(String prefix) throws SAXException { //Dereference the prefix from map m_prefix2NamespaceLink. NamespaceNode node = m_prefix2NamespaceLink.get(prefix); NamespaceNode prevNode = node.previous; node.previous = null; //derefence it m_prefix2NamespaceLink.put(prefix, prevNode ); } @Override public void startPrefixMapping(String prefix, String uri) throws SAXException { /* * For each URI, create a new NamespaceNode. * Update the map m_prefix2NamespaceLink with the NamespaceNode instance * If the map contains prefix already, reference the map value to 'previous' property of NamespaceNode. */ NamespaceNode newNode = new NamespaceNode(); newNode.currentNamespace = uri; newNode.previous = m_prefix2NamespaceLink.get(prefix); m_prefix2NamespaceLink.put(prefix, newNode); try { m_validator.doNamespaceValidation( uri, m_fileName ); } catch (CodeGenFailedException e) { throw new SAXException( e ); } } @Override public void endElement(String uri, String localName, String qName) throws SAXException { m_validator.doEndElementValidation(m_currentSchemaNode); if(SCHEMA.equals( localName ) ){ m_targetNamespace = null; } m_currentSchemaNode = m_currentSchemaNode.getParentNode(); } @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { if(SCHEMA.equals( localName ) ){ m_targetNamespace = getAttribute(attributes, "targetNamespace" ); } SchemaNode lastNode = m_currentSchemaNode; m_currentSchemaNode = new SchemaNode(); m_currentSchemaNode.setNodeName(localName); lastNode.getChildNodes().add( m_currentSchemaNode ); m_currentSchemaNode.setParentNode(lastNode); m_currentSchemaNode.setLineNumber(locator.getLineNumber()); m_currentSchemaNode.setColumnNumber(locator.getColumnNumber()); m_currentSchemaNode.setTargetNamespace(m_targetNamespace); m_currentSchemaNode.setFileName(m_fileName); //if m_targetNamespace is not null, then the current element would be a schema //or a child node of schema //hence it is good to resolve attributes. mapAttributes( m_currentSchemaNode, attributes, m_targetNamespace != null ); updateMap( m_currentSchemaNode ); captureLibraryInfo( m_currentSchemaNode ); m_validator.doStartElementValidation(m_currentSchemaNode); } private void updateMap(SchemaNode currentNode){ m_mapOfAllNodes.updateMap(currentNode); } /** * Converts the attributes collection to collection of SchemaNodeAttribute. * The frequent attributes like base, type, ref are resolved to QName here. * The attributes gets resolved only if resolveAttributes is true. * * * @param node * @param attributes * @param resolveAttributes */ private void mapAttributes(SchemaNode node, Attributes attributes, boolean resolveAttributes){ for( int i=0; i<attributes.getLength(); i++){ String localName = attributes.getLocalName(i); String value = attributes.getValue(i); SchemaNodeAttribute attr = new SchemaNodeAttribute(); attr.setAttributeName(localName); attr.setAttributeValue(value); node.getAttributes().add(attr); //The following section resolves the attributes for the prefix and converts to QName. //The code will work good only for the attributes defined in schema node and its child nodes //the flag resolveAttributes should say whether if( !resolveAttributes ){ continue ; } //value cannot be null. if( CodeGenUtil.isEmptyString(value) ){ String msg = "The value attribute is empty on node '" + node.getNodeName() + "', attribute name '" + localName + "' . Is the wsdl valid?"; getLogger().log(Level.SEVERE, msg); //it is not good to throw exception here //because many attributes are valid with out any value. //throw new RuntimeException(msg); continue; } if(ABSTRACT.equals(localName)){ node.setAbstractAttrExists(true); node.setAbstractAttrValue(Boolean.parseBoolean(value)); }else if(MAXOCCURS.equals(localName) ){ node.setMaxoccursAttrExists(true); node.setMaxoccursAttrValue(value); }else if(NAMEATTR.equals(localName) ){ node.setNameAttrExists(true); node.setNameAttrValue(value); }else if(BASE.equals(localName) ){ node.setBaseAttrExists(true); node.setBaseAttrValue( resolveAttributeValue( value ) ); }else if(REF.equals(localName) ){ node.setRefAttrExists(true); node.setRefAttrValue( resolveAttributeValue( value ) ); }else if(TYPEATTR.equals(localName) ){ node.setTypeAttrExists(true); node.setTypeAttrValue( resolveAttributeValue( value ) ); }else if(ITEMTYPE.equals(localName) ){ node.setItemTypeAttrExists(true); node.setItemTypeAttrValue( resolveAttributeValue( value ) ); } } } private QName resolveAttributeValue(String attributeValue){ if(CodeGenUtil.isEmptyString(attributeValue)){ return null; } String prefix = ""; String value = attributeValue; int colanIndex = attributeValue.indexOf( ":" ); if(colanIndex == 0){ String msg = "The value '"+attributeValue+"' does not have proper prefix. If colan is used the prefix should be atleast one character."; getLogger().log(Level.SEVERE, msg); throw new RuntimeException(msg); }else if( colanIndex > 0 ){ prefix = attributeValue.substring(0, colanIndex); value = attributeValue.substring(colanIndex + 1); } prefix = prefix.trim(); value = value.trim(); NamespaceNode namespace = m_prefix2NamespaceLink.get(prefix); if( namespace == null ){ getLogger().log(Level.SEVERE, "The resolved prefix '"+prefix+"' is invalid. Could not find namespace from available map " ); getLogger().log(Level.SEVERE, "The available values from map are " + m_prefix2NamespaceLink ); throw new RuntimeException("The resolved prefix '"+prefix+"' is invalid. Could not find namespace from available map " + m_prefix2NamespaceLink); } QName qName = new QName( namespace.currentNamespace, value ); getLogger().log(Level.INFO, "Resolved '"+attributeValue+"' as " + qName); return qName; } private String getAttribute(Attributes attributes, String attributeName){ String value = null; for( int i=0; i<attributes.getLength(); i++){ String localName = attributes.getLocalName(i); if(attributeName.equals(localName) ){ value = attributes.getValue(i); break; } } return value; } /** * Captures the typeLibrarySource tag if present. * * @param node */ private void captureLibraryInfo( SchemaNode node ){ if(TYPE_LIB_SOURCE.equals(node.getNodeName())){ if( !isValidTypeLibNode( node ) ){ return ; } SchemaNode parent = getSurroundingType(node); String libraryName = null; String namespace = null; for( SchemaNodeAttribute attr : node.getAttributes() ){ if(LIBRARY.equals( attr.getAttributeName() ) ){ libraryName = attr.getAttributeValue(); continue; } if(NAMESPACEATTR.equals( attr.getAttributeName() ) ){ namespace = attr.getAttributeValue(); continue; } } if( namespace != null && libraryName!= null ){ SchemaNodeLibraryInfo libInfo = new SchemaNodeLibraryInfo(); libInfo.setLibraryName(libraryName); libInfo.setNamespace(namespace); parent.setLibraryInfo(libInfo); } } } private boolean isValidTypeLibNode( SchemaNode node ){ if(node.getParentNode() == null || node.getParentNode().getParentNode() == null ){ return false; } if (APPINFO_TAG.contains( node.getParentNode().getNodeName() ) && ANNOTATION_TAG.contains(node.getParentNode().getParentNode().getNodeName() )){ return true; } return false; } private SchemaNode getSurroundingType( SchemaNode node ){ SchemaNode parent = node.getParentNode(); while (parent != null){ String parentNodename = parent.getNodeName(); if(SchemaConstuctConstants.COMPLEXTYPE.equals(parentNodename) || SchemaConstuctConstants.SIMPLETYPE.equals(parentNodename) ){ return parent; } parent = parent.getParentNode(); } return null; } /** * While SAX parsing, prefix2Namespace map can be defined at definitions level and * at schema level. * The scope of prefix defined at definitions level is global i.e. applicable for entire wsdl. * The scope of prefix defined at schema level is applicable only schema node. * * Also, for the same prefix, namespace can be defined at definitions level and at schema level. * So the prefix resolver should be intelligent enough to handle prefix2Namespace Map. * * To achieve that, NamespaceNode contains the namespace value and the reference to previous namespace. * * @author rkulandaivel * */ private static class NamespaceNode{ public String currentNamespace = null; public NamespaceNode previous = null; @Override public String toString() { return "NamespaceNode [currentNamespace=" + currentNamespace + "]"; } } }