/************************************************************************************** * 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.*; import com.espertech.esper.epl.parse.ASTFilterSpecHelper; import com.espertech.esper.event.EventAdapterService; import com.espertech.esper.event.EventTypeMetadata; import com.espertech.esper.event.ExplicitPropertyDescriptor; import com.espertech.esper.event.property.IndexedProperty; import com.espertech.esper.event.property.Property; import com.espertech.esper.event.property.PropertyParser; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * EventType for xml events that have a Schema. * Mapped and Indexed properties are supported. * All property types resolved via the declared xsd types. * Can access attributes. * Validates the property string at construction time. * @author pablo * */ public class SchemaXMLEventType extends BaseXMLEventType { private static final Log log = LogFactory.getLog(SchemaXMLEventType.class); private final SchemaModel schemaModel; private final SchemaElementComplex schemaModelRoot; private final String rootElementNamespace; private final Map<String, EventPropertyGetter> propertyGetterCache; private final boolean isPropertyExpressionXPath; /** * Ctor. * @param config - configuration for type * @param eventTypeMetadata - event type metadata * @param schemaModel - the schema representation * @param eventAdapterService - type lookup and registration */ public SchemaXMLEventType(EventTypeMetadata eventTypeMetadata, int eventTypeId, ConfigurationEventTypeXMLDOM config, SchemaModel schemaModel, EventAdapterService eventAdapterService) { super(eventTypeMetadata, eventTypeId, config, eventAdapterService); this.propertyGetterCache = new HashMap<String, EventPropertyGetter>(); this.schemaModel = schemaModel; this.rootElementNamespace = config.getRootElementNamespace(); this.schemaModelRoot = SchemaUtil.findRootElement(schemaModel, rootElementNamespace, this.getRootElementName()); this.isPropertyExpressionXPath = config.isXPathPropertyExpr(); // Set of namespace context for XPath expressions XPathNamespaceContext ctx = new XPathNamespaceContext(); if (config.getDefaultNamespace() != null) { ctx.setDefaultNamespace(config.getDefaultNamespace()); } for (Map.Entry<String, String> entry : config.getNamespacePrefixes().entrySet()) { ctx.addPrefix(entry.getKey(), entry.getValue()); } super.setNamespaceContext(ctx); // add properties for the root element List<ExplicitPropertyDescriptor> additionalSchemaProps = new ArrayList<ExplicitPropertyDescriptor>(); // Add a property for each complex child element for (SchemaElementComplex complex : schemaModelRoot.getChildren()) { String propertyName = complex.getName(); Class returnType = Node.class; if (complex.getOptionalSimpleType() != null) { returnType = SchemaUtil.toReturnType(complex); } if (complex.isArray()) { returnType = Node[].class; // We use Node[] for arrays and NodeList for XPath-Expressions returning Nodeset } boolean isFragment = false; if (this.getConfigurationEventTypeXMLDOM().isAutoFragment() && (!this.getConfigurationEventTypeXMLDOM().isXPathPropertyExpr())) { isFragment = canFragment(complex); } EventPropertyGetter getter = doResolvePropertyGetter(propertyName, true); EventPropertyDescriptor desc = new EventPropertyDescriptor(propertyName, returnType, null, false, false, complex.isArray(), false, isFragment); ExplicitPropertyDescriptor explicit = new ExplicitPropertyDescriptor(desc, getter, false, null); additionalSchemaProps.add(explicit); } // Add a property for each simple child element for (SchemaElementSimple simple : schemaModelRoot.getSimpleElements()) { String propertyName = simple.getName(); Class returnType = SchemaUtil.toReturnType(simple); EventPropertyGetter getter = doResolvePropertyGetter(propertyName, true); EventPropertyDescriptor desc = new EventPropertyDescriptor(propertyName, returnType, null, false, false, simple.isArray(), false, false); ExplicitPropertyDescriptor explicit = new ExplicitPropertyDescriptor(desc, getter, false, null); additionalSchemaProps.add(explicit); } // Add a property for each attribute for (SchemaItemAttribute attribute : schemaModelRoot.getAttributes()) { String propertyName = attribute.getName(); Class returnType = SchemaUtil.toReturnType(attribute); EventPropertyGetter getter = doResolvePropertyGetter(propertyName, true); EventPropertyDescriptor desc = new EventPropertyDescriptor(propertyName, returnType, null, false, false, false, false, false); ExplicitPropertyDescriptor explicit = new ExplicitPropertyDescriptor(desc, getter, false, null); additionalSchemaProps.add(explicit); } // Finally add XPath properties as that may depend on the rootElementNamespace super.initialize(config.getXPathProperties().values(), additionalSchemaProps); } public SchemaModel getSchemaModel() { return schemaModel; } protected FragmentEventType doResolveFragmentType(String property) { if ((!this.getConfigurationEventTypeXMLDOM().isAutoFragment()) || (this.getConfigurationEventTypeXMLDOM().isXPathPropertyExpr())) { return null; } Property prop = PropertyParser.parse(property, false); SchemaItem item = prop.getPropertyTypeSchema(schemaModelRoot, this.getEventAdapterService()); if ((item == null) || (!canFragment(item))) { return null; } SchemaElementComplex complex = (SchemaElementComplex) item; // build name of event type String[] atomicProps = prop.toPropertyArray(); String delimiterDot = "."; StringBuilder eventTypeNameBuilder = new StringBuilder(this.getName()); for (String atomic : atomicProps) { eventTypeNameBuilder.append(delimiterDot); eventTypeNameBuilder.append(atomic); } String eventTypeName = eventTypeNameBuilder.toString(); // check if the type exists, use the existing type if found EventType existingType = this.getEventAdapterService().getExistsTypeByName(eventTypeName); if (existingType != null) { return new FragmentEventType(existingType, complex.isArray(), false); } // add a new type ConfigurationEventTypeXMLDOM xmlDom = new ConfigurationEventTypeXMLDOM(); xmlDom.setRootElementName("//" + complex.getName()); // such the reload of the type can resolve it xmlDom.setRootElementNamespace(complex.getNamespace()); xmlDom.setAutoFragment(this.getConfigurationEventTypeXMLDOM().isAutoFragment()); xmlDom.setEventSenderValidatesRoot(this.getConfigurationEventTypeXMLDOM().isEventSenderValidatesRoot()); xmlDom.setXPathPropertyExpr(this.getConfigurationEventTypeXMLDOM().isXPathPropertyExpr()); xmlDom.setXPathResolvePropertiesAbsolute(this.getConfigurationEventTypeXMLDOM().isXPathResolvePropertiesAbsolute()); xmlDom.setSchemaResource(this.getConfigurationEventTypeXMLDOM().getSchemaResource()); xmlDom.setSchemaText(this.getConfigurationEventTypeXMLDOM().getSchemaText()); xmlDom.setXPathFunctionResolver(this.getConfigurationEventTypeXMLDOM().getXPathFunctionResolver()); xmlDom.setXPathVariableResolver(this.getConfigurationEventTypeXMLDOM().getXPathVariableResolver()); xmlDom.setDefaultNamespace(this.getConfigurationEventTypeXMLDOM().getDefaultNamespace()); xmlDom.addNamespacePrefixes(this.getConfigurationEventTypeXMLDOM().getNamespacePrefixes()); EventType newType; try { newType = this.getEventAdapterService().addXMLDOMType(eventTypeName, xmlDom, schemaModel, true); } catch (Exception ex) { log.error("Failed to add dynamic event type for fragment of XML schema for property '" + property + "' :" + ex.getMessage(), ex); return null; } return new FragmentEventType(newType, complex.isArray(), false); } protected Class doResolvePropertyType(String propertyExpression) { return doResolvePropertyType(propertyExpression, false); } private Class doResolvePropertyType(String propertyExpression, boolean allowSimpleProperties) { // see if this is an indexed property int index = ASTFilterSpecHelper.unescapedIndexOfDot(propertyExpression); if ((!allowSimpleProperties) && (index == -1)) { // parse, can be an indexed property Property property = PropertyParser.parse(propertyExpression, false); if (!property.isDynamic()) { if (!(property instanceof IndexedProperty)) { return null; } IndexedProperty indexedProp = (IndexedProperty) property; EventPropertyDescriptor descriptor = propertyDescriptorMap.get(indexedProp.getPropertyNameAtomic()); if (descriptor == null) { return null; } return descriptor.getPropertyType(); } } Property prop = PropertyParser.parse(propertyExpression, false); if (prop.isDynamic()) { return Node.class; } SchemaItem item = prop.getPropertyTypeSchema(schemaModelRoot, this.getEventAdapterService()); if (item == null) { return null; } return SchemaUtil.toReturnType(item); } protected EventPropertyGetter doResolvePropertyGetter(String property) { return doResolvePropertyGetter(property, false); } private EventPropertyGetter doResolvePropertyGetter(String propertyExpression, boolean allowSimpleProperties) { EventPropertyGetter getter = propertyGetterCache.get(propertyExpression); if (getter != null) { return getter; } if (!allowSimpleProperties) { // see if this is an indexed property int index = ASTFilterSpecHelper.unescapedIndexOfDot(propertyExpression); if (index == -1) { // parse, can be an indexed property Property property = PropertyParser.parse(propertyExpression, false); if (!property.isDynamic()) { if (!(property instanceof IndexedProperty)) { return null; } IndexedProperty indexedProp = (IndexedProperty) property; getter = this.propertyGetters.get(indexedProp.getPropertyNameAtomic()); if (null == getter) { return null; } EventPropertyDescriptor descriptor = this.propertyDescriptorMap.get(indexedProp.getPropertyNameAtomic()); if (descriptor == null) { return null; } if (!descriptor.isIndexed()) { return null; } if (descriptor.getPropertyType() == NodeList.class) { FragmentFactory fragmentFactory = new FragmentFactoryDOMGetter(this.getEventAdapterService(), this, indexedProp.getPropertyNameAtomic()); return new XPathPropertyArrayItemGetter(getter, indexedProp.getIndex(), fragmentFactory); } } } } if (!isPropertyExpressionXPath) { Property prop = PropertyParser.parse(propertyExpression, false); boolean isDynamic = prop.isDynamic(); if (!isDynamic) { SchemaItem item = prop.getPropertyTypeSchema(schemaModelRoot, this.getEventAdapterService()); if (item == null) { return null; } getter = prop.getGetterDOM(schemaModelRoot, this.getEventAdapterService(), this, propertyExpression); if (getter == null) { return null; } Class returnType = SchemaUtil.toReturnType(item); if ((returnType != Node.class) && (returnType != NodeList.class)) { if (!returnType.isArray()) { getter = new DOMConvertingGetter(propertyExpression, (DOMPropertyGetter) getter, returnType); } else { getter = new DOMConvertingArrayGetter((DOMPropertyGetter) getter, returnType.getComponentType()); } } } else { return prop.getGetterDOM(); } } else { boolean allowFragments = !this.getConfigurationEventTypeXMLDOM().isXPathPropertyExpr(); getter = SchemaXMLPropertyParser.getXPathResolution(propertyExpression,getXPathFactory(),getRootElementName(),rootElementNamespace, schemaModel, this.getEventAdapterService(), this, allowFragments, this.getConfigurationEventTypeXMLDOM().getDefaultNamespace()); } propertyGetterCache.put(propertyExpression, getter); return getter; } private boolean canFragment(SchemaItem item) { if (!(item instanceof SchemaElementComplex)) { return false; } SchemaElementComplex complex = (SchemaElementComplex) item; if (complex.getOptionalSimpleType() != null) { return false; // no transposing if the complex type also has a simple value else that is hidden } return true; } }