/************************************************************************************** * Copyright (C) 2008 EsperTech, Inc. All rights reserved. * * http://esper.codehaus.org * * http://www.espertech.com * * ---------------------------------------------------------------------------------- * * The software in this package is published under the terms of the GPL license * * a copy of which has been included with this distribution in the license.txt file. * **************************************************************************************/ package com.espertech.esper.event.xml; import com.espertech.esper.client.EPException; import com.espertech.esper.client.EventPropertyGetter; import com.espertech.esper.client.PropertyAccessException; import com.espertech.esper.collection.Pair; import com.espertech.esper.epl.generated.EsperEPL2GrammarParser; import com.espertech.esper.event.EventAdapterService; import com.espertech.esper.event.property.Property; import com.espertech.esper.event.property.PropertyParser; import com.espertech.esper.type.IntValue; import com.espertech.esper.type.StringValue; import com.espertech.esper.util.ExecutionPathDebugLog; import org.antlr.runtime.tree.Tree; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.w3c.dom.Node; import javax.xml.namespace.QName; import javax.xml.xpath.*; import java.util.List; /** * Parses event property names and transforms to XPath expressions using the schema information supplied. Supports the * nested, indexed and mapped event properties. */ public class SchemaXMLPropertyParser { /** * Return the xPath corresponding to the given property. * The propertyName String may be simple, nested, indexed or mapped. * * @param propertyName is the event property name * @param namespace is the default namespace * @param schemaModel is the schema model * @param xPathFactory is the xpath factory instance to use * @param rootElementName is the name of the root element * @param eventAdapterService for type lookup and creation * @param xmlEventType the resolving type * @param isAllowFragment whether fragmenting is allowed * @param defaultNamespace default namespace * @return xpath expression * @throws EPException is there are XPath errors */ public static EventPropertyGetter getXPathResolution(String propertyName, XPathFactory xPathFactory, String rootElementName, String namespace, SchemaModel schemaModel, EventAdapterService eventAdapterService, BaseXMLEventType xmlEventType, boolean isAllowFragment, String defaultNamespace) throws EPException { if (log.isDebugEnabled()) { log.debug("Determining XPath expression for property '" + propertyName + "'"); } XPathNamespaceContext ctx = new XPathNamespaceContext(); List<String> namespaces = schemaModel.getNamespaces(); String defaultNamespacePrefix = null; for (int i = 0; i < namespaces.size(); i++) { String prefix = "n" + i; ctx.addPrefix(prefix, namespaces.get(i)); if ((defaultNamespace != null) && (defaultNamespace.equals(namespaces.get(i)))) { defaultNamespacePrefix = prefix; } } Tree ast = PropertyParser.parse(propertyName); Property property = PropertyParser.parse(propertyName, false); boolean isDynamic = property.isDynamic(); SchemaElementComplex rootComplexElement = SchemaUtil.findRootElement(schemaModel, namespace, rootElementName); String prefix = ctx.getPrefix(rootComplexElement.getNamespace()); if (prefix == null) { prefix = ""; } else { prefix += ':'; } StringBuilder xPathBuf = new StringBuilder(); xPathBuf.append('/'); xPathBuf.append(prefix); if (rootElementName.startsWith("//")) { xPathBuf.append(rootElementName.substring(2)); } else { xPathBuf.append(rootElementName); } SchemaElementComplex parentComplexElement = rootComplexElement; Pair<String, QName> pair = null; if (ast.getChildCount() == 1) { pair = makeProperty(rootComplexElement, ast.getChild(0), ctx, true, isDynamic, defaultNamespacePrefix); if (pair == null) { throw new PropertyAccessException("Failed to locate property '" + propertyName + "' in schema"); } xPathBuf.append(pair.getFirst()); } else { for (int i = 0; i < ast.getChildCount(); i++) { boolean isLast = (i == ast.getChildCount() - 1); Tree child = ast.getChild(i); pair = makeProperty(parentComplexElement, child, ctx, isLast, isDynamic, defaultNamespacePrefix); if (pair == null) { throw new PropertyAccessException("Failed to locate property '" + propertyName + "' nested property part '" + child.toString() + "' in schema"); } String text = child.getChild(0).getText(); SchemaItem obj = SchemaUtil.findPropertyMapping(parentComplexElement, text); if (obj instanceof SchemaElementComplex) { parentComplexElement = (SchemaElementComplex) obj; } xPathBuf.append(pair.getFirst()); } } String xPath = xPathBuf.toString(); if ((ExecutionPathDebugLog.isDebugEnabled) && (log.isDebugEnabled())) { log.debug(".parse XPath for property '" + propertyName + "' is expression=" + xPath); } // Compile assembled XPath expression XPath path = xPathFactory.newXPath(); path.setNamespaceContext(ctx); if (log.isDebugEnabled()) { log.debug("Compiling XPath expression '" + xPath + "' for property '" + propertyName + "' using namespace context :" + ctx); } XPathExpression expr; try { expr = path.compile(xPath); } catch (XPathExpressionException e) { String detail = "Error constructing XPath expression from property expression '" + propertyName + "' expression '" + xPath + "'"; if (e.getMessage() != null) { throw new EPException(detail + " :" + e.getMessage(), e); } throw new EPException(detail, e); } // get type SchemaItem item = property.getPropertyTypeSchema(rootComplexElement, eventAdapterService); if ((item == null) && (!isDynamic)) { return null; } Class resultType; if (!isDynamic) { resultType = SchemaUtil.toReturnType(item); } else { resultType = Node.class; } FragmentFactory fragmentFactory = null; if (isAllowFragment) { fragmentFactory = new FragmentFactoryDOMGetter(eventAdapterService, xmlEventType, propertyName); } return new XPathPropertyGetter(propertyName, xPath, expr, pair.getSecond(), resultType, fragmentFactory); } private static Pair<String, QName> makeProperty(SchemaElementComplex parent, Tree child, XPathNamespaceContext ctx, boolean isLast, boolean isDynamic, String defaultNamespacePrefix) { String text = child.getChild(0).getText(); SchemaItem obj = SchemaUtil.findPropertyMapping(parent, text); if ((obj instanceof SchemaElementSimple) || (obj instanceof SchemaElementComplex)){ return makeElementProperty((SchemaElement) obj, child, ctx, isLast, isDynamic, defaultNamespacePrefix); } else if (obj != null) { return makeAttributeProperty((SchemaItemAttribute) obj, child, ctx); } else if (isDynamic) { return makeElementProperty(null, child, ctx, isLast, isDynamic, defaultNamespacePrefix); } else { return null; } } private static Pair<String, QName> makeAttributeProperty(SchemaItemAttribute attribute, Tree child, XPathNamespaceContext ctx) { String prefix = ctx.getPrefix(attribute.getNamespace()); if (prefix == null) prefix = ""; else prefix += ':'; switch (child.getType()) { case EsperEPL2GrammarParser.EVENT_PROP_DYNAMIC_SIMPLE: case EsperEPL2GrammarParser.EVENT_PROP_SIMPLE: QName type = SchemaUtil.simpleTypeToQName(attribute.getXsSimpleType()); String path = "/@" + prefix + child.getChild(0).getText(); return new Pair<String, QName>(path, type); case EsperEPL2GrammarParser.EVENT_PROP_DYNAMIC_MAPPED: case EsperEPL2GrammarParser.EVENT_PROP_MAPPED: throw new RuntimeException("Mapped properties not applicable to attributes"); case EsperEPL2GrammarParser.EVENT_PROP_DYNAMIC_INDEXED: case EsperEPL2GrammarParser.EVENT_PROP_INDEXED: throw new RuntimeException("Mapped properties not applicable to attributes"); default: throw new IllegalStateException("Event property AST node not recognized, type=" + child.getType()); } } private static Pair<String, QName> makeElementProperty(SchemaElement schemaElement, Tree child, XPathNamespaceContext ctx, boolean isAlone, boolean isDynamic, String defaultNamespacePrefix) { QName type; if (isDynamic) { type = XPathConstants.NODE; } else if (schemaElement instanceof SchemaElementSimple) { type = SchemaUtil.simpleTypeToQName(((SchemaElementSimple) schemaElement).getXsSimpleType()); } else { SchemaElementComplex complex = (SchemaElementComplex) schemaElement; if (complex.getOptionalSimpleType() != null) { type = SchemaUtil.simpleTypeToQName(complex.getOptionalSimpleType()); } else { // The result is a node type = XPathConstants.NODE; } } String prefix; if (!isDynamic) { prefix = ctx.getPrefix(schemaElement.getNamespace()); } else { prefix = defaultNamespacePrefix; } if (prefix == null) { prefix = ""; } else { prefix += ':'; } switch (child.getType()) { case EsperEPL2GrammarParser.EVENT_PROP_SIMPLE: case EsperEPL2GrammarParser.EVENT_PROP_DYNAMIC_SIMPLE: if (!isDynamic && schemaElement.isArray() && !isAlone) { throw new PropertyAccessException("Simple property not allowed in repeating elements at '" + schemaElement.getName() + "'"); } return new Pair<String, QName>('/' + prefix + child.getChild(0).getText(), type); case EsperEPL2GrammarParser.EVENT_PROP_MAPPED: case EsperEPL2GrammarParser.EVENT_PROP_DYNAMIC_MAPPED: if (!isDynamic && !schemaElement.isArray()) { throw new PropertyAccessException("Element " + child.getChild(0).getText() + " is not a collection, cannot be used as mapped property"); } String key = StringValue.parseString(child.getChild(1).getText()); return new Pair<String, QName>('/' + prefix + child.getChild(0).getText() + "[@id='" + key + "']", type); case EsperEPL2GrammarParser.EVENT_PROP_INDEXED: case EsperEPL2GrammarParser.EVENT_PROP_DYNAMIC_INDEXED: if (!isDynamic && !schemaElement.isArray()) { throw new PropertyAccessException("Element " + child.getChild(0).getText() + " is not a collection, cannot be used as mapped property"); } int index = IntValue.parseString(child.getChild(1).getText()); int xPathPosition = index + 1; return new Pair<String, QName>('/' + prefix + child.getChild(0).getText() + "[position() = " + xPathPosition + ']', type); default: throw new IllegalStateException("Event property AST node not recognized, type=" + child.getType()); } } private static Log log = LogFactory.getLog(SchemaXMLPropertyParser.class); }