diff options
author | Jon Bratseth <bratseth@yahoo-inc.com> | 2016-06-15 23:09:44 +0200 |
---|---|---|
committer | Jon Bratseth <bratseth@yahoo-inc.com> | 2016-06-15 23:09:44 +0200 |
commit | 72231250ed81e10d66bfe70701e64fa5fe50f712 (patch) | |
tree | 2728bba1131a6f6e5bdf95afec7d7ff9358dac50 /config-model/src/main/java/com/yahoo/config/model/builder/xml |
Publish
Diffstat (limited to 'config-model/src/main/java/com/yahoo/config/model/builder/xml')
4 files changed, 353 insertions, 0 deletions
diff --git a/config-model/src/main/java/com/yahoo/config/model/builder/xml/ConfigModelBuilder.java b/config-model/src/main/java/com/yahoo/config/model/builder/xml/ConfigModelBuilder.java new file mode 100644 index 00000000000..a0b1be20df6 --- /dev/null +++ b/config-model/src/main/java/com/yahoo/config/model/builder/xml/ConfigModelBuilder.java @@ -0,0 +1,119 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.config.model.builder.xml; + +import com.yahoo.component.AbstractComponent; +import com.yahoo.config.model.ConfigModel; +import com.yahoo.config.model.ConfigModelContext; +import com.yahoo.config.model.ConfigModelInstanceFactory; +import com.yahoo.config.model.ConfigModelRepo; +import com.yahoo.config.model.api.ConfigModelPlugin; +import com.yahoo.config.model.deploy.DeployState; +import com.yahoo.config.model.producer.AbstractConfigProducer; +import org.w3c.dom.Element; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.List; + +/** + * Builds a config model using DOM parsers + * + * @author vegardh + * @since 5.1.10 + */ +public abstract class ConfigModelBuilder<MODEL extends ConfigModel> extends AbstractComponent implements ConfigModelPlugin { + + private Class<MODEL> configModelClass; + + public ConfigModelBuilder(Class<MODEL> configModelClass) { + this.configModelClass = configModelClass; + } + + /** + * Method that must return the XML elements this builder handles. Subclasses must implement this in order to + * get called when one of the elements have been encountered when parsing. + * + * @return A list of elements that this builder handles. + */ + public abstract List<ConfigModelId> handlesElements(); + + /** + * Convenience hook called from {@link #build}. Implement this method to build a config model. + * + * @param spec The XML element that this builder should handle. + * @param modelContext A model context that contains the application package and other data needed by the + * config model constructor. + */ + public abstract void doBuild(MODEL model, Element spec, ConfigModelContext modelContext); + + /** + * Builds an instance of this component model. + * This calls instantiate(...), instance.setUp(...), doBuild(instance, ...). + * + * @param deployState a global deployment state used for this model. + * @param parent the root config producer this should be added to + * @param spec the XML element this is constructed from + */ + public final MODEL build(DeployState deployState, ConfigModelRepo configModelRepo, AbstractConfigProducer parent, Element spec) { + ConfigModelContext context = ConfigModelContext.create(deployState, configModelRepo, parent, getIdString(spec)); + return build(new DefaultModelInstanceFactory(), spec, context); + } + + /** + * Builds an instance of this component model. + * This calls instantiate(...), instance.setUp(...), doBuild(instance, ...). + * + * @param factory A factory capable of creating models. + * @param spec the XML element this is constructed from + * @param context A context object containing various data used by builders. + */ + public MODEL build(ConfigModelInstanceFactory<MODEL> factory, Element spec, ConfigModelContext context) { + MODEL model = factory.createModel(context); + doBuild(model, spec, context); + return model; + } + + public Class<MODEL> getModelClass() { + return configModelClass; + } + + private static String getIdString(Element spec) { + String idString = XmlHelper.getIdString(spec); + if (idString == null || idString.isEmpty()) { + idString = spec.getTagName(); + } + return idString; + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof ConfigModelBuilder)) { + return false; + } + ConfigModelBuilder otherBuilder = (ConfigModelBuilder) other; + List<ConfigModelId> thisIds = this.handlesElements(); + List<ConfigModelId> otherIds = otherBuilder.handlesElements(); + if (thisIds.size() != otherIds.size()) { + return false; + } + for (int i = 0; i < thisIds.size(); i++) { + if (!thisIds.get(i).equals(otherIds.get(i))) { + return false; + } + } + return true; + } + + + private class DefaultModelInstanceFactory implements ConfigModelInstanceFactory<MODEL> { + @Override + public MODEL createModel(ConfigModelContext context) { + try { + Constructor<MODEL> constructor = configModelClass.getConstructor(ConfigModelContext.class); + return constructor.newInstance(context); + } catch (NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException e) { + throw new RuntimeException("Error constructing model '" + configModelClass.getName() + "'", e); + } + } + } +} diff --git a/config-model/src/main/java/com/yahoo/config/model/builder/xml/ConfigModelId.java b/config-model/src/main/java/com/yahoo/config/model/builder/xml/ConfigModelId.java new file mode 100644 index 00000000000..fca114757ec --- /dev/null +++ b/config-model/src/main/java/com/yahoo/config/model/builder/xml/ConfigModelId.java @@ -0,0 +1,94 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.config.model.builder.xml; + +import com.yahoo.component.Version; + +/** + * A {@link ConfigModelId} describes an element handled by a {@link ConfigModelBuilder}. + * + * @author lulf + * @since 5.1 + */ +public class ConfigModelId implements Comparable<ConfigModelId> { + + private final String name; + private final Version version; + private final String stringValue; + + private ConfigModelId(String name, Version version) { + this.name = name; + this.version = version; + this.stringValue = toStringValue(); + } + + /** + * Create id with a name and version + * @param tagName Name of the id + * @param tagVersion Version of the id + * @return A ConfigModelId instance + */ + public static ConfigModelId fromNameAndVersion(String tagName, String tagVersion) { + return new ConfigModelId(tagName, Version.fromString(tagVersion)); + } + + /** + * Create id with given name, using default version 1. + * + * @param tagName Name of the id + * @return A ConfigModelId instance + */ + public static ConfigModelId fromName(String tagName) { + return new ConfigModelId(tagName, new Version(1)); + } + + @Override + public boolean equals(Object object) { + if (!(object instanceof ConfigModelId)) return false; + ConfigModelId other = (ConfigModelId)object; + return this.name.equals(other.name) && this.version.equals(other.version); + } + + @Override + public int compareTo(ConfigModelId other) { + if (other == this) return 0; + int cmp = this.name.compareTo(other.name); + if (cmp == 0) { + cmp = this.version.compareTo(other.version); + } + return cmp; + } + + @Override + public String toString() { + return stringValue; + } + + @Override + public int hashCode() { + return stringValue.hashCode(); + } + + /** + * Return the XML element name. + * @return the name of the config model + */ + public String getName() { + return name; + } + + /** + * Return the XML element version. + * @return the version of the config model + */ + Version getVersion() { + return version; + } + + private String toStringValue() { + StringBuilder sb = new StringBuilder(); + sb.append(name); + sb.append("."); + sb.append(version); + return sb.toString(); + } +} diff --git a/config-model/src/main/java/com/yahoo/config/model/builder/xml/XmlHelper.java b/config-model/src/main/java/com/yahoo/config/model/builder/xml/XmlHelper.java new file mode 100644 index 00000000000..924d888b0d0 --- /dev/null +++ b/config-model/src/main/java/com/yahoo/config/model/builder/xml/XmlHelper.java @@ -0,0 +1,135 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.config.model.builder.xml; + +import com.yahoo.component.ComponentId; +import com.yahoo.component.ComponentSpecification; +import com.yahoo.log.LogLevel; +import com.yahoo.text.XML; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import java.io.IOException; +import java.io.Reader; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import java.util.logging.Logger; + + +/** + * Static methods for helping dom building + * + * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a> + */ +public final class XmlHelper { + private static final Logger log = Logger.getLogger(XmlHelper.class.getPackage().toString()); + + + private static final String idReference = "idref"; + // Access to this needs to be synchronized (as it is in getDocumentBuilder() below) + public static final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + + static { + XmlHelper.factory.setNamespaceAware(true); + // if use jdom and jaxen this will fail badly: + XmlHelper.factory.setXIncludeAware(true); + } + + private XmlHelper() {} + + public static String nullIfEmpty(String attribute) { + if (attribute.isEmpty()) + return null; + else + return attribute; + } + + /** + * For searchers inside search chains, the id may be both a reference and an id at once, or just a reference. + * In other cases, it is clear which one it is from context, so I think the difference is not worth bothering users + * with, unless they are XML purists in which case they will have the option of writing this correctly. + * - Jon + */ + public static String getIdString(Element element) { + String idString = element.getAttribute("id"); + if (idString == null || idString.trim().equals("")) + idString = element.getAttribute(idReference); + if (idString == null || idString.trim().equals("")) + idString = element.getAttribute("ident"); + return idString; + } + + public static ComponentId getId(Element element) { + return new ComponentId(getIdString(element)); + } + + public static ComponentSpecification getIdRef(Element element) { + return new ComponentSpecification(getIdString(element)); + } + + public static Document getDocument(Reader reader) { + Document doc; + try { + doc = getDocumentBuilder().parse(new InputSource(reader)); + } catch (SAXException | IOException e) { + throw new IllegalArgumentException(e); + } + return doc; + } + + public static List<String> splitAndDiscardEmpty(String field, String regex) { + List<String> ret = new ArrayList<>(); + for (String t : field.split(regex)) { + if (!t.isEmpty()) { + ret.add(t); + } + } + return ret; + } + + public static List<String> spaceSeparatedSymbols(String field) { + return splitAndDiscardEmpty(field, " "); + } + + public static Collection<String> spaceSeparatedSymbolsFromAttribute(Element spec, String name) { + return spaceSeparatedSymbols(spec.getAttribute(name)); + } + + public static Collection<String> valuesFromElements(Element parent, String elementName) { + List<String> symbols = new ArrayList<>(); + for (Element symbol : XML.getChildren(parent, elementName)) { + symbols.add(XML.getValue(symbol).trim()); + } + return symbols; + } + + public static boolean isReference(Element element) { + return element.hasAttribute(idReference); + } + + /** + * Creates a new XML document builder. + * + * @return A new DocumentBuilder instance, or null if we fail to get one. + */ + public static synchronized DocumentBuilder getDocumentBuilder() { + try { + DocumentBuilder docBuilder = factory.newDocumentBuilder(); + log.log(LogLevel.DEBUG, "XML parser now operational!"); + return docBuilder; + } catch (ParserConfigurationException e) { + log.log(LogLevel.WARNING, "No XML parser available - " + e); + return null; + } + } + + public static Optional<String> getOptionalAttribute(Element element, String name) { + return Optional.ofNullable(element.getAttribute(name)).filter(s -> !s.isEmpty()); + } +} diff --git a/config-model/src/main/java/com/yahoo/config/model/builder/xml/package-info.java b/config-model/src/main/java/com/yahoo/config/model/builder/xml/package-info.java new file mode 100644 index 00000000000..6ce400dc922 --- /dev/null +++ b/config-model/src/main/java/com/yahoo/config/model/builder/xml/package-info.java @@ -0,0 +1,5 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +@ExportPackage +package com.yahoo.config.model.builder.xml; + +import com.yahoo.osgi.annotation.ExportPackage; |