/* * JBoss, Home of Professional Open Source. * * See the LEGAL.txt file distributed with this work for information regarding copyright ownership and licensing. * * See the AUTHORS.txt file distributed with this work for a full listing of individual contributors. */ package org.teiid.designer.extension.definition; import java.io.File; import java.io.InputStream; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.Stack; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import org.eclipse.osgi.util.NLS; import org.teiid.core.designer.util.CoreArgCheck; import org.teiid.core.designer.util.I18nUtil; import org.teiid.designer.extension.ExtensionConstants; import org.teiid.designer.extension.Messages; import org.teiid.designer.extension.properties.ModelExtensionPropertyDefinition; import org.teiid.designer.extension.properties.Translation; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; import org.xml.sax.helpers.DefaultHandler; /** * The <code>ModelExtensionDefinitionParser</code> parses model extension definition input streams. Each input stream is validated * against a model extension definition schema. * * @since 8.0 */ public class ModelExtensionDefinitionParser { /** * <code>true</code> if debug messages should be output when parsing. */ private static final boolean DEBUG_MODE = false; /** * The locale used when no locale is found for translatable text. */ static final Locale LOCALE = Locale.getDefault(); private final File definitionSchemaFile; private Handler handler; /** * The parser of the definition stream. */ private SAXParser parser; /** * Constructs a parser. * * @param medSchema the model extension definition schema file used during validation (cannot be <code>null</code> and must * exist) * @throws IllegalStateException if there were problems with the model extension definition schema file */ public ModelExtensionDefinitionParser( File medSchema ) throws IllegalStateException { try { SAXParserFactory factory = SAXParserFactory.newInstance(); factory.setNamespaceAware(true); factory.setValidating(true); this.parser = factory.newSAXParser(); this.parser.setProperty("http://java.sun.com/xml/jaxp/properties/schemaLanguage", "http://www.w3.org/2001/XMLSchema"); //$NON-NLS-1$ //$NON-NLS-2$ this.definitionSchemaFile = medSchema; if (definitionSchemaFile == null || !definitionSchemaFile.exists()) { throw new IllegalStateException(Messages.definitionSchemaFileNotFoundInFilesystem); } this.parser.setProperty("http://java.sun.com/xml/jaxp/properties/schemaSource", definitionSchemaFile); //$NON-NLS-1$ } catch (Exception e) { IllegalStateException error = null; if (e instanceof IllegalStateException) { error = (IllegalStateException)e; } else { error = new IllegalStateException(e); } throw error; } } /** * @return the error messages output from the last parse operation or <code>null</code> if * {@link #parse(InputStream, ModelExtensionAssistant))} has not been called */ public Collection<String> getErrors() { if (this.handler == null) { return null; } return this.handler.getErrors(); } /** * @return the fatal error messages output from the last parse operation or <code>null</code> if * {@link #parse(InputStream, ModelExtensionAssistant))} has not been called */ public Collection<String> getFatalErrors() { if (this.handler == null) { return null; } return this.handler.getFatalErrors(); } /** * @return the information messages output from the last parse operation or <code>null</code> if * {@link #parse(InputStream, ModelExtensionAssistant))} has not been called */ public Collection<String> getInfos() { if (this.handler == null) { return null; } return this.handler.getInfos(); } /** * @return the warning messages output from the last parse operation or <code>null</code> if * {@link #parse(InputStream, ModelExtensionAssistant))} has not been called */ public Collection<String> getWarnings() { if (this.handler == null) { return null; } return this.handler.getWarnings(); } /** * @param definitionStream the model extension definition input stream (cannot be <code>null</code>) * @param assistant the model extension assistant (cannot be <code>null</code>) * @return the model extension definition (never <code>null</code>) * @throws Exception if the definition file is <code>null</code> or if there is a problem parsing the file */ public ModelExtensionDefinition parse( InputStream definitionStream, ModelExtensionAssistant assistant ) throws Exception { CoreArgCheck.isNotNull(definitionStream, "definitionStream is null"); //$NON-NLS-1$ this.handler = new Handler(assistant); this.parser.parse(definitionStream, handler); return this.handler.getModelExtensionDefinition(); } /** * The handler used by the parser. Each instance should be only used to parse one file. */ class Handler extends DefaultHandler { private final Collection<String> fatals; private final Collection<String> errors; private final Collection<String> infos; private final Collection<String> warnings; private String advanced; private StringBuilder allowedValue = new StringBuilder(); private Set<String> allowedValues = new HashSet<String>(); private final ModelExtensionAssistant assistant; private String defaultValue; private ModelExtensionDefinition definition; private StringBuilder description = new StringBuilder(); private final Stack<String> elements; private String fixedValue; private String id; private String index; private String masked; private String metaclassName; private String metamodelUri; private StringBuilder modelType = new StringBuilder(); private String namespacePrefix; private String namespaceUri; private ModelExtensionPropertyDefinition propDefn; private final Map<String, Collection<ModelExtensionPropertyDefinition>> properties; private String required; private String type; private String version; private Set<Translation> descriptions = new HashSet<Translation>(); private String currentDescriptionLocale; private StringBuilder currentDescriptionText = new StringBuilder(); private Set<Translation> displayNames = new HashSet<Translation>(); private String currentDisplayNameLocale; private StringBuilder currentDisplayNameText = new StringBuilder(); /** * @param assistant the model extension assistant used by the parser (cannot be <code>null</code>) */ public Handler( ModelExtensionAssistant assistant ) { CoreArgCheck.isNotNull(assistant, "assistant is null"); //$NON-NLS-1$ this.assistant = assistant; this.properties = new HashMap<String, Collection<ModelExtensionPropertyDefinition>>(); this.elements = new Stack<String>(); this.fatals = new ArrayList<String>(); this.errors = new ArrayList<String>(); this.infos = new ArrayList<String>(); this.warnings = new ArrayList<String>(); } /** * {@inheritDoc} * * @see org.xml.sax.helpers.DefaultHandler#characters(char[], int, int) */ @Override public void characters( char[] ch, int start, int length ) throws SAXException { String value = new String(ch, start, length); if (ExtensionConstants.Elements.DESCRIPTION.equals(getCurrentElement())) { if (ExtensionConstants.Elements.MODEL_EXTENSION.equals(getPreviousElement())) { // model extension definition is not localized this.description.append(value); } else if (ExtensionConstants.Elements.PROPERTY.equals(getPreviousElement())) { this.currentDescriptionText.append(value); } else { // should not get here assert false : "Unexpected previous tag of " + getPreviousElement() + " while processing the " //$NON-NLS-1$ //$NON-NLS-2$ + ExtensionConstants.Elements.DESCRIPTION + " tag"; //$NON-NLS-1$ } } else if (ExtensionConstants.Elements.DISPLAY.equals(getCurrentElement())) { if (ExtensionConstants.Elements.PROPERTY.equals(getPreviousElement())) { this.currentDisplayNameText.append(value); } else { // should not get here assert false : "Unexpected previous tag of " + getPreviousElement() + " while processing the " //$NON-NLS-1$ //$NON-NLS-2$ + ExtensionConstants.Elements.DISPLAY + " tag"; //$NON-NLS-1$ } } else if (ExtensionConstants.Elements.ALLOWED_VALUE.equals(getCurrentElement())) { this.allowedValue.append(value); } else if (ExtensionConstants.Elements.MODEL_TYPE.equals(getCurrentElement())) { this.modelType.append(value); } else { if (DEBUG_MODE) { System.err.println("characters not processed=" + value); //$NON-NLS-1$ } } super.characters(ch, start, length); } /** * {@inheritDoc} * * @see org.xml.sax.helpers.DefaultHandler#endElement(java.lang.String, java.lang.String, java.lang.String) */ @Override public void endElement( String uri, String localName, String qName ) throws SAXException { if (DEBUG_MODE) { System.err.println("endElement: localName=" + localName + ", qName=" + qName); //$NON-NLS-1$ //$NON-NLS-2$ } if (ExtensionConstants.Elements.DISPLAY.equals(localName)) { if (this.currentDisplayNameLocale != null) { if (DEBUG_MODE) { System.err.println("Adding display name with locale of " + this.currentDisplayNameLocale + " and text of " //$NON-NLS-1$ //$NON-NLS-2$ + this.currentDisplayNameText); } Locale locale = I18nUtil.parseLocaleString(this.currentDisplayNameLocale); this.displayNames.add(new Translation(locale, this.currentDisplayNameText.toString())); } this.currentDisplayNameLocale = null; this.currentDisplayNameText.setLength(0); } else if (ExtensionConstants.Elements.DESCRIPTION.equals(localName)) { if (this.currentDescriptionLocale != null) { if (DEBUG_MODE) { System.err.println("Adding description with locale of " + this.currentDescriptionLocale + " and text of " //$NON-NLS-1$ //$NON-NLS-2$ + this.currentDescriptionText); } Locale locale = I18nUtil.parseLocaleString(this.currentDescriptionLocale); this.descriptions.add(new Translation(locale, this.currentDescriptionText.toString())); } this.currentDescriptionLocale = null; this.currentDescriptionText.setLength(0); } else if (ExtensionConstants.Elements.PROPERTY.equals(localName)) { savePropertyDefinition(); if (DEBUG_MODE) { System.err.println("saved property: id=" + this.id + ", type=" + this.type + ", required=" + this.required //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + ", defaultValue=" + this.defaultValue + ", fixedValue=" + this.fixedValue + ", advanced=" //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + this.advanced + ", masked=" + this.masked + ", index=" + this.index); //$NON-NLS-1$ //$NON-NLS-2$ } // reset all property definition related fields this.id = null; this.type = null; this.required = null; this.defaultValue = null; this.fixedValue = null; this.advanced = null; this.masked = null; this.index = null; this.allowedValue.setLength(0); this.allowedValues.clear(); this.propDefn = null; this.descriptions.clear(); this.displayNames.clear(); this.currentDescriptionLocale = null; this.currentDescriptionText.setLength(0); this.currentDisplayNameLocale = null; this.currentDisplayNameText.setLength(0); } else if (ExtensionConstants.Elements.EXTENDED_METACLASS.equals(localName)) { if (this.metaclassName != null && !this.metaclassName.isEmpty()) { this.definition.addMetaclass(this.metaclassName); } if (DEBUG_MODE) { System.err.println("reset: metaclassName=" + this.metaclassName); //$NON-NLS-1$ } this.metaclassName = null; } else if (ExtensionConstants.Elements.ALLOWED_VALUE.equals(localName)) { this.allowedValues.add(this.allowedValue.toString()); this.allowedValue.setLength(0); if (DEBUG_MODE) { String DELIM = ", "; //$NON-NLS-1$ StringBuilder valuesString = new StringBuilder(); for (String value : this.allowedValues) { valuesString.append(value).append(DELIM); } System.err.println("allowedValues=" + valuesString.subSequence(0, valuesString.length() - DELIM.length())); //$NON-NLS-1$ } } else if (ExtensionConstants.Elements.MODEL_EXTENSION.equals(localName)) { if (this.definition != null) this.definition.setDescription(this.description.toString()); saveModelExtensionDefinitionProperties(); if (DEBUG_MODE) { System.err.println("reset: namespacePrefix=" + this.namespacePrefix + ", namespaceUri=" + this.namespaceUri //$NON-NLS-1$ //$NON-NLS-2$ + ", metamodelUri=" + this.metamodelUri + ", version=" + this.version + ", description=" //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + this.description); } } else if (ExtensionConstants.Elements.MODEL_TYPE.equals(localName)) { this.assistant.addSupportedModelType(this.modelType.toString()); this.modelType.setLength(0); } if (DEBUG_MODE) { System.err.println("endElement: currentElement=" + getCurrentElement() + ", previousElement=" //$NON-NLS-1$ //$NON-NLS-2$ + getPreviousElement()); } this.elements.pop(); super.endElement(uri, localName, qName); } /** * {@inheritDoc} * * @see org.xml.sax.helpers.DefaultHandler#error(org.xml.sax.SAXParseException) */ @Override public void error( SAXParseException e ) { this.errors.add(e.getLocalizedMessage()); } /** * {@inheritDoc} * * @see org.xml.sax.helpers.DefaultHandler#fatalError(org.xml.sax.SAXParseException) */ @Override public void fatalError( SAXParseException e ) { this.fatals.add(e.getLocalizedMessage()); } /** * @return the element currently being parsed */ private String getCurrentElement() { if (this.elements.empty()) { return null; } return this.elements.peek(); } /** * @return the error messages output from the last parse operation (never <code>null</code> but can be empty) */ Collection<String> getErrors() { return this.errors; } /** * @return the fatal error messages output from the last parse operation (never <code>null</code> but can be empty) */ Collection<String> getFatalErrors() { return this.fatals; } /** * @return the information messages output from the last parse operation (never <code>null</code> but can be empty) */ Collection<String> getInfos() { return this.infos; } ModelExtensionDefinition getModelExtensionDefinition() { return this.definition; } /** * @return the previously found element or <code>null</code> */ private String getPreviousElement() { if (this.elements.size() > 1) { return this.elements.get(this.elements.size() - 2); } return null; } /** * @return the warning messages output from the last parse operation (never <code>null</code> but can be empty) */ Collection<String> getWarnings() { return this.warnings; } /** * Saves the model extension definition that was just parsed. */ private void saveModelExtensionDefinitionProperties() { for (Map.Entry<String, Collection<ModelExtensionPropertyDefinition>> entry : this.properties.entrySet()) { String extendedMetaclassName = entry.getKey(); for (ModelExtensionPropertyDefinition propDefn : entry.getValue()) { this.assistant.addPropertyDefinition(extendedMetaclassName, propDefn); } } } /** * Saves the property definition that was just parsed. */ private void savePropertyDefinition() { this.propDefn = this.assistant.createPropertyDefinition(this.id, this.type, this.required, this.defaultValue, this.fixedValue, this.advanced, this.masked, this.index, this.allowedValues, this.descriptions, this.displayNames); // add all the property definition assert this.metaclassName != null : "metaclassName is null"; //$NON-NLS-1$ Collection<ModelExtensionPropertyDefinition> props = this.properties.get(this.metaclassName); if (props == null) { props = new ArrayList<ModelExtensionPropertyDefinition>(); this.properties.put(this.metaclassName, props); } props.add(this.propDefn); } /** * {@inheritDoc} * * @see org.xml.sax.helpers.DefaultHandler#skippedEntity(java.lang.String) */ @Override public void skippedEntity( String name ) { this.infos.add(NLS.bind(Messages.parserFoundUnparsedEntityDeclaration, name)); } /** * {@inheritDoc} * * @see org.xml.sax.helpers.DefaultHandler#startElement(java.lang.String, java.lang.String, java.lang.String, * org.xml.sax.Attributes) */ @Override public void startElement( String uri, String localName, String qName, Attributes attributes ) throws SAXException { if (DEBUG_MODE) { System.err.println("startElement: uri" + uri + ", localName=" + localName + ", qName=" + qName); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } this.elements.push(localName); if (ExtensionConstants.Elements.MODEL_EXTENSION.equals(getCurrentElement())) { this.namespacePrefix = attributes.getValue(ExtensionConstants.Attributes.NAMESPACE_PREFIX); this.namespaceUri = attributes.getValue(ExtensionConstants.Attributes.NAMESPACE_URI); this.metamodelUri = attributes.getValue(ExtensionConstants.Attributes.METAMODEL_URI); this.version = attributes.getValue(ExtensionConstants.Attributes.VERSION); this.definition = this.assistant.createModelExtensionDefinition(this.namespacePrefix, this.namespaceUri, this.metamodelUri, null, this.description.toString(), this.version); } else if (ExtensionConstants.Elements.EXTENDED_METACLASS.equals(getCurrentElement())) { this.metaclassName = attributes.getValue(ExtensionConstants.Attributes.NAME); } else if (ExtensionConstants.Elements.PROPERTY.equals(getCurrentElement())) { this.id = attributes.getValue(ExtensionConstants.Attributes.NAME); this.type = attributes.getValue(ExtensionConstants.Attributes.TYPE); this.required = attributes.getValue(ExtensionConstants.Attributes.REQUIRED); // optional attributes this.defaultValue = attributes.getValue(ExtensionConstants.Attributes.DEFAULT_VALUE); this.fixedValue = attributes.getValue(ExtensionConstants.Attributes.FIXED_VALUE); this.advanced = attributes.getValue(ExtensionConstants.Attributes.ADVANCED); this.masked = attributes.getValue(ExtensionConstants.Attributes.MASKED); this.index = attributes.getValue(ExtensionConstants.Attributes.INDEX); } else if (ExtensionConstants.Elements.DISPLAY.equals(getCurrentElement())) { this.currentDisplayNameLocale = attributes.getValue(ExtensionConstants.Attributes.LOCALE); } else if (ExtensionConstants.Elements.DESCRIPTION.equals(getCurrentElement()) && ExtensionConstants.Elements.PROPERTY.equals(getPreviousElement())) { this.currentDescriptionLocale = attributes.getValue(ExtensionConstants.Attributes.LOCALE); } else { if (DEBUG_MODE) { System.err.println("\n\nstartElement not being process: currentElement=" + getCurrentElement() //$NON-NLS-1$ + ", previousElement=" + getPreviousElement()); //$NON-NLS-1$ } } super.startElement(uri, localName, qName, attributes); } /** * {@inheritDoc} * * @see org.xml.sax.helpers.DefaultHandler#warning(org.xml.sax.SAXParseException) */ @Override public void warning( SAXParseException e ) { this.warnings.add(e.getLocalizedMessage()); } /** * {@inheritDoc} * * @see org.xml.sax.helpers.DefaultHandler#unparsedEntityDecl(java.lang.String, java.lang.String, java.lang.String, * java.lang.String) */ @Override public void unparsedEntityDecl( String name, String publicId, String systemId, String notationName ) { this.infos.add(NLS.bind(Messages.parserFoundUnparsedEntityDeclaration, name)); } } }