org.opendaylight.oven.impl.OvenProvider.java Source code

Java tutorial

Introduction

Here is the source code for org.opendaylight.oven.impl.OvenProvider.java

Source

/*
 * Copyright(c) Yoyodyne, 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.oven.impl;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicInteger;
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.DataChangeListener;
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.AsyncDataChangeEvent;
import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
import org.opendaylight.controller.md.sal.common.api.data.OptimisticLockFailedException;
import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
import org.opendaylight.controller.sal.binding.api.BindingAwareBroker.ProviderContext;
import org.opendaylight.controller.sal.binding.api.BindingAwareBroker.RpcRegistration;
import org.opendaylight.controller.sal.binding.api.BindingAwareProvider;
import org.opendaylight.controller.sal.binding.api.NotificationProviderService;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.oven.impl.rev160113.OvenRuntimeMXBean;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.oven.rev160113.CookFoodInput;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.oven.rev160113.DisplayString;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.oven.rev160113.KitchenOutOfFoodBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.oven.rev160113.KitchenRestocked;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.oven.rev160113.KitchenRestockedBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.oven.rev160113.OvenParams;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.oven.rev160113.OvenParams.OvenStatus;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.oven.rev160113.OvenParamsBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.oven.rev160113.OvenService;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.oven.rev160113.RestockFoodInput;
import org.opendaylight.yangtools.concepts.ListenerRegistration;
import org.opendaylight.yangtools.yang.binding.DataObject;
import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.util.concurrent.AsyncFunction;
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.SettableFuture;

public class OvenProvider
        implements BindingAwareProvider, AutoCloseable, OvenService, OvenRuntimeMXBean, DataChangeListener {

    private static final Logger LOG = LoggerFactory.getLogger(OvenProvider.class);

    public static final InstanceIdentifier<OvenParams> OVEN_IID = InstanceIdentifier.builder(OvenParams.class)
            .build();
    public static final DisplayString OVEN_MANUFACTURER = new DisplayString("Miele");
    public static final DisplayString OVEN_MODEL = new DisplayString("PureLine M Touch");

    private NotificationProviderService notificationProvider;
    private RpcRegistration<OvenService> rpcReg;
    private ListenerRegistration<DataChangeListener> dcReg;
    private DataBroker db;

    private final ExecutorService executor;

    private volatile OvenStatus status = OvenStatus.Waiting;
    //private final AtomicInteger program = new AtomicInteger(0);
    private final AtomicInteger timer = new AtomicInteger(1);
    public AtomicInteger thermostat = new AtomicInteger(1);
    private final AtomicLong mealCooked = new AtomicLong(0);
    private final AtomicLong numberOfMealAvailable = new AtomicLong(100);

    // The following holds the Future for the current make toast task.
    // This is used to cancel the current toast.
    private final AtomicReference<Future<?>> currentCookingMealTask = new AtomicReference<>();

    public OvenProvider() {
        executor = Executors.newFixedThreadPool(1);
    }

    @Override
    public void onSessionInitiated(ProviderContext session) {
        LOG.info("OvenProvider Session Initiated");
        this.db = session.getSALService(DataBroker.class);
        this.notificationProvider = session.getSALService(NotificationProviderService.class);

        // Register the RPC Service
        //rpcReg = session.addRpcImplementation(OvenService.class, this);

        // Register the DataChangeListener for Toaster's configuration subtree
        //dcReg = db.registerDataChangeListener(LogicalDatastoreType.CONFIGURATION, OvenProvider.OVEN_IID, this, DataChangeScope.SUBTREE);

        if (db != null) {
            syncOvenParamWithDataStore(LogicalDatastoreType.OPERATIONAL, OVEN_IID, buildOvenParams(status));
        } else {
            LOG.warn("No SALService DataBroker got.");
        }
        // syncOvenParamWithDataStore(LogicalDatastoreType.CONFIGURATION,OVEN_IID,buildOvenParams(status));
    }

    /**
     * This method is used to notify the OvenProvider when a data change
     * is made to the configuration.
     *
     * Effectively, the camera subtree node is modified through restconf
     * and the onDataChanged is triggered. We check if the changed dataObject is
     * from the camera subtree, if so we get the value of the thermostatFactor.
     *
     * If the thermostatFactor from the node is not null, then we change the thermostatFactor of
     * the OvenProvider with the subtree thermostatFactor.
     */
    @Override
    public void onDataChanged(AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> change) {
        final DataObject dataObject = change.getUpdatedSubtree();
        if (dataObject instanceof OvenParams) {
            final Integer thermostatFactor = ((OvenParams) dataObject).getThermostat();
            if (thermostatFactor != null) {
                thermostat.set(thermostatFactor);
                LOG.info("@thermostatFactor:" + thermostatFactor);
            }
        }
    }

    /*
     * Default function to build a oven returns a OvenParams object using
     * the OvenParamsBuilder().build()
     */
    private OvenParams buildOvenParams(OvenStatus ovenStatus) {
        return new OvenParamsBuilder().setOvenManufacturer(OVEN_MANUFACTURER).setOvenModelNumber(OVEN_MODEL)
                .setOvenStatus(ovenStatus).build();
    }

    @SuppressWarnings({ "rawtypes", "unchecked" })
    private void syncOvenParamWithDataStore(final LogicalDatastoreType store, InstanceIdentifier iid,
            final DataObject oven) {
        final WriteTransaction transaction = db.newWriteOnlyTransaction();
        transaction.put(store, iid, oven);
        //Perform the tx.submit asynchronously
        Futures.addCallback(transaction.submit(), new FutureCallback<Void>() {
            @Override
            public void onSuccess(final Void result) {
                LOG.info("SyncStore {} with object {} succeeded", store, oven);
            }

            @Override
            public void onFailure(final Throwable throwable) {
                LOG.error("SyncStore {} with object {} failed", store, oven);
            }
        });
    }

    @Override
    public void close() throws Exception {
        executor.shutdown(); // part 2
        if (db != null) {
            final WriteTransaction transaction = db.newWriteOnlyTransaction();
            transaction.delete(LogicalDatastoreType.OPERATIONAL, OVEN_IID);
            Futures.addCallback(transaction.submit(), new FutureCallback<Void>() {
                @Override
                public void onFailure(final Throwable t) {
                    LOG.error("Failed to delete oven" + t);
                }

                @Override
                public void onSuccess(final Void result) {
                    LOG.debug("Successfully deleted oven" + result);
                }
            });
        }
        if (rpcReg != null)
            rpcReg.close();

        if (dcReg != null)
            dcReg.close();

        LOG.info("OvenProvider closed");
    }

    @Override
    public Long getMealCooked() {
        return mealCooked.get();
    }

    @Override
    public void clearMealCookedRpc() {
        LOG.info("In clearMealCookedRpc");
        mealCooked.set(0);

    }

    @Override
    public Future<RpcResult<Void>> cancelProgram() {
        LOG.info("cancelProgram");
        final Future<?> current = currentCookingMealTask.getAndSet(null);
        if (current != null) {
            current.cancel(true);
        }
        // Always return success from the cancel toast call.
        return Futures.immediateFuture(RpcResultBuilder.<Void>success().build());
    }

    @Override
    public Future<RpcResult<Void>> cookFood(CookFoodInput input) {
        //LOG.info("In cookFood()");
        LOG.info("cookFood: {}", input);
        final SettableFuture<RpcResult<Void>> futureResult = SettableFuture.create();
        checkStatusAndCookFood(input, futureResult, 2);
        LOG.info("cookFood returning...");
        return futureResult;
    }

    /**
     * Read the OvenStatus and, if currently Waiting, try to write the status to Preheating. 
     * If that succeeds, then we can proceed to cook the food. 
     *
     * @param input
     * @param futureResult
     * @param tries
     */
    private void checkStatusAndCookFood(final CookFoodInput input,
            final SettableFuture<RpcResult<Void>> futureResult, final int tries) {
        /*
         * We create a ReadWriteTransaction by using the databroker. Then, we
         * read the status of the oven with getOvenStatus() using the
         * databroker again. Once we have the status, we analyze it and then
         * databroker submit function is called to effectively change the oven
         * status. This all affects the MD-SAL tree, more specifically the part
         * of the tree that contain the oven (the nodes).
         */
        LOG.info("In checkStatusAndCookFood()");
        final ReadWriteTransaction tx = db.newReadWriteTransaction();
        final ListenableFuture<Optional<OvenParams>> readFuture = tx.read(LogicalDatastoreType.OPERATIONAL,
                OVEN_IID);
        final ListenableFuture<Void> commitFuture = Futures.transform(readFuture,
                new AsyncFunction<Optional<OvenParams>, Void>() {

                    @Override
                    public ListenableFuture<Void> apply(Optional<OvenParams> ovenParamsData) throws Exception {
                        if (ovenParamsData.isPresent()) {
                            status = ovenParamsData.get().getOvenStatus();
                        } else {
                            throw new Exception("Error reading OvenParams.status data from the store.");
                        }
                        LOG.info("Read oven status: {}", status);

                        if (status == OvenStatus.Waiting) {
                            //Check if numberOfMealAvailable is not 0, if yes Notify outOfStock
                            if (numberOfMealAvailable.get() == 0) {
                                LOG.info("No more meal availble to cook");
                                notificationProvider.publish(new KitchenOutOfFoodBuilder().build());
                                return Futures.immediateFailedCheckedFuture(
                                        new TransactionCommitFailedException("", cookNoMoreMealError()));
                            }

                            LOG.info("Setting Camera status to Preheating");
                            // We're not currently cooking food - we try to update the status to On
                            // to indicate we're going to cook food. This acts as a lock to prevent
                            // concurrent cooking.
                            tx.put(LogicalDatastoreType.OPERATIONAL, OVEN_IID,
                                    buildOvenParams(OvenStatus.Preheating));
                            return tx.submit();
                        }
                        LOG.info("The oven is actually on use, cancel actual program before.");
                        // Return an error since we are already cooking food. This will get
                        // propagated to the commitFuture below which will interpret the null
                        // TransactionStatus in the RpcResult as an error condition.
                        return Futures.immediateFailedCheckedFuture(
                                new TransactionCommitFailedException("", cookOvenInUseError()));
                    }

                    private RpcError cookNoMoreMealError() {
                        return RpcResultBuilder.newError(ErrorType.APPLICATION, "resource-denied",
                                "No more food available to cook", "out-of-stock", null, null);
                    }
                });
        Futures.addCallback(commitFuture, new FutureCallback<Void>() {
            @Override
            public void onFailure(Throwable t) {
                if (t instanceof OptimisticLockFailedException) {
                    // Another thread is likely trying to cook food simultaneously and updated the
                    // status before us. Try reading the status again - if another cookFood is
                    // now in progress, we should get OvenStatus.Waiting and fail.
                    if ((tries - 1) > 0) {
                        LOG.info("Got OptimisticLockFailedException - trying again");
                        checkStatusAndCookFood(input, futureResult, tries - 1);
                    } else {
                        futureResult.set(RpcResultBuilder.<Void>failed()
                                .withError(ErrorType.APPLICATION, t.getMessage()).build());
                    }
                } else {
                    LOG.info("Failed to commit Oven status", t);
                    // Probably already cooking.
                    futureResult.set(RpcResultBuilder.<Void>failed()
                            .withRpcErrors(((TransactionCommitFailedException) t).getErrorList()).build());
                }
            }

            @Override
            public void onSuccess(Void result) {
                // OK to cook
                currentCookingMealTask.set(executor.submit(new CookMealTask(input, futureResult)));

            }

        });
    }

    private RpcError cookOvenInUseError() {
        return RpcResultBuilder.newWarning(ErrorType.APPLICATION, "in use", "Oven is busy (in-use)", null, null,
                null);
    }

    private class CookMealTask implements Callable<Void> {
        final CookFoodInput mealRequest;
        final SettableFuture<RpcResult<Void>> futureResult;

        public CookMealTask(CookFoodInput mealRequest, SettableFuture<RpcResult<Void>> futureResult) {
            this.mealRequest = mealRequest;
            this.futureResult = futureResult;
        }

        @Override
        public Void call() throws Exception {
            // cookMeal sleeps for n seconds for every 1 unit change of
            // timer level
            LOG.info("Inside CookMealTask's call() method");
            LOG.info("Temperature is:" + mealRequest.getTemperature());
            LOG.info("Timer is:" + mealRequest.getTimer());
            if (mealRequest.getProgram() != null)
                LOG.info("Program is:" + mealRequest.getProgram().getName());
            try {
                final int timer = OvenProvider.this.timer.get();

                //Get the thermostat constant if present
                int tF = 1;
                if (OvenProvider.this.thermostat != null)
                    tF = OvenProvider.this.thermostat.get();
                setOvenStatusUp(new Function<Boolean, Void>() {
                    @Override
                    public Void apply(Boolean result) {
                        currentCookingMealTask.set(null);
                        LOG.info("Oven Ready @setOvenStatusCooking.apply()");
                        futureResult.set(RpcResultBuilder.<Void>success().build());
                        return null;
                    }
                });
                Thread.sleep(Math.abs(tF * (timer)));
            } catch (final InterruptedException e) {
                LOG.info("Interrupted while cooking food.");
            }
            mealCooked.incrementAndGet();
            numberOfMealAvailable.getAndDecrement();
            // need to redo this even though it is already handled in checkStatusAndCookFood
            // before it gets here. Since if the first time getAndDecrement() on numberOfMeals
            // available gets called and is 0 then publish Notif
            if (numberOfMealAvailable.get() == 0) {
                LOG.info("Oven out of food to cook meal!");
                notificationProvider.publish(new KitchenOutOfFoodBuilder().build());
            }

            // Set the Oven status back to waiting - this essentially unloads the oven.
            // We can't clear the current cookMealTask nor set the Future result until the
            // update has been committed so we pass a callback to be notified on completion.

            //To-Do:  Instead of Sleep here, add a callback to syncOvenParamWithDataStore
            //to return a boolean to let you know about the completion of cookFood,
            //if true then only reset the OvenStatus to waiting.
            Thread.sleep(10);
            setOvenStatusWaiting(new Function<Boolean, Void>() {
                @Override
                public Void apply(Boolean result) {
                    currentCookingMealTask.set(null);
                    LOG.info("Oven Ready @setOvenStatusWaiting.apply()");
                    futureResult.set(RpcResultBuilder.<Void>success().build());
                    return null;
                }
            });
            return null;
        }
    }

    private void setOvenStatusWaiting(final Function<Boolean, Void> resultCallback) {
        final WriteTransaction tx = db.newWriteOnlyTransaction();
        tx.put(LogicalDatastoreType.OPERATIONAL, OVEN_IID, buildOvenParams(OvenStatus.Waiting));

        Futures.addCallback(tx.submit(), new FutureCallback<Void>() {

            @Override
            public void onFailure(Throwable t) {
                LOG.error("Failed to reset the Oven Status to Waiting");
                notifyCallback(false);
            }

            @Override
            public void onSuccess(Void arg0) {
                LOG.info("Reset Oven Status to Waiting");
                notifyCallback(true);
            }

            private void notifyCallback(boolean b) {
                if (resultCallback != null) {
                    resultCallback.apply(b);
                }
            }
        });
    }

    private void setOvenStatusUp(final Function<Boolean, Void> resultCallback) {

        final WriteTransaction tx = db.newWriteOnlyTransaction();
        tx.put(LogicalDatastoreType.OPERATIONAL, OVEN_IID, buildOvenParams(OvenStatus.Cooking));

        Futures.addCallback(tx.submit(), new FutureCallback<Void>() {
            @Override
            public void onSuccess(final Void result) {
                notifyCallback(true);
            }

            @Override
            public void onFailure(final Throwable t) {
                // We shouldn't get an OptimisticLockFailedException (or any ex) as no
                // other component should be updating the operational state.
                LOG.error("Failed to update oven status", t);

                notifyCallback(false);
            }

            void notifyCallback(final boolean result) {
                if (resultCallback != null) {
                    resultCallback.apply(result);
                }
            }
        });
    }

    @Override
    public Future<RpcResult<Void>> listProgram() {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public Future<RpcResult<Void>> restockFood(RestockFoodInput input) {
        LOG.info("currentNumberOfMeal:" + numberOfMealAvailable.get() + " | RestockFoodInput:" + input);
        final Long currentNumberOfMeal = numberOfMealAvailable.get();
        numberOfMealAvailable.set(currentNumberOfMeal + input.getAmountOfFoodToStock());
        if (input.getAmountOfFoodToStock() > 0) {
            final KitchenRestocked kitchenRestockedNotif = new KitchenRestockedBuilder()
                    .setNumberOfMeal(input.getAmountOfFoodToStock()).build();
            notificationProvider.publish(kitchenRestockedNotif);
        }

        return Futures.immediateFuture(RpcResultBuilder.<Void>success().build());
    }

}