/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.ignite.examples.servicegrid;

import java.util.Collection;
import org.apache.ignite.Ignite;
import org.apache.ignite.IgniteServices;
import org.apache.ignite.Ignition;
import org.apache.ignite.examples.ExampleNodeStartup;
import org.apache.ignite.examples.ExamplesUtils;
import org.apache.ignite.lang.IgniteCallable;
import org.apache.ignite.resources.ServiceResource;

/**
 * Example that demonstrates how to deploy distributed services in Ignite.
 * Distributed services are especially useful when deploying singletons on the ignite,
 * be that cluster-singleton, or per-node-singleton, etc...
 * <p>
 * To start remote nodes, you must run {@link ExampleNodeStartup} in another JVM
 * which will start node with {@code examples/config/example-ignite.xml} configuration.
 * <p>
 * NOTE:<br/>
 * Starting {@code ignite.sh} directly will not work, as distributed services
 * cannot be peer-deployed and classes must be on the classpath for every node.
 */
public class ServicesExample {
    /**
     * Executes example.
     *
     * @param args Command line arguments, none required.
     * @throws Exception If example execution failed.
     */
    public static void main(String[] args) throws Exception {
        // Mark this node as client node.
        Ignition.setClientMode(true);

        try (Ignite ignite = Ignition.start("examples/config/example-ignite.xml")) {
            if (!ExamplesUtils.hasServerNodes(ignite))
                return;

            // Deploy services only on server nodes.
            IgniteServices svcs = ignite.services(ignite.cluster().forServers());

            try {
                // Deploy cluster singleton.
                svcs.deployClusterSingleton("myClusterSingletonService", new SimpleMapServiceImpl());

                // Deploy node singleton.
                svcs.deployNodeSingleton("myNodeSingletonService", new SimpleMapServiceImpl());

                // Deploy 2 instances, regardless of number nodes.
                svcs.deployMultiple("myMultiService",
                    new SimpleMapServiceImpl(),
                    2 /*total number*/,
                    0 /*0 for unlimited*/);

                // Example for using a service proxy
                // to access a remotely deployed service.
                serviceProxyExample(ignite);

                // Example for auto-injecting service proxy
                // into remote closure execution.
                serviceInjectionExample(ignite);
            }
            finally {
                // Undeploy all services.
                ignite.services().cancelAll();
            }
        }
    }

    /**
     * Simple example to demonstrate service proxy invocation of a remotely deployed service.
     *
     * @param ignite Ignite instance.
     * @throws Exception If failed.
     */
    private static void serviceProxyExample(Ignite ignite) throws Exception {
        System.out.println(">>>");
        System.out.println(">>> Starting service proxy example.");
        System.out.println(">>>");

        // Get a sticky proxy for node-singleton map service.
        SimpleMapService<Integer, String> mapSvc = ignite.services().serviceProxy("myNodeSingletonService",
            SimpleMapService.class,
            true);

        int cnt = 10;

        // Each service invocation will go over a proxy to some remote node.
        // Since service proxy is sticky, we will always be contacting the same remote node.
        for (int i = 0; i < cnt; i++)
            mapSvc.put(i, Integer.toString(i));

        // Get size from remotely deployed service instance.
        int mapSize = mapSvc.size();

        System.out.println("Map service size: " + mapSize);

        if (mapSize != cnt)
            throw new Exception("Invalid map size [expected=" + cnt + ", actual=" + mapSize + ']');
    }

    /**
     * Simple example to demonstrate how to inject service proxy into distributed closures.
     *
     * @param ignite Ignite instance.
     * @throws Exception If failed.
     */
    private static void serviceInjectionExample(Ignite ignite) throws Exception {
        System.out.println(">>>");
        System.out.println(">>> Starting service injection example.");
        System.out.println(">>>");

        // Get a sticky proxy for cluster-singleton map service.
        SimpleMapService<Integer, String> mapSvc = ignite.services().serviceProxy("myClusterSingletonService",
            SimpleMapService.class,
            true);

        int cnt = 10;

        // Each service invocation will go over a proxy to the remote cluster-singleton instance.
        for (int i = 0; i < cnt; i++)
            mapSvc.put(i, Integer.toString(i));

        // Broadcast closure to every node.
        final Collection<Integer> mapSizes = ignite.compute().broadcast(new SimpleClosure());

        System.out.println("Closure execution result: " + mapSizes);

        // Since we invoked the same cluster-singleton service instance
        // from all the remote closure executions, they should all return
        // the same size equal to 'cnt' value.
        for (int mapSize : mapSizes)
            if (mapSize != cnt)
                throw new Exception("Invalid map size [expected=" + cnt + ", actual=" + mapSize + ']');
    }

    /**
     * Simple closure to demonstrate auto-injection of the service proxy.
     */
    private static class SimpleClosure implements IgniteCallable<Integer> {
        // Auto-inject service proxy.
        @ServiceResource(serviceName = "myClusterSingletonService", proxyInterface = SimpleMapService.class)
        private transient SimpleMapService mapSvc;

        /** {@inheritDoc} */
        @Override public Integer call() throws Exception {
            int mapSize = mapSvc.size();

            System.out.println("Executing closure [mapSize=" + mapSize + ']');

            return mapSize;
        }
    }
}
