*/
package org.opendaylight.controller.sample.toaster.provider;
-import static org.opendaylight.controller.md.sal.binding.api.DataObjectModification.ModificationType.DELETE;
-import static org.opendaylight.controller.md.sal.binding.api.DataObjectModification.ModificationType.WRITE;
-import static org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType.CONFIGURATION;
-import static org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType.OPERATIONAL;
-import static org.opendaylight.yangtools.yang.common.RpcError.ErrorType.APPLICATION;
-
-import com.google.common.base.Function;
-import com.google.common.base.Optional;
-import com.google.common.base.Preconditions;
+import static java.util.Objects.requireNonNull;
+import static org.opendaylight.mdsal.common.api.LogicalDatastoreType.CONFIGURATION;
+import static org.opendaylight.mdsal.common.api.LogicalDatastoreType.OPERATIONAL;
+import static org.opendaylight.yangtools.yang.common.ErrorType.APPLICATION;
+
+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.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.SettableFuture;
-import java.util.Collection;
+import java.util.List;
+import java.util.Optional;
import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
-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.DataTreeChangeListener;
-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.NotificationPublishService;
-import org.opendaylight.controller.md.sal.binding.api.ReadWriteTransaction;
-import org.opendaylight.controller.md.sal.binding.api.WriteTransaction;
-import org.opendaylight.controller.md.sal.common.api.data.OptimisticLockFailedException;
-import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
+import java.util.function.Function;
+import javax.annotation.PreDestroy;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import org.eclipse.jdt.annotation.NonNull;
import org.opendaylight.controller.md.sal.common.util.jmx.AbstractMXBean;
+import org.opendaylight.mdsal.binding.api.DataBroker;
+import org.opendaylight.mdsal.binding.api.DataTreeChangeListener;
+import org.opendaylight.mdsal.binding.api.DataTreeIdentifier;
+import org.opendaylight.mdsal.binding.api.DataTreeModification;
+import org.opendaylight.mdsal.binding.api.NotificationPublishService;
+import org.opendaylight.mdsal.binding.api.ReadWriteTransaction;
+import org.opendaylight.mdsal.binding.api.RpcProviderService;
+import org.opendaylight.mdsal.binding.api.WriteTransaction;
+import org.opendaylight.mdsal.common.api.CommitInfo;
+import org.opendaylight.mdsal.common.api.OptimisticLockFailedException;
+import org.opendaylight.mdsal.common.api.TransactionCommitFailedException;
+import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.CancelToast;
import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.CancelToastInput;
import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.CancelToastOutput;
import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.CancelToastOutputBuilder;
import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.DisplayString;
+import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.MakeToast;
import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.MakeToastInput;
import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.MakeToastOutput;
import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.MakeToastOutputBuilder;
+import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.RestockToaster;
import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.RestockToasterInput;
import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.RestockToasterOutput;
import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.RestockToasterOutputBuilder;
import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.ToasterOutOfBreadBuilder;
import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.ToasterRestocked;
import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.ToasterRestockedBuilder;
-import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.ToasterService;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.toaster.app.config.rev160503.ToasterAppConfig;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.toaster.app.config.rev160503.ToasterAppConfigBuilder;
-import org.opendaylight.yangtools.concepts.ListenerRegistration;
+import org.opendaylight.yangtools.concepts.Registration;
import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.common.ErrorTag;
+import org.opendaylight.yangtools.yang.common.ErrorType;
import org.opendaylight.yangtools.yang.common.RpcError;
-import org.opendaylight.yangtools.yang.common.RpcError.ErrorType;
import org.opendaylight.yangtools.yang.common.RpcResult;
import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.Designate;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-public class OpendaylightToaster extends AbstractMXBean
- implements ToasterService, ToasterProviderRuntimeMXBean, DataTreeChangeListener<Toaster>, AutoCloseable {
+@Singleton
+@Component(service = MakeToast.class, immediate = true)
+@Designate(ocd = OpendaylightToaster.Configuration.class)
+public final class OpendaylightToaster extends AbstractMXBean
+ implements MakeToast, ToasterProviderRuntimeMXBean, DataTreeChangeListener<Toaster>, AutoCloseable {
+ @ObjectClassDefinition
+ public @interface Configuration {
+ @AttributeDefinition(description = "The name of the toaster's manufacturer", max = "255")
+ String manufacturer() default TOASTER_MANUFACTURER;
+ @AttributeDefinition(description = "The name of the toaster's model", max = "255")
+ String modelNumber() default TOASTER_MODEL_NUMBER;
+ @AttributeDefinition(description = "How many times we attempt to make toast before failing ",
+ min = "0", max = "65535")
+ int maxMakeToastTries() default 2;
+ }
private static final CancelToastOutput EMPTY_CANCEL_OUTPUT = new CancelToastOutputBuilder().build();
private static final MakeToastOutput EMPTY_MAKE_OUTPUT = new MakeToastOutputBuilder().build();
private static final Logger LOG = LoggerFactory.getLogger(OpendaylightToaster.class);
private static final InstanceIdentifier<Toaster> TOASTER_IID = InstanceIdentifier.builder(Toaster.class).build();
- private static final DisplayString TOASTER_MANUFACTURER = new DisplayString("Opendaylight");
- private static final DisplayString TOASTER_MODEL_NUMBER = new DisplayString("Model 1 - Binding Aware");
+ private static final String TOASTER_MANUFACTURER = "Opendaylight";
+ private static final String TOASTER_MODEL_NUMBER = "Model 1 - Binding Aware";
- private DataBroker dataBroker;
- private NotificationPublishService notificationProvider;
- private ListenerRegistration<OpendaylightToaster> dataTreeChangeListenerRegistration;
+ private final DataBroker dataBroker;
+ private final NotificationPublishService notificationProvider;
+ private final Registration dataTreeChangeListenerRegistration;
+ private final Registration reg;
private final ExecutorService executor;
private final AtomicLong toastsMade = new AtomicLong(0);
private final AtomicLong darknessFactor = new AtomicLong(1000);
- private final ToasterAppConfig toasterAppConfig;
-
- public OpendaylightToaster() {
- this(new ToasterAppConfigBuilder().setManufacturer(TOASTER_MANUFACTURER).setModelNumber(TOASTER_MODEL_NUMBER)
- .setMaxMakeToastTries(2).build());
- }
+ private final @NonNull DisplayString manufacturer;
+ private final @NonNull DisplayString modelNumber;
+ private final int maxMakeToastTries;
- public OpendaylightToaster(final ToasterAppConfig toasterAppConfig) {
+ public OpendaylightToaster(final DataBroker dataProvider,
+ final NotificationPublishService notificationPublishService, final RpcProviderService rpcProviderService,
+ final String manufacturer, final String modelNumber, final int maxMakeToastTries) {
super("OpendaylightToaster", "toaster-provider", null);
- executor = Executors.newFixedThreadPool(1);
- this.toasterAppConfig = toasterAppConfig;
- }
+ notificationProvider = requireNonNull(notificationPublishService);
+ dataBroker = requireNonNull(dataProvider);
- public void setNotificationProvider(final NotificationPublishService notificationPublishService) {
- this.notificationProvider = notificationPublishService;
- }
+ this.manufacturer = new DisplayString(manufacturer);
+ this.modelNumber = new DisplayString(modelNumber);
+ this.maxMakeToastTries = maxMakeToastTries;
- public void setDataBroker(final DataBroker dataBroker) {
- this.dataBroker = dataBroker;
- }
+ executor = Executors.newFixedThreadPool(1);
+ reg = rpcProviderService.registerRpcImplementations(
+ (CancelToast) this::cancelToast,
+ this,
+ (RestockToaster) this::restockToaster);
- public void init() {
LOG.info("Initializing...");
- Preconditions.checkNotNull(dataBroker, "dataBroker must be set");
- dataTreeChangeListenerRegistration = dataBroker.registerDataTreeChangeListener(
- new DataTreeIdentifier<>(CONFIGURATION, TOASTER_IID), this);
- setToasterStatusUp(null);
+ dataTreeChangeListenerRegistration = requireNonNull(dataBroker, "dataBroker must be set")
+ .registerTreeChangeListener(DataTreeIdentifier.of(CONFIGURATION, TOASTER_IID), this);
+ try {
+ setToasterStatusUp(null).get();
+ } catch (InterruptedException | ExecutionException e) {
+ throw new IllegalStateException("Failed to commit initial data", e);
+ }
// Register our MXBean.
register();
}
+ @Inject
+ public OpendaylightToaster(final DataBroker dataProvider,
+ final NotificationPublishService notificationPublishService, final RpcProviderService rpcProviderService) {
+ this(dataProvider, notificationPublishService, rpcProviderService, TOASTER_MANUFACTURER, TOASTER_MODEL_NUMBER,
+ 2);
+ }
+
+ @Activate
+ public OpendaylightToaster(@Reference final DataBroker dataProvider,
+ @Reference final NotificationPublishService notificationPublishService,
+ @Reference final RpcProviderService rpcProviderService, final @NonNull Configuration configuration) {
+ this(dataProvider, notificationPublishService, rpcProviderService, configuration.manufacturer(),
+ configuration.modelNumber(), configuration.maxMakeToastTries());
+ }
+
/**
* Implemented from the AutoCloseable interface.
*/
@Override
+ @PreDestroy
+ @Deactivate
public void close() {
LOG.info("Closing...");
// Unregister our MXBean.
unregister();
+ reg.close();
// When we close this service we need to shutdown our executor!
executor.shutdown();
if (dataBroker != null) {
WriteTransaction tx = dataBroker.newWriteOnlyTransaction();
tx.delete(OPERATIONAL,TOASTER_IID);
- Futures.addCallback(tx.submit(), new FutureCallback<Void>() {
+ Futures.addCallback(tx.commit(), new FutureCallback<CommitInfo>() {
@Override
- public void onSuccess(final Void result) {
+ public void onSuccess(final CommitInfo result) {
LOG.debug("Successfully deleted the operational Toaster");
}
// note - we are simulating a device whose manufacture and model are
// fixed (embedded) into the hardware.
// This is why the manufacture and model number are hardcoded.
- return new ToasterBuilder().setToasterManufacturer(toasterAppConfig.getManufacturer())
- .setToasterModelNumber(toasterAppConfig.getModelNumber()).setToasterStatus(status).build();
+ return new ToasterBuilder()
+ .setToasterManufacturer(manufacturer)
+ .setToasterModelNumber(modelNumber)
+ .setToasterStatus(status)
+ .build();
}
/**
* Implemented from the DataTreeChangeListener interface.
*/
@Override
- public void onDataTreeChanged(final Collection<DataTreeModification<Toaster>> changes) {
- for (DataTreeModification<Toaster> change: changes) {
- DataObjectModification<Toaster> rootNode = change.getRootNode();
- if (rootNode.getModificationType() == WRITE) {
- Toaster oldToaster = rootNode.getDataBefore();
- Toaster newToaster = rootNode.getDataAfter();
- LOG.info("onDataTreeChanged - Toaster config with path {} was added or replaced: "
- + "old Toaster: {}, new Toaster: {}", change.getRootPath().getRootIdentifier(),
- oldToaster, newToaster);
-
- Long darkness = newToaster.getDarknessFactor();
- if (darkness != null) {
- darknessFactor.set(darkness);
+ public void onDataTreeChanged(final List<DataTreeModification<Toaster>> changes) {
+ for (var change: changes) {
+ final var rootNode = change.getRootNode();
+ switch (rootNode.modificationType()) {
+ case WRITE -> {
+ final var oldToaster = rootNode.dataBefore();
+ final var newToaster = rootNode.dataAfter();
+ LOG.info("onDataTreeChanged - Toaster config with path {} was added or replaced: old Toaster: {}, "
+ + "new Toaster: {}", change.getRootPath().path(), oldToaster, newToaster);
+
+ final var darkness = newToaster.getDarknessFactor();
+ if (darkness != null) {
+ darknessFactor.set(darkness.toJava());
+ }
+ }
+ case DELETE -> LOG.info("onDataTreeChanged - Toaster config with path {} was deleted: old Toaster: {}",
+ change.getRootPath().path(), rootNode.dataBefore());
+ default -> {
+ // No-op
}
- } else if (rootNode.getModificationType() == DELETE) {
- LOG.info("onDataTreeChanged - Toaster config with path {} was deleted: old Toaster: {}",
- change.getRootPath().getRootIdentifier(), rootNode.getDataBefore());
}
}
}
/**
* RPC call implemented from the ToasterService interface that cancels the current toast, if any.
*/
- @Override
- public ListenableFuture<RpcResult<CancelToastOutput>> cancelToast(final CancelToastInput input) {
- Future<?> current = currentMakeToastTask.getAndSet(null);
+ private ListenableFuture<RpcResult<CancelToastOutput>> cancelToast(final CancelToastInput input) {
+ final var current = currentMakeToastTask.getAndSet(null);
if (current != null) {
current.cancel(true);
}
* RPC call implemented from the ToasterService interface that attempts to make toast.
*/
@Override
- public ListenableFuture<RpcResult<MakeToastOutput>> makeToast(final MakeToastInput input) {
- LOG.info("makeToast: " + input);
-
- final SettableFuture<RpcResult<MakeToastOutput>> futureResult = SettableFuture.create();
-
- checkStatusAndMakeToast(input, futureResult, toasterAppConfig.getMaxMakeToastTries());
-
+ public ListenableFuture<RpcResult<MakeToastOutput>> invoke(final MakeToastInput input) {
+ LOG.info("makeToast: {}", input);
+ final var futureResult = SettableFuture.<RpcResult<MakeToastOutput>>create();
+ checkStatusAndMakeToast(input, futureResult, maxMakeToastTries);
return futureResult;
}
private static RpcError makeToasterOutOfBreadError() {
- return RpcResultBuilder.newError(APPLICATION, "resource-denied", "Toaster is out of bread", "out-of-stock",
- null, null);
+ return RpcResultBuilder.newError(APPLICATION, ErrorTag.RESOURCE_DENIED, "Toaster is out of bread",
+ "out-of-stock", null, null);
}
private static RpcError makeToasterInUseError() {
- return RpcResultBuilder.newWarning(APPLICATION, "in-use", "Toaster is busy", null, null, null);
+ return RpcResultBuilder.newWarning(APPLICATION, ErrorTag.IN_USE, "Toaster is busy", null, null, null);
}
private void checkStatusAndMakeToast(final MakeToastInput input,
// If that succeeds, then we essentially have an exclusive lock and can proceed
// to make toast.
final ReadWriteTransaction tx = dataBroker.newReadWriteTransaction();
- ListenableFuture<Optional<Toaster>> readFuture = tx.read(OPERATIONAL, TOASTER_IID);
+ FluentFuture<Optional<Toaster>> readFuture = tx.read(OPERATIONAL, TOASTER_IID);
- final ListenableFuture<Void> commitFuture =
+ final ListenableFuture<? extends CommitInfo> commitFuture =
Futures.transformAsync(readFuture, toasterData -> {
ToasterStatus toasterStatus = ToasterStatus.Up;
if (toasterData.isPresent()) {
- toasterStatus = toasterData.get().getToasterStatus();
+ toasterStatus = toasterData.orElseThrow().getToasterStatus();
}
LOG.debug("Read toaster status: {}", toasterStatus);
if (outOfBread()) {
LOG.debug("Toaster is out of bread");
- return Futures.immediateFailedCheckedFuture(
+ tx.cancel();
+ return Futures.immediateFailedFuture(
new TransactionCommitFailedException("", makeToasterOutOfBreadError()));
}
// to indicate we're going to make toast. This acts as a lock to prevent
// concurrent toasting.
tx.put(OPERATIONAL, TOASTER_IID, buildToaster(ToasterStatus.Down));
- return tx.submit();
+ return tx.commit();
}
LOG.debug("Oops - already making toast!");
// Return an error since we are already making toast. This will get
// propagated to the commitFuture below which will interpret the null
// TransactionStatus in the RpcResult as an error condition.
- return Futures.immediateFailedCheckedFuture(
+ tx.cancel();
+ return Futures.immediateFailedFuture(
new TransactionCommitFailedException("", makeToasterInUseError()));
}, MoreExecutors.directExecutor());
- Futures.addCallback(commitFuture, new FutureCallback<Void>() {
+ Futures.addCallback(commitFuture, new FutureCallback<CommitInfo>() {
@Override
- public void onSuccess(final Void result) {
+ public void onSuccess(final CommitInfo result) {
// OK to make toast
currentMakeToastTask.set(executor.submit(new MakeToastTask(input, futureResult)));
}
* Restocks the bread for the toaster, resets the toastsMade counter to 0, and sends a
* ToasterRestocked notification.
*/
- @Override
- public ListenableFuture<RpcResult<RestockToasterOutput>> restockToaster(final RestockToasterInput input) {
- LOG.info("restockToaster: " + input);
+ private ListenableFuture<RpcResult<RestockToasterOutput>> restockToaster(final RestockToasterInput input) {
+ LOG.info("restockToaster: {}", input);
- amountOfBreadInStock.set(input.getAmountOfBreadToStock());
+ amountOfBreadInStock.set(input.getAmountOfBreadToStock().toJava());
if (amountOfBreadInStock.get() > 0) {
ToasterRestocked reStockedNotification = new ToasterRestockedBuilder()
return toastsMade.get();
}
- private void setToasterStatusUp(final Function<Boolean, MakeToastOutput> resultCallback) {
+ private ListenableFuture<?> setToasterStatusUp(final Function<Boolean, MakeToastOutput> resultCallback) {
WriteTransaction tx = dataBroker.newWriteOnlyTransaction();
tx.put(OPERATIONAL,TOASTER_IID, buildToaster(ToasterStatus.Up));
- Futures.addCallback(tx.submit(), new FutureCallback<Void>() {
+ final var future = tx.commit();
+ Futures.addCallback(future, new FutureCallback<CommitInfo>() {
@Override
- public void onSuccess(final Void result) {
+ public void onSuccess(final CommitInfo result) {
LOG.info("Successfully set ToasterStatus to Up");
notifyCallback(true);
}
}
}
}, MoreExecutors.directExecutor());
+
+ return future;
}
private boolean outOfBread() {
public Void call() {
try {
// make toast just sleeps for n seconds per doneness level.
- Thread.sleep(OpendaylightToaster.this.darknessFactor.get() * toastRequest.getToasterDoneness());
+ Thread.sleep(darknessFactor.get()
+ * toastRequest.getToasterDoneness().toJava());
} catch (InterruptedException e) {
LOG.info("Interrupted while making the toast");