/*
 * Copyright (C) 2013 4th Line GmbH, Switzerland
 *
 * The contents of this file are subject to the terms of either the GNU
 * Lesser General Public License Version 2 or later ("LGPL") or the
 * Common Development and Distribution License Version 1 or later
 * ("CDDL") (collectively, the "License"). You may not use this file
 * except in compliance with the License. See LICENSE.txt for more
 * information.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 */
package example.controlpoint;

import example.binarylight.BinaryLightSampleData;
import org.fourthline.cling.binding.LocalServiceBinder;
import org.fourthline.cling.binding.annotations.AnnotationLocalServiceBinder;
import org.fourthline.cling.controlpoint.ActionCallback;
import org.fourthline.cling.mock.MockUpnpService;
import org.fourthline.cling.model.DefaultServiceManager;
import org.fourthline.cling.model.action.ActionCancelledException;
import org.fourthline.cling.model.action.ActionInvocation;
import org.fourthline.cling.model.message.UpnpResponse;
import org.fourthline.cling.model.meta.Action;
import org.fourthline.cling.model.meta.LocalDevice;
import org.fourthline.cling.model.meta.LocalService;
import org.fourthline.cling.model.types.UDAServiceId;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

import java.util.concurrent.Future;

import static org.testng.Assert.assertEquals;

/**
 * Cancelling an action invocation
 * <p>
 * You call actions of services with the <code>ControlPoint#execute(myCallback)</code> method. So far you probably
 * haven't considered the optional return value of this method, a <code>Future</code> which can be used to cancel
 * the invocation:
 * </p>
 * <p/>
 * <a class="citation"
 * href="javacode://this#invokeActions(LocalDevice)"
 * style="include: EXECUTE_CANCEL;"/>
 * <p/>
 * <p>
 * Here we are calling the <code>SetTarget</code> action of a <em>SwitchPower:1</em> service, and after waiting a
 * (short) time period, we cancel the request. What happens now depends on the invocation and what service you are
 * calling. If it's a local service, and no network access is needed, the thread calling the local service (method)
 * will simply be interrupted. If you are calling a remote service, Cling will abort the HTTP request to the server.
 * </p>
 * <p>
 * Most likely you want to handle this explicit cancellation of an action call in your action invocation callback, so
 * you can present the result to your user. Override the <code>failure()</code> method to handle the interruption:
 * </p>
 * <p/>
 * <a class="citation" href="javacode://this#invokeActions(LocalDevice)"
 *    id="ActionCancellationTest_invokeActions2"
 *    style="include: CALLBACK; exclude: TEST;"/>
 * <p>
 * A special exception type is provided if the action call was indeed cancelled.
 * </p>
 * <p>
 * Several important issues have to be considered when you try to cancel action calls to remote services:
 * </p>
 * <p>
 * There is no guarantee that the server will actually stop processing your request. When the client closes the
 * connection, the server doesn't get notified. The server will complete the action call and only fail when trying to
 * return the response to the client on the closed connection. Cling's server transports offer a special heartbeat
 * feature for checking client connections, we'll discuss this feature later in this chapter. Other UPnP servers will
 * most likely not detect a dropped client connection immediately.
 * </p>
 * <p>
 * Not all HTTP client transports in Cling support interruption of requests:
 * </p>
 * <table class="infotable fullwidth" border="1">
 * <thead>
 * <tr>
 * <th>Transport</th>
 * <th class="thirdwidth">Supports Interruption?</th>
 * </tr>
 * </thead>
 * <tbody>
 * <tr>
 * <td class="nowrap">
 * <code>org.fourthline.cling.transport.impl.StreamClientImpl (default)</code>
 * </td>
 * <td>NO</td>
 * </tr>
 * <tr>
 * <td class="nowrap">
 * <code>org.fourthline.cling.transport.impl.apache.StreamClientImpl</code>
 * </td>
 * <td>YES</td>
 * </tr>
 * <tr>
 * <td class="nowrap">
 * <code>org.fourthline.cling.transport.impl.jetty.StreamClientImpl (default on Android)</code>
 * </td>
 * <td>YES</td>
 * </tr>
 * </tbody>
 * </table>
 * <p>
 * Transports which do not support cancellation won't produce an error when you abort an action invocation, they
 * silently ignore the interruption and continue waiting for the server to respond.
 * </p>
 */
public class ActionCancellationTest {

    protected LocalService bindService(Class<?> clazz) throws Exception {
        LocalServiceBinder binder = new AnnotationLocalServiceBinder();
        LocalService svc = binder.read(clazz);
        svc.setManager(
            new DefaultServiceManager(svc, clazz)
        );
        return svc;
    }

    @DataProvider(name = "devices")
    public Object[][] getDevices() throws Exception {
        return new LocalDevice[][]{
            {BinaryLightSampleData.createDevice(bindService(SwitchPowerWithInterruption.class))},
        };
    }

    @Test(dataProvider = "devices")
    public void invokeActions(LocalDevice device) throws Exception {
        final boolean[] tests = new boolean[1];

        MockUpnpService upnpService = new MockUpnpService(false, false, true);
        LocalService service = device.findService(new UDAServiceId("SwitchPower"));
        Action action = service.getAction("SetTarget");

        ActionInvocation setTargetInvocation = new ActionInvocation(action);
        setTargetInvocation.setInput("NewTargetValue", true);

        // DOC:CALLBACK
        ActionCallback setTargetCallback = new ActionCallback(setTargetInvocation) {

            @Override
            public void success(ActionInvocation invocation) {
                // Will not be called if invocation has been cancelled
            }

            @Override
            public void failure(ActionInvocation invocation,
                                UpnpResponse operation,
                                String defaultMsg) {
                if (invocation.getFailure() instanceof ActionCancelledException) {
                    // Handle the cancellation here...
                    tests[0] = true; // DOC:TEST
                }
            }
        };
        // DOC:CALLBACK

        // DOC:EXECUTE_CANCEL
        Future future = upnpService.getControlPoint().execute(setTargetCallback);
        Thread.sleep(250); // DOC:WAIT_FOR_THREAD
        future.cancel(true);
        // DOC:EXECUTE_CANCEL

        Thread.sleep(250);
        for (boolean test : tests) {
            assertEquals(test, true);
        }
    }
}
