2 * Copyright (c) 2016 Brocade Communications Systems, Inc. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.controller.blueprint.ext;
10 import com.google.common.util.concurrent.FluentFuture;
11 import com.google.common.util.concurrent.FutureCallback;
12 import com.google.common.util.concurrent.MoreExecutors;
14 import java.io.IOException;
15 import java.net.URISyntaxException;
16 import java.util.Collection;
17 import java.util.Objects;
18 import java.util.Optional;
19 import java.util.concurrent.atomic.AtomicBoolean;
20 import javax.xml.stream.XMLStreamException;
21 import org.apache.aries.blueprint.services.ExtendedBlueprintContainer;
22 import org.eclipse.jdt.annotation.NonNull;
23 import org.eclipse.jdt.annotation.Nullable;
24 import org.opendaylight.controller.blueprint.ext.DataStoreAppConfigDefaultXMLReader.ConfigURLProvider;
25 import org.opendaylight.mdsal.binding.api.ClusteredDataTreeChangeListener;
26 import org.opendaylight.mdsal.binding.api.DataBroker;
27 import org.opendaylight.mdsal.binding.api.DataObjectModification;
28 import org.opendaylight.mdsal.binding.api.DataObjectModification.ModificationType;
29 import org.opendaylight.mdsal.binding.api.DataTreeIdentifier;
30 import org.opendaylight.mdsal.binding.api.DataTreeModification;
31 import org.opendaylight.mdsal.binding.api.ReadTransaction;
32 import org.opendaylight.mdsal.binding.dom.codec.api.BindingNormalizedNodeSerializer;
33 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
34 import org.opendaylight.mdsal.dom.api.DOMSchemaService;
35 import org.opendaylight.yangtools.concepts.Registration;
36 import org.opendaylight.yangtools.yang.binding.ChildOf;
37 import org.opendaylight.yangtools.yang.binding.DataObject;
38 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
39 import org.opendaylight.yangtools.yang.model.api.SchemaTreeInference;
40 import org.osgi.service.blueprint.container.ComponentDefinitionException;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
43 import org.w3c.dom.Element;
44 import org.xml.sax.SAXException;
47 * Factory metadata corresponding to the "clustered-app-config" element that obtains an application's
48 * config data from the data store and provides the binding DataObject instance to the Blueprint container
49 * as a bean. In addition registers a DataTreeChangeListener to restart the Blueprint container when the
50 * config data is changed.
52 * @author Thomas Pantelis
54 public class DataStoreAppConfigMetadata extends AbstractDependentComponentFactoryMetadata {
55 private static final Logger LOG = LoggerFactory.getLogger(DataStoreAppConfigMetadata.class);
57 static final String BINDING_CLASS = "binding-class";
58 static final String DEFAULT_CONFIG = "default-config";
59 static final String DEFAULT_CONFIG_FILE_NAME = "default-config-file-name";
60 static final String LIST_KEY_VALUE = "list-key-value";
62 private static final String DEFAULT_APP_CONFIG_FILE_PATH = "etc" + File.separator + "opendaylight" + File.separator
63 + "datastore" + File.separator + "initial" + File.separator + "config";
65 private final Element defaultAppConfigElement;
66 private final String defaultAppConfigFileName;
67 private final String appConfigBindingClassName;
68 private final String appConfigListKeyValue;
69 private final UpdateStrategy appConfigUpdateStrategy;
70 private final AtomicBoolean readingInitialAppConfig = new AtomicBoolean(true);
72 private volatile BindingContext bindingContext;
73 private volatile Registration appConfigChangeListenerReg;
74 private volatile DataObject currentAppConfig;
76 // Note: the BindingNormalizedNodeSerializer interface is annotated as deprecated because there's an
77 // equivalent interface in the mdsal project but the corresponding binding classes in the controller
78 // project are still used - conversion to the mdsal binding classes hasn't occurred yet.
79 private volatile BindingNormalizedNodeSerializer bindingSerializer;
81 public DataStoreAppConfigMetadata(final String id, final @NonNull String appConfigBindingClassName,
82 final @Nullable String appConfigListKeyValue, final @Nullable String defaultAppConfigFileName,
83 final @NonNull UpdateStrategy updateStrategyValue, final @Nullable Element defaultAppConfigElement) {
85 this.defaultAppConfigElement = defaultAppConfigElement;
86 this.defaultAppConfigFileName = defaultAppConfigFileName;
87 this.appConfigBindingClassName = appConfigBindingClassName;
88 this.appConfigListKeyValue = appConfigListKeyValue;
89 appConfigUpdateStrategy = updateStrategyValue;
93 @SuppressWarnings("unchecked")
94 public void init(final ExtendedBlueprintContainer container) {
95 super.init(container);
97 Class<DataObject> appConfigBindingClass;
99 Class<?> bindingClass = container.getBundleContext().getBundle().loadClass(appConfigBindingClassName);
100 if (!ChildOf.class.isAssignableFrom(bindingClass)) {
101 throw new ComponentDefinitionException(String.format(
102 "%s: Specified app config binding class %s does not extend %s",
103 logName(), appConfigBindingClassName, ChildOf.class.getName()));
106 appConfigBindingClass = (Class<DataObject>) bindingClass;
107 } catch (final ClassNotFoundException e) {
108 throw new ComponentDefinitionException(String.format("%s: Error loading app config binding class %s",
109 logName(), appConfigBindingClassName), e);
112 bindingContext = BindingContext.create(logName(), appConfigBindingClass, appConfigListKeyValue);
116 public Object create() throws ComponentDefinitionException {
117 LOG.debug("{}: In create - currentAppConfig: {}", logName(), currentAppConfig);
121 return currentAppConfig;
125 protected void startTracking() {
126 // First get the BindingNormalizedNodeSerializer OSGi service. This will be used to create a default
127 // instance of the app config binding class, if necessary.
129 retrieveService("binding-codec", BindingNormalizedNodeSerializer.class, service -> {
130 bindingSerializer = (BindingNormalizedNodeSerializer)service;
131 retrieveDataBrokerService();
135 private void retrieveDataBrokerService() {
136 LOG.debug("{}: In retrieveDataBrokerService", logName());
137 // Get the binding DataBroker OSGi service.
138 retrieveService("data-broker", DataBroker.class, service -> retrieveInitialAppConfig((DataBroker)service));
141 private void retrieveInitialAppConfig(final DataBroker dataBroker) {
142 LOG.debug("{}: Got DataBroker instance - reading app config {}", logName(), bindingContext.appConfigPath);
144 setDependencyDesc("Initial app config " + bindingContext.appConfigBindingClass.getSimpleName());
146 // We register a DTCL to get updates and also read the app config data from the data store. If
147 // the app config data is present then both the read and initial DTCN update will return it. If the
148 // the data isn't present, we won't get an initial DTCN update so the read will indicate the data
151 DataTreeIdentifier<DataObject> dataTreeId = DataTreeIdentifier.of(LogicalDatastoreType.CONFIGURATION,
152 bindingContext.appConfigPath);
153 appConfigChangeListenerReg = dataBroker.registerDataTreeChangeListener(dataTreeId,
154 (ClusteredDataTreeChangeListener<DataObject>) this::onAppConfigChanged);
156 readInitialAppConfig(dataBroker);
159 private void readInitialAppConfig(final DataBroker dataBroker) {
160 final FluentFuture<Optional<DataObject>> future;
161 try (ReadTransaction readOnlyTx = dataBroker.newReadOnlyTransaction()) {
162 future = readOnlyTx.read(LogicalDatastoreType.CONFIGURATION, bindingContext.appConfigPath);
165 future.addCallback(new FutureCallback<Optional<DataObject>>() {
167 public void onSuccess(final Optional<DataObject> possibleAppConfig) {
168 LOG.debug("{}: Read of app config {} succeeded: {}", logName(), bindingContext
169 .appConfigBindingClass.getName(), possibleAppConfig);
171 setInitialAppConfig(possibleAppConfig);
175 public void onFailure(final Throwable failure) {
176 // We may have gotten the app config via the data tree change listener so only retry if not.
177 if (readingInitialAppConfig.get()) {
178 LOG.warn("{}: Read of app config {} failed - retrying", logName(),
179 bindingContext.appConfigBindingClass.getName(), failure);
181 readInitialAppConfig(dataBroker);
184 }, MoreExecutors.directExecutor());
187 private void onAppConfigChanged(final Collection<DataTreeModification<DataObject>> changes) {
188 for (DataTreeModification<DataObject> change: changes) {
189 DataObjectModification<DataObject> changeRoot = change.getRootNode();
190 ModificationType type = changeRoot.getModificationType();
192 LOG.debug("{}: onAppConfigChanged: {}, {}", logName(), type, change.getRootPath());
194 if (type == ModificationType.SUBTREE_MODIFIED || type == ModificationType.WRITE) {
195 DataObject newAppConfig = changeRoot.getDataAfter();
197 LOG.debug("New app config instance: {}, previous: {}", newAppConfig, currentAppConfig);
199 if (!setInitialAppConfig(Optional.of(newAppConfig))
200 && !Objects.equals(currentAppConfig, newAppConfig)) {
201 LOG.debug("App config was updated");
203 if (appConfigUpdateStrategy == UpdateStrategy.RELOAD) {
207 } else if (type == ModificationType.DELETE) {
208 LOG.debug("App config was deleted");
210 if (appConfigUpdateStrategy == UpdateStrategy.RELOAD) {
217 private boolean setInitialAppConfig(final Optional<DataObject> possibleAppConfig) {
218 boolean result = readingInitialAppConfig.compareAndSet(true, false);
220 DataObject localAppConfig;
221 if (possibleAppConfig.isPresent()) {
222 localAppConfig = possibleAppConfig.orElseThrow();
224 // No app config data is present so create an empty instance via the bindingSerializer service.
225 // This will also return default values for leafs that haven't been explicitly set.
226 localAppConfig = createDefaultInstance();
229 LOG.debug("{}: Setting currentAppConfig instance: {}", logName(), localAppConfig);
231 // Now publish the app config instance to the volatile field and notify the callback to let the
232 // container know our dependency is now satisfied.
233 currentAppConfig = localAppConfig;
240 private DataObject createDefaultInstance() {
242 ConfigURLProvider inputStreamProvider = appConfigFileName -> {
243 File appConfigFile = new File(DEFAULT_APP_CONFIG_FILE_PATH, appConfigFileName);
244 LOG.debug("{}: parsePossibleDefaultAppConfigXMLFile looking for file {}", logName(),
245 appConfigFile.getAbsolutePath());
247 if (!appConfigFile.exists()) {
248 return Optional.empty();
251 LOG.debug("{}: Found file {}", logName(), appConfigFile.getAbsolutePath());
253 return Optional.of(appConfigFile.toURI().toURL());
256 DataStoreAppConfigDefaultXMLReader<?> reader = new DataStoreAppConfigDefaultXMLReader<>(logName(),
257 defaultAppConfigFileName, getOSGiService(DOMSchemaService.class), bindingSerializer, bindingContext,
258 inputStreamProvider);
259 return reader.createDefaultInstance(dataSchema -> {
260 // Fallback if file cannot be read, try XML from Config
261 NormalizedNode dataNode = parsePossibleDefaultAppConfigElement(dataSchema);
262 if (dataNode == null) {
263 // or, as last resort, defaults from the model
264 return bindingContext.newDefaultNode(dataSchema);
270 } catch (ConfigXMLReaderException | IOException | SAXException | XMLStreamException | URISyntaxException e) {
271 if (e.getCause() == null) {
272 setFailureMessage(e.getMessage());
274 setFailure(e.getMessage(), e);
280 private @Nullable NormalizedNode parsePossibleDefaultAppConfigElement(final SchemaTreeInference dataSchema)
281 throws URISyntaxException, IOException, SAXException, XMLStreamException {
282 if (defaultAppConfigElement == null) {
286 LOG.debug("{}: parsePossibleDefaultAppConfigElement for {}", logName(), bindingContext.bindingQName);
288 LOG.debug("{}: Got app config schema: {}", logName(), dataSchema);
290 NormalizedNode dataNode = bindingContext.parseDataElement(defaultAppConfigElement, dataSchema);
292 LOG.debug("{}: Parsed data node: {}", logName(), dataNode);
298 public void destroy(final Object instance) {
299 super.destroy(instance);
301 if (appConfigChangeListenerReg != null) {
302 appConfigChangeListenerReg.close();
303 appConfigChangeListenerReg = null;