All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.lightapi.portal.HybridCommandClient Maven / Gradle / Ivy

package net.lightapi.portal;

import com.networknt.client.Http2Client;
import com.networknt.cluster.Cluster;
import com.networknt.config.Config;
import com.networknt.config.JsonMapper;
import com.networknt.monad.Failure;
import com.networknt.monad.Result;
import com.networknt.monad.Success;
import com.networknt.security.JwtVerifier;
import com.networknt.server.Server;
import com.networknt.service.SingletonServiceFactory;
import com.networknt.status.Status;
import io.undertow.UndertowOptions;
import io.undertow.client.ClientConnection;
import io.undertow.client.ClientRequest;
import io.undertow.client.ClientResponse;
import io.undertow.util.Headers;
import io.undertow.util.Methods;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xnio.OptionMap;

import java.net.URI;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference;

/**
 * This is a client utility that is shared by all command side services to update data from
 * the command side. It creates a HTTP/2 connection to the command side and cache that connection
 * until the connection is closed.
 *
 * @author Steve Hu
 */
public class HybridCommandClient {
    static final Logger logger = LoggerFactory.getLogger(HybridCommandClient.class);
    public static final PortalClientConfig config = (PortalClientConfig) Config.getInstance().getJsonObjectConfig(PortalClientConfig.CONFIG_NAME, PortalClientConfig.class);
    static String tag = Server.getServerConfig().getEnvironment();
    // Get the singleton Cluster instance
    static Cluster cluster = SingletonServiceFactory.getBean(Cluster.class);
    // Get the singleton Http2Client instance
    static Http2Client client = Http2Client.getInstance();
    static ClientConnection connection;
    {
        if (!config.isPortalByServiceUrl()) {
            String host = cluster.serviceToUrl("https", config.getPortalCommandServiceId(), tag, null);
            try {
                connection = client.connect(new URI(host), Http2Client.WORKER, Http2Client.SSL, Http2Client.BUFFER_POOL, OptionMap.create(UndertowOptions.ENABLE_HTTP2, true)).get();
            } catch (Exception e) {
                logger.error("Exception:", e);
            }
        }

    }
    static final String GENERIC_EXCEPTION = "ERR10014";
    static final String ESTABLISH_CONNECTION_ERROR = "ERR10053";
    static Map connCache = new ConcurrentHashMap<>();

    public static Result callCommandWithToken(String command, String token) {
        try {
            if(connection == null || !connection.isOpen()) {
                // The connection is close or not created.
                String host = cluster.serviceToUrl("https", config.getPortalCommandServiceId(), tag, null);
                connection = client.connect(new URI(host), Http2Client.WORKER, Http2Client.SSL, Http2Client.BUFFER_POOL, OptionMap.create(UndertowOptions.ENABLE_HTTP2, true)).get();
            }
            return callCommandWithToken(connection, command, token);
        } catch (Exception e) {
            logger.error("Exception:", e);
            Status status = new Status(ESTABLISH_CONNECTION_ERROR, e.getMessage());
            return Failure.of(status);
        }
    }

    public static Result callCommandWithToken(String command, String token, String url) {
        try {
            ClientConnection conn = connCache.get(url);
            if(conn == null || !conn.isOpen()) {
                conn = client.connect(new URI(url), Http2Client.WORKER, Http2Client.SSL, Http2Client.BUFFER_POOL, OptionMap.create(UndertowOptions.ENABLE_HTTP2, true)).get();
                connCache.put(url, conn);
            }
            return callCommandWithToken(conn, command, token);
        } catch (Exception e) {
            logger.error("Exception:", e);
            Status status = new Status(ESTABLISH_CONNECTION_ERROR, e.getMessage());
            return Failure.of(status);
        }
    }

    public  static Result callCommandWithToken(ClientConnection connection, String command, String token) {
        Result result = null;
        try {

            // Create one CountDownLatch that will be reset in the callback function
            final CountDownLatch latch = new CountDownLatch(1);
            // Create an AtomicReference object to receive ClientResponse from callback function
            final AtomicReference reference = new AtomicReference<>();
            String message = "/portal/command?cmd=" + URLEncoder.encode(command, "UTF-8");
            final ClientRequest request = new ClientRequest().setMethod(Methods.GET).setPath(message);
            request.getRequestHeaders().put(Headers.AUTHORIZATION, "Bearer " + token);
            request.getRequestHeaders().put(Headers.HOST, "localhost");
            connection.sendRequest(request, client.createClientCallback(reference, latch));
            latch.await();
            int statusCode = reference.get().getResponseCode();
            String body = reference.get().getAttachment(Http2Client.RESPONSE_BODY);
            if(statusCode != 200) {
                Status status = Config.getInstance().getMapper().readValue(body, Status.class);
                result = Failure.of(status);
            } else result = Success.of(body);
        } catch (Exception e) {
            logger.error("Exception:", e);
            Status status = new Status(GENERIC_EXCEPTION, e.getMessage());
            result = Failure.of(status);
        }
        return result;
    }


    /**
     * Create a refresh token from the oauth-kafka. The token will be a client credential token so that there is no user_id
     * in the JWT to bypass the match verification. This is an internal method that is called between oauth and portal
     * services and a client credential token must be provided.
     *
     * @param refreshTokenMap map contains all the properties including refreshToken
     * @param token a client credential JWT token
     * @return Result of refreshToken
     */
    public static Result createRefreshToken(Map refreshTokenMap, String token) {
        Map commandMap = new HashMap<>();
        commandMap.put("host", "lightapi.net");
        commandMap.put("service", "market");
        commandMap.put("action", "createRefreshToken");
        commandMap.put("version", "0.1.0");
        commandMap.put("data", refreshTokenMap);
        String command = JsonMapper.toJson(commandMap);
        if(logger.isTraceEnabled()) logger.trace("command = " + command);
        if (config.isPortalByServiceUrl()) {
            return callCommandWithToken(command, token, config.getPortalCommandServiceUrl());
        } else {
            return callCommandWithToken(command, token);
        }
    }

    /**
     * Create Auth code from oauth-kafka.
     * @param codeMap auth code map
     * @param token access token
     * @return Result of authCode
     */
    public static Result createAuthCode(Map codeMap, String token) {
        Map commandMap = new HashMap<>();
        commandMap.put("host", "lightapi.net");
        commandMap.put("service", "market");
        commandMap.put("action", "createAuthCode");
        commandMap.put("version", "0.1.0");
        commandMap.put("data", codeMap);
        if (config.isPortalByServiceUrl()) {
            return callCommandWithToken(JsonMapper.toJson(commandMap), token, config.getPortalCommandServiceUrl());
        } else {
            return callCommandWithToken(JsonMapper.toJson(commandMap), token);
        }

    }

    /**
     * Delete Auth code from oauth-kafka
     * @param host host name
     * @param authCode auth code
     * @param token access token
     * @return Result of authCode
     */
    public static Result deleteAuthCode(String host, String authCode, String token) {
        final String command = String.format("{\"host\":\"lightapi.net\",\"service\":\"market\",\"action\":\"deleteAuthCode\",\"version\":\"0.1.0\",\"data\":{\"host\":\"%s\",\"authCode\":\"%s\"}}", host, authCode);
        if (config.isPortalByServiceUrl()) {
            return callCommandWithToken(command, token, config.getPortalCommandServiceUrl());
        } else {
            return callCommandWithToken(command, token);
        }
    }

    /**
     * Create a reference token from the oauth-kafka. The token will be the mapping between a uuid to a JWT token and
     * it is exchanged on the light-router.
     *
     * @param refTokenMap map contains all the properties including refreshToken
     * @param token a client credential JWT token
     * @return Result of refreshToken
     */
    public static Result createRefToken(Map refTokenMap, String token) {
        Map commandMap = new HashMap<>();
        commandMap.put("host", "lightapi.net");
        commandMap.put("service", "market");
        commandMap.put("action", "createRefToken");
        commandMap.put("version", "0.1.0");
        commandMap.put("data", refTokenMap);
        String command = JsonMapper.toJson(commandMap);
        if(logger.isTraceEnabled()) logger.trace("command = " + command);
        if (config.isPortalByServiceUrl()) {
            return callCommandWithToken(command, token, config.getPortalCommandServiceUrl());
        } else {
            return callCommandWithToken(command, token);
        }
    }

    /**
     * Create a sociate user with bootstrap token from light-spa-4j statelessAuthHandler.
     *
     * @param userMap map contains all the properties for the social user
     * @param token a client credential JWT token
     * @return Result of refreshToken
     */
    public static Result createSocialUser(Map userMap, String token) {
        Map commandMap = new HashMap<>();
        commandMap.put("host", "lightapi.net");
        commandMap.put("service", "user");
        commandMap.put("action", "createSocialUser");
        commandMap.put("version", "0.1.0");
        commandMap.put("data", userMap);
        String command = JsonMapper.toJson(commandMap);
        if(logger.isTraceEnabled()) logger.trace("command = " + command);
        if (config.isPortalByServiceUrl()) {
            return callCommandWithToken(command, token, config.getPortalCommandServiceUrl());
        } else {
            return callCommandWithToken(command, token);
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy