X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?p=controller.git;a=blobdiff_plain;f=opendaylight%2Fblueprint%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Fcontroller%2Fblueprint%2Fext%2FDataStoreAppConfigMetadata.java;h=b2801d052f3a0e595eaf0865e45974ae67ac80b9;hp=b479d39ffe06efd2a388a0e8b2aa21a8289d49d9;hb=6f3281a884977f857217b0d2b40a84fb70483fec;hpb=67ff0fc78b2933b8b4f5a8544c7639499824e622 diff --git a/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/DataStoreAppConfigMetadata.java b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/DataStoreAppConfigMetadata.java index b479d39ffe..b2801d052f 100644 --- a/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/DataStoreAppConfigMetadata.java +++ b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/DataStoreAppConfigMetadata.java @@ -7,58 +7,40 @@ */ 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.FluentFuture; import com.google.common.util.concurrent.FutureCallback; -import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.MoreExecutors; import java.io.File; -import java.io.FileInputStream; import java.io.IOException; -import java.lang.reflect.InvocationTargetException; +import java.net.URISyntaxException; import java.util.Collection; -import java.util.Collections; -import java.util.List; import java.util.Objects; +import java.util.Optional; import java.util.concurrent.atomic.AtomicBoolean; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; +import javax.xml.stream.XMLStreamException; import org.apache.aries.blueprint.services.ExtendedBlueprintContainer; -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.md.sal.binding.api.DataObjectModification.ModificationType; -import org.opendaylight.controller.md.sal.binding.api.DataTreeIdentifier; -import org.opendaylight.controller.md.sal.binding.api.DataTreeModification; -import org.opendaylight.controller.md.sal.binding.api.ReadOnlyTransaction; -import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType; -import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException; -import org.opendaylight.controller.sal.core.api.model.SchemaService; +import org.opendaylight.controller.blueprint.ext.DataStoreAppConfigDefaultXMLReader.ConfigURLProvider; +import org.opendaylight.mdsal.binding.api.ClusteredDataTreeChangeListener; +import org.opendaylight.mdsal.binding.api.DataBroker; +import org.opendaylight.mdsal.binding.api.DataObjectModification; +import org.opendaylight.mdsal.binding.api.DataObjectModification.ModificationType; +import org.opendaylight.mdsal.binding.api.DataTreeIdentifier; +import org.opendaylight.mdsal.binding.api.DataTreeModification; +import org.opendaylight.mdsal.binding.api.ReadTransaction; import org.opendaylight.mdsal.binding.dom.codec.api.BindingNormalizedNodeSerializer; +import org.opendaylight.mdsal.common.api.LogicalDatastoreType; +import org.opendaylight.mdsal.dom.api.DOMSchemaService; import org.opendaylight.yangtools.concepts.ListenerRegistration; 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; @@ -81,17 +63,6 @@ public class DataStoreAppConfigMetadata extends AbstractDependentComponentFactor private static final String DEFAULT_APP_CONFIG_FILE_PATH = "etc" + File.separator + "opendaylight" + File.separator + "datastore" + File.separator + "initial" + File.separator + "config"; - private static final DocumentBuilderFactory DOC_BUILDER_FACTORY; - - static { - DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); - factory.setNamespaceAware(true); - factory.setCoalescing(true); - factory.setIgnoringElementContentWhitespace(true); - factory.setIgnoringComments(true); - DOC_BUILDER_FACTORY = factory; - } - private final Element defaultAppConfigElement; private final String defaultAppConfigFileName; private final String appConfigBindingClassName; @@ -108,9 +79,9 @@ public class DataStoreAppConfigMetadata extends AbstractDependentComponentFactor // project are still used - conversion to the mdsal binding classes hasn't occurred yet. private volatile BindingNormalizedNodeSerializer bindingSerializer; - public DataStoreAppConfigMetadata(@Nonnull String id, @Nonnull String appConfigBindingClassName, - @Nullable String appConfigListKeyValue, @Nullable String defaultAppConfigFileName, - @Nonnull UpdateStrategy updateStrategyValue, @Nullable Element defaultAppConfigElement) { + public DataStoreAppConfigMetadata(@Nonnull final String id, @Nonnull final String appConfigBindingClassName, + @Nullable final String appConfigListKeyValue, @Nullable final String defaultAppConfigFileName, + @Nonnull final UpdateStrategy updateStrategyValue, @Nullable final Element defaultAppConfigElement) { super(id); this.defaultAppConfigElement = defaultAppConfigElement; this.defaultAppConfigFileName = defaultAppConfigFileName; @@ -121,7 +92,7 @@ public class DataStoreAppConfigMetadata extends AbstractDependentComponentFactor @Override @SuppressWarnings("unchecked") - public void init(ExtendedBlueprintContainer container) { + public void init(final ExtendedBlueprintContainer container) { super.init(container); Class appConfigBindingClass; @@ -134,31 +105,12 @@ public class DataStoreAppConfigMetadata extends AbstractDependentComponentFactor } appConfigBindingClass = (Class) bindingClass; - } catch (ClassNotFoundException e) { + } catch (final ClassNotFoundException e) { throw new ComponentDefinitionException(String.format("%s: Error loading app config binding class %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 @@ -183,49 +135,45 @@ public class DataStoreAppConfigMetadata extends AbstractDependentComponentFactor 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 retrieveInitialAppConfig(DataBroker dataBroker) { + private void retrieveInitialAppConfig(final DataBroker dataBroker) { LOG.debug("{}: Got DataBroker instance - reading app config {}", logName(), bindingContext.appConfigPath); - setDependendencyDesc("Initial app config " + bindingContext.appConfigBindingClass.getSimpleName()); + setDependencyDesc("Initial app config " + bindingContext.appConfigBindingClass.getSimpleName()); // We register a DTCL to get updates and also read the app config data from the data store. If // the app config data is present then both the read and initial DTCN update will return it. If the // the data isn't present, we won't get an initial DTCN update so the read will indicate the data // isn't present. - DataTreeIdentifier dataTreeId = new DataTreeIdentifier<>(LogicalDatastoreType.CONFIGURATION, + DataTreeIdentifier dataTreeId = DataTreeIdentifier.create(LogicalDatastoreType.CONFIGURATION, bindingContext.appConfigPath); appConfigChangeListenerReg = dataBroker.registerDataTreeChangeListener(dataTreeId, - (ClusteredDataTreeChangeListener) changes -> onAppConfigChanged(changes)); + (ClusteredDataTreeChangeListener) this::onAppConfigChanged); readInitialAppConfig(dataBroker); } private void readInitialAppConfig(final DataBroker dataBroker) { + final FluentFuture> future; + try (ReadTransaction readOnlyTx = dataBroker.newReadOnlyTransaction()) { + future = readOnlyTx.read(LogicalDatastoreType.CONFIGURATION, bindingContext.appConfigPath); + } - final ReadOnlyTransaction readOnlyTx = dataBroker.newReadOnlyTransaction(); - CheckedFuture, ReadFailedException> future = readOnlyTx.read( - LogicalDatastoreType.CONFIGURATION, bindingContext.appConfigPath); - Futures.addCallback(future, new FutureCallback>() { + future.addCallback(new FutureCallback>() { @Override - public void onSuccess(Optional possibleAppConfig) { + public void onSuccess(final Optional possibleAppConfig) { LOG.debug("{}: Read of app config {} succeeded: {}", logName(), bindingContext .appConfigBindingClass.getName(), possibleAppConfig); - readOnlyTx.close(); setInitialAppConfig(possibleAppConfig); } @Override - public void onFailure(Throwable failure) { - readOnlyTx.close(); - + public void onFailure(final Throwable failure) { // We may have gotten the app config via the data tree change listener so only retry if not. if (readingInitialAppConfig.get()) { LOG.warn("{}: Read of app config {} failed - retrying", logName(), @@ -234,10 +182,10 @@ public class DataStoreAppConfigMetadata extends AbstractDependentComponentFactor readInitialAppConfig(dataBroker); } } - }); + }, MoreExecutors.directExecutor()); } - private void onAppConfigChanged(Collection> changes) { + private void onAppConfigChanged(final Collection> changes) { for (DataTreeModification change: changes) { DataObjectModification changeRoot = change.getRootNode(); ModificationType type = changeRoot.getModificationType(); @@ -267,7 +215,7 @@ public class DataStoreAppConfigMetadata extends AbstractDependentComponentFactor } } - private boolean setInitialAppConfig(Optional possibleAppConfig) { + private boolean setInitialAppConfig(final Optional possibleAppConfig) { boolean result = readingInitialAppConfig.compareAndSet(true, false); if (result) { DataObject localAppConfig; @@ -291,141 +239,68 @@ public class DataStoreAppConfigMetadata extends AbstractDependentComponentFactor } 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)); - } + try { + ConfigURLProvider inputStreamProvider = appConfigFileName -> { + File appConfigFile = new File(DEFAULT_APP_CONFIG_FILE_PATH, appConfigFileName); + LOG.debug("{}: parsePossibleDefaultAppConfigXMLFile looking for file {}", logName(), + appConfigFile.getAbsolutePath()); - return appConfig; - } + if (!appConfigFile.exists()) { + return Optional.empty(); + } - private NormalizedNode parsePossibleDefaultAppConfigXMLFile(SchemaContext schemaContext, - DataSchemaNode dataSchema) { + LOG.debug("{}: Found file {}", logName(), appConfigFile.getAbsolutePath()); + + return Optional.of(appConfigFile.toURI().toURL()); + }; + + DataStoreAppConfigDefaultXMLReader reader = new DataStoreAppConfigDefaultXMLReader<>(logName(), + defaultAppConfigFileName, getOSGiService(DOMSchemaService.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 (final ConfigXMLReaderException | IOException | SAXException | XMLStreamException + | ParserConfigurationException | URISyntaxException 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 = DOC_BUILDER_FACTORY.newDocumentBuilder().parse(fis); - NormalizedNode dataNode = bindingContext.parseDataElement(root.getDocumentElement(), dataSchema, - parserFactory); - - LOG.debug("{}: Parsed data node: {}", logName(), dataNode); - - return dataNode; - } catch (SAXException | IOException | ParserConfigurationException 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(QName qname, 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 - private NormalizedNode parsePossibleDefaultAppConfigElement(SchemaContext schemaContext, - DataSchemaNode dataSchema) { + private NormalizedNode parsePossibleDefaultAppConfigElement(final SchemaContext schemaContext, + final DataSchemaNode dataSchema) throws URISyntaxException, IOException, ParserConfigurationException, + SAXException, XMLStreamException { if (defaultAppConfigElement == null) { return null; } LOG.debug("{}: parsePossibleDefaultAppConfigElement for {}", logName(), bindingContext.bindingQName); - DomToNormalizedNodeParserFactory parserFactory = DomToNormalizedNodeParserFactory.getInstance( - XmlUtils.DEFAULT_XML_CODEC_PROVIDER, schemaContext); - - LOG.debug("{}: Got app config schema: {}", logName(), dataSchema); NormalizedNode dataNode = bindingContext.parseDataElement(defaultAppConfigElement, dataSchema, - parserFactory); + schemaContext); LOG.debug("{}: Parsed data node: {}", logName(), dataNode); return dataNode; } - @Override - public void destroy(Object instance) { + public void destroy(final Object instance) { super.destroy(instance); if (appConfigChangeListenerReg != null) { @@ -434,88 +309,4 @@ public class DataStoreAppConfigMetadata extends AbstractDependentComponentFactor } } - /** - * Internal base class to abstract binding type-specific behavior. - */ - private abstract static class BindingContext { - final InstanceIdentifier appConfigPath; - final Class appConfigBindingClass; - final Class schemaType; - final QName bindingQName; - - protected BindingContext(Class appConfigBindingClass, InstanceIdentifier appConfigPath, - Class 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(Class appConfigBindingClass) { - super(appConfigBindingClass, InstanceIdentifier.create(appConfigBindingClass), ContainerSchemaNode.class); - } - - @Override - NormalizedNode newDefaultNode(DataSchemaNode dataSchema) { - return ImmutableNodes.containerNode(bindingQName); - } - - @Override - NormalizedNode parseDataElement(Element element, DataSchemaNode dataSchema, - 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(Class appConfigBindingClass, InstanceIdentifier appConfigPath, - String appConfigListKeyValue) { - super(appConfigBindingClass, appConfigPath, ListSchemaNode.class); - this.appConfigListKeyValue = appConfigListKeyValue; - } - - @SuppressWarnings({ "rawtypes", "unchecked" }) - private static ListBindingContext newInstance(Class bindingClass, 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(DataSchemaNode dataSchema) { - // We assume there's only one key for the list. - List 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(Element element, DataSchemaNode dataSchema, - DomToNormalizedNodeParserFactory parserFactory) { - return parserFactory.getMapEntryNodeParser().parse(Collections.singletonList(element), - (ListSchemaNode)dataSchema); - } - } }