DataStoreAppConfigDefaultXMLReaderTest illustrates usage.
Change-Id: I342fca4583c90802238e63262871e33b4b713438
Signed-off-by: Michael Vorburger <vorburger@redhat.com>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>com.google.truth</groupId>
+ <artifactId>truth</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>sal-test-model</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>sal-binding-broker-impl</artifactId>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>sal-binding-broker-impl</artifactId>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<build>
--- /dev/null
+/*
+ * Copyright (c) 2016 Brocade Communications Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.blueprint.ext;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Collections;
+import java.util.List;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.Identifiable;
+import org.opendaylight.yangtools.yang.binding.Identifier;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.binding.util.BindingReflections;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
+import org.opendaylight.yangtools.yang.data.impl.schema.transform.dom.parser.DomToNormalizedNodeParserFactory;
+import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
+import org.osgi.service.blueprint.container.ComponentDefinitionException;
+import org.w3c.dom.Element;
+
+/**
+ * Base class to abstract binding type-specific behavior.
+ *
+ * @author Thomas Pantelis (originally; re-factored by Michael Vorburger.ch)
+ */
+public abstract class BindingContext {
+
+ public static BindingContext create(String logName, Class<? extends DataObject> klass,
+ String appConfigListKeyValue) {
+ if (Identifiable.class.isAssignableFrom(klass)) {
+ // The binding class corresponds to a yang list.
+ if (Strings.isNullOrEmpty(appConfigListKeyValue)) {
+ throw new ComponentDefinitionException(String.format(
+ "%s: App config binding class %s represents a yang list therefore \"%s\" must be specified",
+ logName, klass.getName(), DataStoreAppConfigMetadata.LIST_KEY_VALUE));
+ }
+
+ try {
+ return ListBindingContext.newInstance(klass, appConfigListKeyValue);
+ } catch (InstantiationException | IllegalAccessException | IllegalArgumentException
+ | InvocationTargetException | NoSuchMethodException | SecurityException e) {
+ throw new ComponentDefinitionException(String.format(
+ "%s: Error initializing for app config list binding class %s",
+ logName, klass.getName()), e);
+ }
+
+ } else {
+ return new ContainerBindingContext(klass);
+ }
+ }
+
+ public final InstanceIdentifier<DataObject> appConfigPath;
+ public final Class<DataObject> appConfigBindingClass;
+ public final Class<? extends DataSchemaNode> schemaType;
+ public final QName bindingQName;
+
+ private BindingContext(final Class<DataObject> appConfigBindingClass,
+ final InstanceIdentifier<DataObject> appConfigPath, final Class<? extends DataSchemaNode> schemaType) {
+ this.appConfigBindingClass = appConfigBindingClass;
+ this.appConfigPath = appConfigPath;
+ this.schemaType = schemaType;
+
+ bindingQName = BindingReflections.findQName(appConfigBindingClass);
+ }
+
+ public abstract NormalizedNode<?, ?> parseDataElement(Element element, DataSchemaNode dataSchema,
+ DomToNormalizedNodeParserFactory parserFactory);
+
+ public abstract NormalizedNode<?, ?> newDefaultNode(DataSchemaNode dataSchema);
+
+ /**
+ * BindingContext implementation for a container binding.
+ */
+ private static class ContainerBindingContext extends BindingContext {
+ @SuppressWarnings("unchecked")
+ ContainerBindingContext(final Class<? extends DataObject> appConfigBindingClass) {
+ super((Class<DataObject>) appConfigBindingClass,
+ InstanceIdentifier.create((Class<DataObject>) appConfigBindingClass), ContainerSchemaNode.class);
+ }
+
+ @Override
+ public NormalizedNode<?, ?> newDefaultNode(final DataSchemaNode dataSchema) {
+ return ImmutableNodes.containerNode(bindingQName);
+ }
+
+ @Override
+ public NormalizedNode<?, ?> parseDataElement(final Element element, final DataSchemaNode dataSchema,
+ final DomToNormalizedNodeParserFactory parserFactory) {
+ return parserFactory.getContainerNodeParser().parse(Collections.singletonList(element),
+ (ContainerSchemaNode)dataSchema);
+ }
+ }
+
+ /**
+ * BindingContext implementation for a list binding.
+ */
+ private static class ListBindingContext extends BindingContext {
+ final String appConfigListKeyValue;
+
+ @SuppressWarnings("unchecked")
+ ListBindingContext(final Class<? extends DataObject> appConfigBindingClass,
+ final InstanceIdentifier<? extends DataObject> appConfigPath, final String appConfigListKeyValue) {
+ super((Class<DataObject>) appConfigBindingClass, (InstanceIdentifier<DataObject>) appConfigPath,
+ ListSchemaNode.class);
+ this.appConfigListKeyValue = appConfigListKeyValue;
+ }
+
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ private static ListBindingContext newInstance(final Class<? extends DataObject> bindingClass,
+ final String listKeyValue) throws InstantiationException, IllegalAccessException,
+ IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException {
+ // We assume the yang list key type is string.
+ Identifier keyInstance = (Identifier) bindingClass.getMethod("getKey").getReturnType()
+ .getConstructor(String.class).newInstance(listKeyValue);
+ InstanceIdentifier appConfigPath = InstanceIdentifier.builder((Class)bindingClass, keyInstance).build();
+ return new ListBindingContext(bindingClass, appConfigPath, listKeyValue);
+ }
+
+ @Override
+ public NormalizedNode<?, ?> newDefaultNode(final DataSchemaNode dataSchema) {
+ // We assume there's only one key for the list.
+ List<QName> keys = ((ListSchemaNode)dataSchema).getKeyDefinition();
+ Preconditions.checkArgument(keys.size() == 1, "Expected only 1 key for list %s", appConfigBindingClass);
+ QName listKeyQName = keys.get(0);
+ return ImmutableNodes.mapEntryBuilder(bindingQName, listKeyQName, appConfigListKeyValue).build();
+ }
+
+ @Override
+ public NormalizedNode<?, ?> parseDataElement(final Element element, final DataSchemaNode dataSchema,
+ final DomToNormalizedNodeParserFactory parserFactory) {
+ return parserFactory.getMapEntryNodeParser().parse(Collections.singletonList(element),
+ (ListSchemaNode)dataSchema);
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Red Hat, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.blueprint.ext;
+
+/**
+ * Exception thrown by {@link DataStoreAppConfigDefaultXMLReader}.
+ *
+ * @author Michael Vorburger.ch
+ */
+public class ConfigXMLReaderException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ public ConfigXMLReaderException(String message) {
+ super(message);
+ }
+
+ public ConfigXMLReaderException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2016 Brocade Communications Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.blueprint.ext;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Strings;
+import com.google.common.io.Resources;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import org.opendaylight.controller.sal.core.api.model.SchemaService;
+import org.opendaylight.mdsal.binding.dom.codec.api.BindingNormalizedNodeSerializer;
+import org.opendaylight.yangtools.util.xml.UntrustedXML;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.impl.codec.xml.XmlUtils;
+import org.opendaylight.yangtools.yang.data.impl.schema.transform.dom.parser.DomToNormalizedNodeParserFactory;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.w3c.dom.Document;
+import org.xml.sax.SAXException;
+
+/**
+ * DataObject XML file reader used by {@link DataStoreAppConfigMetadata}.
+ * Available as a standalone class to make it easy to write unit tests which can
+ * catch malformed default "clustered-app-conf" config data XML files in
+ * downstream projects.
+ *
+ * @author Thomas Pantelis (originally; re-factored by Michael Vorburger.ch)
+ */
+public class DataStoreAppConfigDefaultXMLReader<T extends DataObject> {
+
+ private static final Logger LOG = LoggerFactory.getLogger(DataStoreAppConfigDefaultXMLReader.class);
+
+ private final String logName;
+ private final String defaultAppConfigFileName;
+ private final SchemaService schemaService;
+ private final BindingNormalizedNodeSerializer bindingSerializer;
+ private final BindingContext bindingContext;
+ private final ConfigURLProvider inputStreamProvider;
+
+ @FunctionalInterface
+ public interface FallbackConfigProvider {
+ NormalizedNode<?,?> get(SchemaContext schemaContext, DataSchemaNode dataSchema);
+ }
+
+ @FunctionalInterface
+ public interface ConfigURLProvider {
+ Optional<URL> getURL(String appConfigFileName) throws IOException;
+ }
+
+ public DataStoreAppConfigDefaultXMLReader(
+ String logName,
+ String defaultAppConfigFileName,
+ SchemaService schemaService,
+ BindingNormalizedNodeSerializer bindingSerializer,
+ BindingContext bindingContext,
+ ConfigURLProvider inputStreamProvider) {
+
+ this.logName = logName;
+ this.defaultAppConfigFileName = defaultAppConfigFileName;
+ this.schemaService = schemaService;
+ this.bindingSerializer = bindingSerializer;
+ this.bindingContext = bindingContext;
+ this.inputStreamProvider = inputStreamProvider;
+ }
+
+ public DataStoreAppConfigDefaultXMLReader(
+ Class<?> testClass,
+ String defaultAppConfigFileName,
+ SchemaService schemaService,
+ BindingNormalizedNodeSerializer bindingSerializer,
+ Class<T> klass) {
+ this(testClass.getName(), defaultAppConfigFileName, schemaService, bindingSerializer,
+ BindingContext.create(testClass.getName(), klass, null),
+ appConfigFileName -> Optional.of(getURL(testClass, defaultAppConfigFileName)));
+ }
+
+ private static URL getURL(Class<?> testClass, String defaultAppConfigFileName) {
+ return Resources.getResource(testClass, defaultAppConfigFileName);
+ }
+
+ public T createDefaultInstance() throws ConfigXMLReaderException {
+ return createDefaultInstance((schemaContext, dataSchema) -> {
+ throw new IllegalArgumentException("Failed to read XML "
+ + "(not creating model from defaults as runtime would, for better clarity in tests)");
+ });
+ }
+
+ @SuppressWarnings("unchecked")
+ public T createDefaultInstance(FallbackConfigProvider fallback) throws ConfigXMLReaderException {
+ YangInstanceIdentifier yangPath = bindingSerializer.toYangInstanceIdentifier(bindingContext.appConfigPath);
+
+ LOG.debug("{}: Creating app config instance from path {}, Qname: {}", logName, yangPath,
+ bindingContext.bindingQName);
+
+ if (schemaService == null) {
+ throw new ConfigXMLReaderException(
+ String.format("%s: Could not obtain the SchemaService OSGi service", logName));
+ }
+
+ SchemaContext schemaContext = schemaService.getGlobalContext();
+
+ Module module = schemaContext.findModuleByNamespaceAndRevision(bindingContext.bindingQName.getNamespace(),
+ bindingContext.bindingQName.getRevision());
+ if (module == null) {
+ throw new ConfigXMLReaderException(
+ String.format("%s: Could not obtain the module schema for namespace %s, revision %s",
+ logName, bindingContext.bindingQName.getNamespace(), bindingContext.bindingQName.getRevision()));
+ }
+
+ DataSchemaNode dataSchema = module.getDataChildByName(bindingContext.bindingQName);
+ if (dataSchema == null) {
+ throw new ConfigXMLReaderException(
+ String.format("%s: Could not obtain the schema for %s", logName,
+ bindingContext.bindingQName));
+ }
+
+ if (!bindingContext.schemaType.isAssignableFrom(dataSchema.getClass())) {
+ throw new ConfigXMLReaderException(
+ String.format("%s: Expected schema type %s for %s but actual type is %s", logName,
+ bindingContext.schemaType, bindingContext.bindingQName, dataSchema.getClass()));
+ }
+
+ NormalizedNode<?, ?> dataNode = parsePossibleDefaultAppConfigXMLFile(schemaContext, dataSchema);
+ if (dataNode == null) {
+ dataNode = fallback.get(schemaService.getGlobalContext(), dataSchema);
+ }
+
+ DataObject appConfig = bindingSerializer.fromNormalizedNode(yangPath, dataNode).getValue();
+ if (appConfig == null) {
+ // This shouldn't happen but need to handle it in case...
+ throw new ConfigXMLReaderException(
+ String.format("%s: Could not create instance for app config binding %s",
+ logName, bindingContext.appConfigBindingClass));
+ } else {
+ return (T) appConfig;
+ }
+ }
+
+ private NormalizedNode<?, ?> parsePossibleDefaultAppConfigXMLFile(final SchemaContext schemaContext,
+ final DataSchemaNode dataSchema) throws ConfigXMLReaderException {
+
+ String appConfigFileName = defaultAppConfigFileName;
+ if (Strings.isNullOrEmpty(appConfigFileName)) {
+ String moduleName = findYangModuleName(bindingContext.bindingQName, schemaContext);
+ appConfigFileName = moduleName + "_" + bindingContext.bindingQName.getLocalName() + ".xml";
+ }
+
+ DomToNormalizedNodeParserFactory parserFactory = DomToNormalizedNodeParserFactory.getInstance(
+ XmlUtils.DEFAULT_XML_CODEC_PROVIDER, schemaContext);
+
+ Optional<URL> optionalURL;
+ try {
+ optionalURL = inputStreamProvider.getURL(appConfigFileName);
+ } catch (IOException e) {
+ String msg = String.format("%s: Could not getURL()", logName);
+ LOG.error(msg, e);
+ throw new ConfigXMLReaderException(msg, e);
+ }
+ if (!optionalURL.isPresent()) {
+ return null;
+ }
+ URL url = optionalURL.get();
+ try (InputStream is = url.openStream()) {
+ Document root = UntrustedXML.newDocumentBuilder().parse(is);
+ NormalizedNode<?, ?> dataNode = bindingContext.parseDataElement(root.getDocumentElement(), dataSchema,
+ parserFactory);
+
+ LOG.debug("{}: Parsed data node: {}", logName, dataNode);
+
+ return dataNode;
+ } catch (SAXException | IOException e) {
+ String msg = String.format("%s: Could not read/parse app config %s", logName, url);
+ LOG.error(msg, e);
+ throw new ConfigXMLReaderException(msg, e);
+ }
+ }
+
+ private String findYangModuleName(final QName qname, final SchemaContext schemaContext)
+ throws ConfigXMLReaderException {
+ for (Module m : schemaContext.getModules()) {
+ if (qname.getModule().equals(m.getQNameModule())) {
+ return m.getName();
+ }
+ }
+ throw new ConfigXMLReaderException(
+ String.format("%s: Could not find yang module for QName %s", logName, qname));
+ }
+
+}
package org.opendaylight.controller.blueprint.ext;
import com.google.common.base.Optional;
-import com.google.common.base.Preconditions;
-import com.google.common.base.Strings;
import com.google.common.util.concurrent.CheckedFuture;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.lang.reflect.InvocationTargetException;
import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.apache.aries.blueprint.services.ExtendedBlueprintContainer;
+import org.opendaylight.controller.blueprint.ext.DataStoreAppConfigDefaultXMLReader.ConfigURLProvider;
import org.opendaylight.controller.md.sal.binding.api.ClusteredDataTreeChangeListener;
import org.opendaylight.controller.md.sal.binding.api.DataBroker;
import org.opendaylight.controller.md.sal.binding.api.DataObjectModification;
import org.opendaylight.controller.sal.core.api.model.SchemaService;
import org.opendaylight.mdsal.binding.dom.codec.api.BindingNormalizedNodeSerializer;
import org.opendaylight.yangtools.concepts.ListenerRegistration;
-import org.opendaylight.yangtools.util.xml.UntrustedXML;
import org.opendaylight.yangtools.yang.binding.DataObject;
-import org.opendaylight.yangtools.yang.binding.Identifiable;
-import org.opendaylight.yangtools.yang.binding.Identifier;
-import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
-import org.opendaylight.yangtools.yang.binding.util.BindingReflections;
-import org.opendaylight.yangtools.yang.common.QName;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
import org.opendaylight.yangtools.yang.data.impl.codec.xml.XmlUtils;
-import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
import org.opendaylight.yangtools.yang.data.impl.schema.transform.dom.parser.DomToNormalizedNodeParserFactory;
-import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.Module;
import org.opendaylight.yangtools.yang.model.api.SchemaContext;
import org.osgi.service.blueprint.container.ComponentDefinitionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.w3c.dom.Document;
import org.w3c.dom.Element;
-import org.xml.sax.SAXException;
/**
* Factory metadata corresponding to the "clustered-app-config" element that obtains an application's
logName(), appConfigBindingClassName), e);
}
- if (Identifiable.class.isAssignableFrom(appConfigBindingClass)) {
- // The binding class corresponds to a yang list.
- if (Strings.isNullOrEmpty(appConfigListKeyValue)) {
- throw new ComponentDefinitionException(String.format(
- "%s: App config binding class %s represents a yang list therefore \"%s\" must be specified",
- logName(), appConfigBindingClassName, LIST_KEY_VALUE));
- }
-
- try {
- bindingContext = ListBindingContext.newInstance(appConfigBindingClass, appConfigListKeyValue);
- } catch (InstantiationException | IllegalAccessException | IllegalArgumentException
- | InvocationTargetException | NoSuchMethodException | SecurityException e) {
- throw new ComponentDefinitionException(String.format(
- "%s: Error initializing for app config list binding class %s",
- logName(), appConfigBindingClassName), e);
- }
-
- } else {
- bindingContext = new ContainerBindingContext(appConfigBindingClass);
- }
+ bindingContext = BindingContext.create(logName(), appConfigBindingClass, appConfigListKeyValue);
}
@Override
private void retrieveDataBrokerService() {
LOG.debug("{}: In retrieveDataBrokerService", logName());
-
// Get the binding DataBroker OSGi service.
-
retrieveService("data-broker", DataBroker.class, service -> retrieveInitialAppConfig((DataBroker)service));
}
}
private void readInitialAppConfig(final DataBroker dataBroker) {
-
final ReadOnlyTransaction readOnlyTx = dataBroker.newReadOnlyTransaction();
CheckedFuture<Optional<DataObject>, ReadFailedException> future = readOnlyTx.read(
LogicalDatastoreType.CONFIGURATION, bindingContext.appConfigPath);
}
private DataObject createDefaultInstance() {
- YangInstanceIdentifier yangPath = bindingSerializer.toYangInstanceIdentifier(bindingContext.appConfigPath);
-
- LOG.debug("{}: Creating app config instance from path {}, Qname: {}", logName(), yangPath,
- bindingContext.bindingQName);
-
- SchemaService schemaService = getOSGiService(SchemaService.class);
- if (schemaService == null) {
- setFailureMessage(String.format("%s: Could not obtain the SchemaService OSGi service", logName()));
- return null;
- }
-
- SchemaContext schemaContext = schemaService.getGlobalContext();
-
- Module module = schemaContext.findModuleByNamespaceAndRevision(bindingContext.bindingQName.getNamespace(),
- bindingContext.bindingQName.getRevision());
- if (module == null) {
- setFailureMessage(String.format("%s: Could not obtain the module schema for namespace %s, revision %s",
- logName(), bindingContext.bindingQName.getNamespace(), bindingContext.bindingQName.getRevision()));
- return null;
- }
-
- DataSchemaNode dataSchema = module.getDataChildByName(bindingContext.bindingQName);
- if (dataSchema == null) {
- setFailureMessage(String.format("%s: Could not obtain the schema for %s", logName(),
- bindingContext.bindingQName));
- return null;
- }
-
- if (!bindingContext.schemaType.isAssignableFrom(dataSchema.getClass())) {
- setFailureMessage(String.format("%s: Expected schema type %s for %s but actual type is %s", logName(),
- bindingContext.schemaType, bindingContext.bindingQName, dataSchema.getClass()));
- return null;
- }
-
- NormalizedNode<?, ?> dataNode = parsePossibleDefaultAppConfigXMLFile(schemaContext, dataSchema);
- if (dataNode == null) {
- dataNode = parsePossibleDefaultAppConfigElement(schemaContext, dataSchema);
- }
-
- if (dataNode == null) {
- dataNode = bindingContext.newDefaultNode(dataSchema);
- }
-
- DataObject appConfig = bindingSerializer.fromNormalizedNode(yangPath, dataNode).getValue();
-
- if (appConfig == null) {
- // This shouldn't happen but need to handle it in case...
- setFailureMessage(String.format("%s: Could not create instance for app config binding %s",
- logName(), bindingContext.appConfigBindingClass));
- }
-
- return appConfig;
- }
+ try {
+ @SuppressWarnings("resource")
+ ConfigURLProvider inputStreamProvider = appConfigFileName -> {
+ File appConfigFile = new File(DEFAULT_APP_CONFIG_FILE_PATH, appConfigFileName);
+ LOG.debug("{}: parsePossibleDefaultAppConfigXMLFile looking for file {}", logName(),
+ appConfigFile.getAbsolutePath());
+
+ if (!appConfigFile.exists()) {
+ return Optional.absent();
+ }
- private NormalizedNode<?, ?> parsePossibleDefaultAppConfigXMLFile(final SchemaContext schemaContext,
- final DataSchemaNode dataSchema) {
+ LOG.debug("{}: Found file {}", logName(), appConfigFile.getAbsolutePath());
+
+ return Optional.of(appConfigFile.toURI().toURL());
+ };
+
+ DataStoreAppConfigDefaultXMLReader<?> reader = new DataStoreAppConfigDefaultXMLReader(logName(),
+ defaultAppConfigFileName, getOSGiService(SchemaService.class), bindingSerializer, bindingContext,
+ inputStreamProvider);
+ return reader.createDefaultInstance((schemaContext, dataSchema) -> {
+ // Fallback if file cannot be read, try XML from Config
+ NormalizedNode<?, ?> dataNode = parsePossibleDefaultAppConfigElement(schemaContext, dataSchema);
+ if (dataNode == null) {
+ // or, as last resort, defaults from the model
+ return bindingContext.newDefaultNode(dataSchema);
+ } else {
+ return dataNode;
+ }
+ });
- String appConfigFileName = defaultAppConfigFileName;
- if (Strings.isNullOrEmpty(appConfigFileName)) {
- String moduleName = findYangModuleName(bindingContext.bindingQName, schemaContext);
- if (moduleName == null) {
- return null;
+ } catch (ConfigXMLReaderException e) {
+ if (e.getCause() == null) {
+ setFailureMessage(e.getMessage());
+ } else {
+ setFailure(e.getMessage(), e);
}
-
- appConfigFileName = moduleName + "_" + bindingContext.bindingQName.getLocalName() + ".xml";
- }
-
- File appConfigFile = new File(DEFAULT_APP_CONFIG_FILE_PATH, appConfigFileName);
-
- LOG.debug("{}: parsePossibleDefaultAppConfigXMLFile looking for file {}", logName(),
- appConfigFile.getAbsolutePath());
-
- if (!appConfigFile.exists()) {
return null;
}
-
- LOG.debug("{}: Found file {}", logName(), appConfigFile.getAbsolutePath());
-
- DomToNormalizedNodeParserFactory parserFactory = DomToNormalizedNodeParserFactory.getInstance(
- XmlUtils.DEFAULT_XML_CODEC_PROVIDER, schemaContext);
-
- try (FileInputStream fis = new FileInputStream(appConfigFile)) {
- Document root = UntrustedXML.newDocumentBuilder().parse(fis);
- NormalizedNode<?, ?> dataNode = bindingContext.parseDataElement(root.getDocumentElement(), dataSchema,
- parserFactory);
-
- LOG.debug("{}: Parsed data node: {}", logName(), dataNode);
-
- return dataNode;
- } catch (SAXException | IOException e) {
- String msg = String.format("%s: Could not read/parse app config file %s", logName(), appConfigFile);
- LOG.error(msg, e);
- setFailureMessage(msg);
- }
-
- return null;
- }
-
- private String findYangModuleName(final QName qname, final SchemaContext schemaContext) {
- for (Module m : schemaContext.getModules()) {
- if (qname.getModule().equals(m.getQNameModule())) {
- return m.getName();
- }
- }
-
- setFailureMessage(String.format("%s: Could not find yang module for QName %s", logName(), qname));
- return null;
}
@Nullable
return dataNode;
}
-
@Override
public void destroy(final Object instance) {
super.destroy(instance);
}
}
- /**
- * Internal base class to abstract binding type-specific behavior.
- */
- private abstract static class BindingContext {
- final InstanceIdentifier<DataObject> appConfigPath;
- final Class<DataObject> appConfigBindingClass;
- final Class<? extends DataSchemaNode> schemaType;
- final QName bindingQName;
-
- protected BindingContext(final Class<DataObject> appConfigBindingClass,
- final InstanceIdentifier<DataObject> appConfigPath, final Class<? extends DataSchemaNode> schemaType) {
- this.appConfigBindingClass = appConfigBindingClass;
- this.appConfigPath = appConfigPath;
- this.schemaType = schemaType;
-
- bindingQName = BindingReflections.findQName(appConfigBindingClass);
- }
-
- abstract NormalizedNode<?, ?> parseDataElement(Element element, DataSchemaNode dataSchema,
- DomToNormalizedNodeParserFactory parserFactory);
-
- abstract NormalizedNode<?, ?> newDefaultNode(DataSchemaNode dataSchema);
- }
-
- /**
- * BindingContext implementation for a container binding.
- */
- private static class ContainerBindingContext extends BindingContext {
- ContainerBindingContext(final Class<DataObject> appConfigBindingClass) {
- super(appConfigBindingClass, InstanceIdentifier.create(appConfigBindingClass), ContainerSchemaNode.class);
- }
-
- @Override
- NormalizedNode<?, ?> newDefaultNode(final DataSchemaNode dataSchema) {
- return ImmutableNodes.containerNode(bindingQName);
- }
-
- @Override
- NormalizedNode<?, ?> parseDataElement(final Element element, final DataSchemaNode dataSchema,
- final DomToNormalizedNodeParserFactory parserFactory) {
- return parserFactory.getContainerNodeParser().parse(Collections.singletonList(element),
- (ContainerSchemaNode)dataSchema);
- }
- }
-
- /**
- * BindingContext implementation for a list binding.
- */
- private static class ListBindingContext extends BindingContext {
- final String appConfigListKeyValue;
-
- ListBindingContext(final Class<DataObject> appConfigBindingClass,
- final InstanceIdentifier<DataObject> appConfigPath, final String appConfigListKeyValue) {
- super(appConfigBindingClass, appConfigPath, ListSchemaNode.class);
- this.appConfigListKeyValue = appConfigListKeyValue;
- }
-
- @SuppressWarnings({ "rawtypes", "unchecked" })
- private static ListBindingContext newInstance(final Class<DataObject> bindingClass, final String listKeyValue)
- throws InstantiationException, IllegalAccessException, IllegalArgumentException,
- InvocationTargetException, NoSuchMethodException, SecurityException {
- // We assume the yang list key type is string.
- Identifier keyInstance = (Identifier) bindingClass.getMethod("getKey").getReturnType()
- .getConstructor(String.class).newInstance(listKeyValue);
- InstanceIdentifier appConfigPath = InstanceIdentifier.builder((Class)bindingClass, keyInstance).build();
- return new ListBindingContext(bindingClass, appConfigPath, listKeyValue);
- }
-
- @Override
- NormalizedNode<?, ?> newDefaultNode(final DataSchemaNode dataSchema) {
- // We assume there's only one key for the list.
- List<QName> keys = ((ListSchemaNode)dataSchema).getKeyDefinition();
- Preconditions.checkArgument(keys.size() == 1, "Expected only 1 key for list %s", appConfigBindingClass);
- QName listKeyQName = keys.iterator().next();
- return ImmutableNodes.mapEntryBuilder(bindingQName, listKeyQName, appConfigListKeyValue).build();
- }
-
- @Override
- NormalizedNode<?, ?> parseDataElement(final Element element, final DataSchemaNode dataSchema,
- final DomToNormalizedNodeParserFactory parserFactory) {
- return parserFactory.getMapEntryNodeParser().parse(Collections.singletonList(element),
- (ListSchemaNode)dataSchema);
- }
- }
}
--- /dev/null
+/*
+ * Copyright (c) 2017 Red Hat, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.blueprint.tests;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+import org.opendaylight.controller.blueprint.ext.DataStoreAppConfigDefaultXMLReader;
+import org.opendaylight.controller.md.sal.binding.test.AbstractConcurrentDataBrokerTest;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.store.rev140422.Lists;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.store.rev140422.lists.unordered.container.UnorderedList;
+
+/**
+ * Example unit test using the {@link DataStoreAppConfigDefaultXMLReader}.
+ *
+ * @author Michael Vorburger.ch
+ */
+public class DataStoreAppConfigDefaultXMLReaderTest extends AbstractConcurrentDataBrokerTest {
+
+ @Test
+ public void testConfigXML() throws Exception {
+ Lists lists = new DataStoreAppConfigDefaultXMLReader<>(
+ getClass(), "/opendaylight-sal-test-store-config.xml",
+ getDataBrokerTestCustomizer().getSchemaService(),
+ getDataBrokerTestCustomizer().getBindingToNormalized(),
+ Lists.class).createDefaultInstance();
+
+ UnorderedList element = lists.getUnorderedContainer().getUnorderedList().get(0);
+ assertThat(element.getName()).isEqualTo("someName");
+ assertThat(element.getValue()).isEqualTo("someValue");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testBadXMLName() throws Exception {
+ new DataStoreAppConfigDefaultXMLReader<>(
+ getClass(), "/badname.xml",
+ getDataBrokerTestCustomizer().getSchemaService(),
+ getDataBrokerTestCustomizer().getBindingToNormalized(),
+ Lists.class).createDefaultInstance();
+ }
+}
--- /dev/null
+<lists xmlns="urn:opendaylight:params:xml:ns:yang:controller:md:sal:test:store">
+ <unordered-container>
+ <unordered-list>
+ <name>someName</name>
+ <value>someValue</value>
+ </unordered-list>
+ </unordered-container>
+</lists>
protected abstract AbstractDataBrokerTestCustomizer createDataBrokerTestCustomizer();
+ public AbstractDataBrokerTestCustomizer getDataBrokerTestCustomizer() {
+ if (testCustomizer == null) {
+ throw new IllegalStateException("testCustomizer not yet set by call to createDataBrokerTestCustomizer()");
+ }
+ return testCustomizer;
+ }
+
@Override
protected void setupWithSchema(final SchemaContext context) {
testCustomizer = createDataBrokerTestCustomizer();