/*
 *   Copyright (c) Motadata 2025. All rights reserved.
 *
 *   This source code is the property of Motadata and constitutes
 *   proprietary and confidential information. Unauthorized copying, distribution,
 *   modification, or use of this file, via any medium, is strictly prohibited
 *   unless prior written permission is obtained from Motadata.
 *
 *   Unauthorized access or use of this software may result in legal action
 *   and/or prosecution to the fullest extent of the law.
 *
 *   This software is provided "AS IS," without warranties of any kind, express
 *   or implied, including but not limited to implied warranties of
 *   merchantability or fitness for a particular purpose. In no event shall
 *   Motadata be held liable for any damages arising from the use
 *   of this software.
 *
 *   For inquiries, contact: engg@motadata.com
 *
 */

import javax.management.ObjectName;

import javax.management.*;

import javax.management.remote.*;

import javax.naming.Context;

import java.io.IOException;

import java.lang.management.MemoryPoolMXBean;

import java.lang.management.MemoryType;

import java.text.SimpleDateFormat;

import java.util.*;

import javax.management.openmbean.CompositeData;

import org.json.JSONArray;

import org.json.JSONObject;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;



public class WebLogicServer
{

    private static MBeanServerConnection connection = (MBeanServerConnection) null;

    private static JMXConnector connector = (JMXConnector) null;

    private static final ObjectName domainRuntimeService;

    private static final List excludedApps = List.of("uddi", "uddiexplorer", "bea_wls9_async_response", "bea_wls_diagnostics", "bea_wls_management_internal2", "bea_wls_deployment_internal", "wls-wsat");

    private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("MMM dd HH:mm:ss:SSS");

    public static final String SYSTEM_SENT_BYTES = "weblogic.sent.bytes";

    public static final String SYSTEM_RECEIVED_BYTES = "weblogic.received.bytes";

    public static final String SYSTEM_SENT_MESSAGES = "weblogic.sent.messages";

    public static final String SYSTEM_RECEIVED_MESSAGES = "weblogic.received.messages";

    public static final String SYSTEM_COMPLETED_REQUEST_COUNT = "weblogic.completed.requests";

    public static final String SYSTEM_REJECTED_REQUEST_COUNT = "weblogic.rejected.requests";

    public static final String SYSTEM_QUEUE_LENGTH = "weblogic.queue.length";

    public static final String SYSTEM_CPU_USED_PERCENT = "weblogic.cpu.used.percent";

    public static final String SYSTEM_PROCESS_CPU_LOAD = "weblogic.process.cpu.used.percent";

    public static final String SYSTEM_MEMORY_UTILIZATION_BYTES = "weblogic.memory.used.bytes";

    public static final String SYSTEM_HEAP_MEMORY_USED_BYTES = "weblogic.heap.memory.used.bytes";

    public static final String SYSTEM_NON_HEAP_MEMORY_USED_BYTES = "weblogic.non.heap.memory.used.bytes";

    public static final String SYSTEM_CURRENT_OPEN_SOCKET_COUNT = "weblogic.current.open.sockets";

    public static final String SYSTEM_MAX_OPEN_SOCKET_COUNT = "weblogic.max.open.sockets";

    public static final String SYSTEM_TOTAL_OPEN_SOCKET_COUNT = "weblogic.open.sockets";

    public static final String SYSTEM_MEMORY_USED_PERCENT = "weblogic.memory.used.percent";

    public static final String THREADPOOL = "weblogic.threadpool";

    public static final String THREADPOOL_TOTAL_THREADS = "weblogic.threadpool.threads";

    public static final String THREADPOOL_IDLE_THREADS = "weblogic.threadpool.idle.threads";

    public static final String THREADPOOL_HOGGING_THREADS = "weblogic.threadpool.hogging.threads";

    public static final String THREADPOOL_STUCK_THREADS = "weblogic.threadpool.stuck.threads";

    public static final String THREADPOOL_STANDBY_THREADS = "weblogic.threadpool.standby.threads";

    public static final String THREADPOOL_PENDING_REQUESTS = "weblogic.threadpool.pending.requests";

    public static final String MEMORYPOOL = "weblogic.memorypool";

    public static final String MEMORYPOOL_TYPE = "weblogic.memorypool.type";

    public static final String MEMORYPOOL_COMMITTED_MEMORY = "weblogic.memorypool.committed.bytes";

    public static final String MEMORYPOOL_INITIAL_MEMORY = "weblogic.memorypool.initial.bytes";

    public static final String MEMORYPOOL_USED_MEMORY = "weblogic.memorypool.used.bytes";

    public static final String MEMORYPOOL_MAX_MEMORY = "weblogic.memorypool.capacity.bytes";

    public static final String GC = "weblogic.gc";

    public static final String GC_COLLECTION_COUNT = "weblogic.gc.collections";

    public static final String GC_TOTAL_COLLECTION_TIME = "weblogic.gc.collection.time.seconds";

    public static final String GC_LAST_GC_START_TIME = "weblogic.gc.last.start.time";

    public static final String GC_LAST_GC_END_TIME = "weblogic.gc.last.end.time";

    public static final String GC_LAST_GC_DURATION = "weblogic.gc.duration.seconds";

    public static final String GC_TOTAL_THREAD_COUNT = "weblogic.gc.threads";

    public static final String SESSION = "weblogic.module";

    public static final String SESSION_ACTIVE_COUNT = "weblogic.module.active.sessions";

    public static final String SESSION_MAX_COUNT = "weblogic.module.max.sessions";

    public static final String SESSION_TOTAL_COUNT = "weblogic.module.sessions";

    public static final String SESSION_SERVLET_COUNT = "weblogic.module.servlets";

    public static final String JMSCONNECTION = "weblogic.jms.connection";

    public static final String JMSCONNECTION_CURRENT_COUNT = "weblogic.jms.connection.active.connections";

    public static final String JMSCONNECTION_TOTAL_COUNT = "weblogic.jms.connection.created.connections";

    public static final String JMSSERVER = "weblogic.jms.server";

    public static final String JMSSERVER_TOTAL_COUNT = "weblogic.jms.server.connected.servers";

    static
    {

        var temp = (ObjectName) null;

        try
        {

            temp = new ObjectName("com.bea:Name=DomainRuntimeService,Type=weblogic.management.mbeanservers.domainruntime.DomainRuntimeServiceMBean");

        }
        catch (Exception e)
        {

            // Suppress exception

        }

        domainRuntimeService = temp;

    }

    public static void discover(String host, String port, JSONArray credentials) {

        var result = new JSONObject();

        result.put("status", "fail");

        result.put("ip", host);

        boolean success = false;

        for (var i = 0; i < credentials.length(); i++) {

            var cred = credentials.getJSONObject(i);

            var username = cred.optString("username", "");

            var password = cred.optString("password", "");

            try
            {
                initConnection(host, port, username, password);

                var serverRuntimesObj = safeAttribute(domainRuntimeService, "ServerRuntimes");

                if (serverRuntimesObj instanceof Object[] && ((Object[]) serverRuntimesObj).length > 0) {

                    result.put("status", "succeed");

                    result.put("id", cred.optString("id", ""));

                    result.put("credential.profile.name", cred.optString("credential.profile.name", ""));

                    success = true;

                    break;

                }

            }
            catch (Exception e)
            {


            }
            finally
            {

                try
                {

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

                }
                catch (Exception e)
                {

                    // Suppress exception

                }

            }

        }

        if (!success)
        {
            result.put("errors", "invalid credential profile");
        }

        System.out.println(result.toString(2));

    }

    public static void poll(String host, String port, String username, String password, String pluginType)
    {

        var data = new JSONObject();

        try {

            initConnection(host, port, username, password);

            switch (pluginType.toLowerCase()) {

                case "system":

                    var serverRuntimesObj = safeAttribute(domainRuntimeService, "ServerRuntimes");

                    if (serverRuntimesObj instanceof Object[]) {

                        var serverRuntimes = (ObjectName[]) serverRuntimesObj;

                        if (serverRuntimes.length > 0) {

                            var serverRuntime = serverRuntimes[0];

                            var bytesReceived = 0L;

                            var bytesSent = 0L;

                            var messagesReceived = 0L;

                            var messagesSent = 0L;

                            try {

                                var channelRuntimes = (ObjectName[]) safeAttribute(serverRuntime, "ServerChannelRuntimes");

                                if (channelRuntimes != null) {

                                    for (var channel : channelRuntimes) {

                                        var channelName = safeStringAttribute(channel, "Name");

                                        if (channelName != null && (channelName.toLowerCase().contains("http") || channelName.toLowerCase().contains("https"))) {

                                            var channelBytesReceived = safeLongAttribute(channel, "BytesReceivedCount");

                                            var channelBytesSent = safeLongAttribute(channel, "BytesSentCount");

                                            var channelMessagesReceived = safeLongAttribute(channel, "MessagesReceivedCount");

                                            var channelMessagesSent = safeLongAttribute(channel, "MessagesSentCount");

                                            if (channelBytesReceived != -1) bytesReceived += channelBytesReceived;

                                            if (channelBytesSent != -1) bytesSent += channelBytesSent;

                                            if (channelMessagesReceived != -1) messagesReceived += channelMessagesReceived;

                                            if (channelMessagesSent != -1) messagesSent += channelMessagesSent;

                                        }

                                    }

                                }

                            } catch (Exception e) {

                                // Suppress exception

                            }

                            putField(data, SYSTEM_RECEIVED_BYTES, bytesReceived != -1 ? bytesReceived : 0);

                            putField(data, SYSTEM_SENT_BYTES, bytesSent != -1 ? bytesSent : 0);

                            putField(data, SYSTEM_RECEIVED_MESSAGES, messagesReceived != -1 ? messagesReceived : 0);

                            putField(data, SYSTEM_SENT_MESSAGES, messagesSent != -1 ? messagesSent : 0);

                            var completedRequestCount = 0L;

                            try
                            {

                                var appRuntimes = (ObjectName[]) safeAttribute(serverRuntime, "ApplicationRuntimes");

                                if (appRuntimes != null) {

                                    for (var appRuntime : appRuntimes) {

                                        var appName = safeStringAttribute(appRuntime, "Name");

                                        if (excludedApps.contains(appName)) continue;

                                        var componentRuntimes = (ObjectName[]) safeAttribute(appRuntime, "ComponentRuntimes");

                                        if (componentRuntimes != null) {

                                            for (var component : componentRuntimes) {

                                                if ("WebAppComponentRuntime".equals(safeStringAttribute(component, "Type"))) {

                                                    var servlets = (ObjectName[]) safeAttribute(component, "Servlets");

                                                    if (servlets != null) {

                                                        for (var servlet : servlets) {

                                                            var invocations = safeLongAttribute(servlet, "InvocationTotalCount");

                                                            if (invocations != -1) completedRequestCount += invocations;

                                                        }

                                                    }

                                                }

                                            }

                                        }

                                    }

                                }

                            }
                            catch (Exception e)
                            {

                                // Suppress exception

                            }

                            putField(data, SYSTEM_COMPLETED_REQUEST_COUNT, completedRequestCount != -1 ? completedRequestCount : 0);

                            putField(data, SYSTEM_REJECTED_REQUEST_COUNT, 0);

                            var queueLength = -1L;

                            try
                            {

                                var workManagerRuntimes = (ObjectName[]) safeAttribute(serverRuntime, "WorkManagerRuntimes");

                                if (workManagerRuntimes != null) {

                                    var totalPendingRequests = 0L;

                                    for (var wm : workManagerRuntimes) {

                                        totalPendingRequests += safeIntAttribute(wm, "PendingRequests");

                                    }

                                    queueLength = totalPendingRequests;

                                }

                                if (queueLength == -1) {

                                    var executeQueues = (ObjectName[]) safeAttribute(serverRuntime, "ExecuteQueueRuntimes");

                                    if (executeQueues != null) {

                                        for (var queue : executeQueues) {

                                            if ("weblogic.kernel.Default".equals(safeStringAttribute(queue, "Name"))) {

                                                queueLength = safeIntAttribute(queue, "QueueLength");

                                                break;

                                            }

                                        }

                                    }

                                }

                            }
                            catch (Exception e)
                            {

                                // Suppress exception

                            }

                            putField(data, SYSTEM_QUEUE_LENGTH, queueLength != -1 ? queueLength : 0);

                            var processCpuLoad = -1.0;

                            var systemCpuLoad = -1.0;

                            var freePhysicalMemorySize = -1L;

                            var totalPhysicalMemorySize = -1L;

                            var heapMemoryUsed = -1L;

                            var nonHeapMemoryUsed = -1L;

                            var heapMemoryCommitted = -1L;

                            var heapMemoryMax =-1L;

                            var nonHeapMemoryCommitted = -1L;

                            var memoryUtilizationPercent = -1.0;

                            try
                            {

                                var osMBeans = connection.queryNames(new ObjectName("java.lang:type=OperatingSystem"), null);

                                var osMB = osMBeans.isEmpty() ? null : osMBeans.iterator().next();

                                if (osMB != null) {

                                    processCpuLoad = safeDoubleAttribute(osMB, "ProcessCpuLoad");

                                    systemCpuLoad = safeDoubleAttribute(osMB, "SystemCpuLoad");

                                    freePhysicalMemorySize = safeLongAttribute(osMB, "FreePhysicalMemorySize");

                                    totalPhysicalMemorySize = safeLongAttribute(osMB, "TotalPhysicalMemorySize");

                                    if (totalPhysicalMemorySize != -1 && freePhysicalMemorySize != -1 && totalPhysicalMemorySize != 0)
                                    {

                                        var usedMemory = totalPhysicalMemorySize - freePhysicalMemorySize;

                                        putField(data, SYSTEM_MEMORY_UTILIZATION_BYTES, usedMemory);

                                    }

                                }

                                var memMBeans = connection.queryNames(new ObjectName("java.lang:type=Memory"), null);

                                var memMB = memMBeans.isEmpty() ? null : memMBeans.iterator().next();

                                if (memMB != null)
                                {

                                    var heapUsage = safeCompositeDataAttribute(memMB, "HeapMemoryUsage");

                                    if (heapUsage != null && heapUsage.containsKey("used") && heapUsage.containsKey("committed") && heapUsage.containsKey("max")) {

                                        heapMemoryUsed = (Long) heapUsage.get("used");

                                        heapMemoryCommitted = (Long) heapUsage.get("committed");

                                        heapMemoryMax = (Long) heapUsage.get("max");

                                    }


                                    var nonHeapUsage = safeCompositeDataAttribute(memMB, "NonHeapMemoryUsage");

                                    if (nonHeapUsage != null && nonHeapUsage.containsKey("used") && nonHeapUsage.containsKey("committed"))
                                    {

                                        nonHeapMemoryUsed = (Long) nonHeapUsage.get("used");

                                        nonHeapMemoryCommitted = (Long) nonHeapUsage.get("committed");

                                    }

                                    if (heapMemoryUsed != -1 && nonHeapMemoryUsed != -1 && heapMemoryCommitted != -1 && nonHeapMemoryCommitted != -1)
                                    {

                                        if (heapMemoryMax != 0) {

                                            memoryUtilizationPercent = ((double) heapMemoryUsed / heapMemoryMax) * 100;

                                            putField(data, SYSTEM_MEMORY_USED_PERCENT, memoryUtilizationPercent);

                                        }

                                    }

                                }

                                putField(data, SYSTEM_MEMORY_UTILIZATION_BYTES, heapMemoryUsed != -1 && nonHeapMemoryUsed != -1 ? heapMemoryUsed + nonHeapMemoryUsed : 0);

                            } catch (Exception e)
                            {

                                // Suppress exception

                            }

                            putField(data, SYSTEM_PROCESS_CPU_LOAD, !Double.isNaN(processCpuLoad) && processCpuLoad != -1.0 ? processCpuLoad * 100 : 0.0);

                            putField(data, SYSTEM_CPU_USED_PERCENT, !Double.isNaN(systemCpuLoad) && systemCpuLoad != -1.0 ? systemCpuLoad * 100 : 0.0);

                            putField(data, SYSTEM_HEAP_MEMORY_USED_BYTES, heapMemoryUsed != -1 ? heapMemoryUsed : 0);

                            putField(data, SYSTEM_NON_HEAP_MEMORY_USED_BYTES, nonHeapMemoryUsed != -1 ? nonHeapMemoryUsed : 0);

                            var totalOpenSockets = safeIntAttribute(serverRuntime, "OpenSocketsCurrentCount");

                            putField(data, SYSTEM_CURRENT_OPEN_SOCKET_COUNT, safeIntAttribute(serverRuntime, "OpenSocketsCurrentCount"));

                            try
                            {

                                var domainMBean = (ObjectName) connection.getAttribute(new ObjectName("com.bea:Name=DomainRuntimeService,Type=weblogic.management.mbeanservers.domainruntime.DomainRuntimeServiceMBean"), "DomainConfiguration");

                                var servers = (ObjectName[]) connection.getAttribute(domainMBean, "Servers");

                                if (servers.length > 0) {

                                    putField(data, SYSTEM_MAX_OPEN_SOCKET_COUNT, safeIntAttribute(servers[0], "MaxOpenSockCount"));

                                }

                            }
                            catch (Exception e)
                            {

                                // Suppress exception

                            }

                            putField(data, SYSTEM_TOTAL_OPEN_SOCKET_COUNT, totalOpenSockets != -1 ? totalOpenSockets : 0);

                            var gcCollectorsArray = new JSONArray();

                            try
                            {

                                var jvmRuntimeSet = connection.queryNames(new ObjectName("*:*,Type=JVMRuntime"), null);

                                Long jvmUptime = null;

                                if (!jvmRuntimeSet.isEmpty()) {

                                    var jvmRT = jvmRuntimeSet.iterator().next();

                                    jvmUptime = (Long) connection.getAttribute(jvmRT, "Uptime");
                                }

                                var gcSet = connection.queryNames(new ObjectName("java.lang:type=GarbageCollector,*"), null);

                                var gcs = new ArrayList<ObjectName>(gcSet);

                                for (var gc : gcs)
                                {

                                    var gcObj = new JSONObject();

                                    var gcName = escapeJson(safeStringAttribute(gc, "Name"));

                                    putField(gcObj, GC, gcName);

                                    putField(gcObj, GC_COLLECTION_COUNT, safeLongAttribute(gc, "CollectionCount"));

                                    var totalCollectionTime = safeLongAttribute(gc, "CollectionTime");

                                    putField(gcObj, GC_TOTAL_COLLECTION_TIME, totalCollectionTime != -1 ? totalCollectionTime / 1000.0 : 0.0);

                                    var lastGcInfo = safeCompositeDataAttribute(gc, "LastGcInfo");

                                    if (lastGcInfo != null)
                                    {

                                        var startTime = lastGcInfo.containsKey("startTime") ? (Long) lastGcInfo.get("startTime") : 0;

                                        var endTime = lastGcInfo.containsKey("endTime") ? (Long) lastGcInfo.get("endTime") : 0;

                                        var duration = lastGcInfo.containsKey("duration") ? (Long) lastGcInfo.get("duration") : 0;

                                        if( jvmUptime != null)
                                        {

                                            var now = System.currentTimeMillis();

                                            var jvmStartTime = now - jvmUptime;

                                            var absoluteStartTime = startTime != 0 ? jvmStartTime + startTime : 0;

                                            var absoluteEndTime = endTime != 0 ? jvmStartTime + endTime : 0;

                                            putField(gcObj, GC_LAST_GC_START_TIME, absoluteStartTime != 0 ? DATE_FORMAT.format(new Date(absoluteStartTime)) : "N/A");

                                            putField(gcObj, GC_LAST_GC_END_TIME, absoluteEndTime != 0 ? DATE_FORMAT.format(new Date(absoluteEndTime)) : "N/A");
                                        }

                                        putField(gcObj, GC_LAST_GC_DURATION, duration != 0 ? duration / 1000.0 : 0.0);

                                        putField(gcObj, GC_TOTAL_THREAD_COUNT, lastGcInfo.containsKey("GcThreadCount") ? (Integer) lastGcInfo.get("GcThreadCount") : 0);

                                    }

                                    gcCollectorsArray.put(gcObj);

                                }

                            }
                            catch (Exception e)
                            {

                                // Suppress exception

                            }

                            if (gcCollectorsArray.length() > 0) data.put(GC, gcCollectorsArray);

                        }

                    }

                    break;

                case "pool":

                    var serverRuntimePoolObj = safeAttribute(domainRuntimeService, "ServerRuntimes");

                    if (serverRuntimePoolObj instanceof Object[])
                    {

                        var serverRuntime = (ObjectName) ((Object[]) serverRuntimePoolObj)[0];

                        var threadPoolsArray = new JSONArray();

                        var memoryPoolsArray = new JSONArray();

                        try
                        {

                            var threadPool = (ObjectName) safeAttribute(serverRuntime, "ThreadPoolRuntime");

                            if (threadPool != null) {

                                var threadPoolObj = new JSONObject();

                                var threadPoolName = escapeJson(safeStringAttribute(threadPool, "Name"));

                                putField(threadPoolObj,THREADPOOL, threadPoolName);

                                putField(threadPoolObj, THREADPOOL_TOTAL_THREADS, safeIntAttribute(threadPool, "ExecuteThreadTotalCount"));

                                putField(threadPoolObj, THREADPOOL_IDLE_THREADS, safeIntAttribute(threadPool, "ExecuteThreadIdleCount"));

                                putField(threadPoolObj, THREADPOOL_HOGGING_THREADS, safeIntAttribute(threadPool, "HoggingThreadCount"));

                                putField(threadPoolObj, THREADPOOL_STUCK_THREADS, safeIntAttribute(threadPool, "StuckThreadCount"));

                                putField(threadPoolObj, THREADPOOL_STANDBY_THREADS, safeIntAttribute(threadPool, "StandbyThreadCount"));

                                putField(threadPoolObj, THREADPOOL_PENDING_REQUESTS, safeIntAttribute(threadPool, "PendingUserRequestCount"));

                                threadPoolsArray.put(threadPoolObj);

                            }

                        }
                        catch (Exception e)
                        {

                            // Suppress exception

                        }

                        try
                        {

                            var memPoolSet = connection.queryNames(new ObjectName("java.lang:type=MemoryPool,*"), null);

                            var memPools = new ArrayList<ObjectName>(memPoolSet);

                            for (var pool : memPools)
                            {

                                var memoryPoolObj = new JSONObject();

                                var poolName = escapeJson(safeStringAttribute(pool, "Name"));

                                putField(memoryPoolObj, MEMORYPOOL, poolName);

                                var memoryPoolMXBean = JMX.newMXBeanProxy(connection, pool, MemoryPoolMXBean.class, true);

                                var memoryType = memoryPoolMXBean.getType();

                                putField(memoryPoolObj, MEMORYPOOL_TYPE, memoryType);

                                var usage = safeCompositeDataAttribute(pool, "Usage");

                                if (usage != null)
                                {

                                    var committedMemory = usage.containsKey("committed") ? (Long) usage.get("committed") : -1;

                                    var initialMemory = usage.containsKey("init") ? (Long) usage.get("init") : -1;

                                    var usedMemory = usage.containsKey("used") ? (Long) usage.get("used") : -1;

                                    var maxAllowedMemory = usage.containsKey("max") ? (Long) usage.get("max") : -1;

                                    putField(memoryPoolObj, MEMORYPOOL_COMMITTED_MEMORY, committedMemory != -1 ? committedMemory : 0);

                                    putField(memoryPoolObj, MEMORYPOOL_INITIAL_MEMORY, initialMemory != -1 ? initialMemory : 0);

                                    putField(memoryPoolObj, MEMORYPOOL_USED_MEMORY, usedMemory != -1 ? usedMemory : 0);

                                    putField(memoryPoolObj, MEMORYPOOL_MAX_MEMORY, maxAllowedMemory != -1 ? maxAllowedMemory : 0);

                                }

                                memoryPoolsArray.put(memoryPoolObj);

                            }

                        }
                        catch (Exception e)
                        {

                            // Suppress exception

                        }

                        if (threadPoolsArray.length() > 0) data.put(THREADPOOL, threadPoolsArray);

                        if (memoryPoolsArray.length() > 0) data.put(MEMORYPOOL, memoryPoolsArray);

                    }

                    break;

                case "session":

                    var serverRuntimeObj = safeAttribute(domainRuntimeService, "ServerRuntimes");

                    if (serverRuntimeObj instanceof Object[]) {

                        var serverRuntime = (ObjectName) ((Object[]) serverRuntimeObj)[0];

                        var appRuntimesObj = safeAttribute(serverRuntime, "ApplicationRuntimes");

                        if (appRuntimesObj instanceof Object[]) {

                            var appRuntimes = (ObjectName[]) appRuntimesObj;

                            var sessionsArray = new JSONArray();

                            try
                            {

                                for (var appRuntime : appRuntimes) {

                                    var appName = safeStringAttribute(appRuntime, "Name");

                                    if (excludedApps.contains(appName)) continue;

                                    var componentRuntimesObj = safeAttribute(appRuntime, "ComponentRuntimes");

                                    if (componentRuntimesObj instanceof Object[]) {

                                        var componentRuntimes = (ObjectName[]) componentRuntimesObj;

                                        for (var component : componentRuntimes) {

                                            var type = safeStringAttribute(component, "Type");

                                            if ("WebAppComponentRuntime".equals(type)) {

                                                var sessionRuntime = (ObjectName) safeAttribute(component, "SessionRuntime");

                                                if (sessionRuntime != null) {

                                                    var sessionObj = new JSONObject();

                                                    var sessionName = escapeJson(safeStringAttribute(component, "Name"));

                                                    putField(sessionObj, SESSION, sessionName);

                                                    putField(sessionObj, SESSION_ACTIVE_COUNT, safeIntAttribute(sessionRuntime, "OpenSessionsCurrentCount"));

                                                    putField(sessionObj, SESSION_MAX_COUNT, safeIntAttribute(sessionRuntime, "OpenSessionsHighCount"));

                                                    putField(sessionObj, SESSION_TOTAL_COUNT, safeIntAttribute(sessionRuntime, "SessionsOpenedTotalCount"));

                                                    putField(sessionObj, SESSION_SERVLET_COUNT, safeIntAttribute(component, "ServletsCurrentCount"));

                                                    sessionsArray.put(sessionObj);

                                                }

                                            }

                                        }

                                    }

                                }

                            }
                            catch (Exception e)
                            {

                                // Suppress exception

                            }

                            if (sessionsArray.length() > 0) data.put(SESSION, sessionsArray);

                        }

                    }

                    break;

                case "jms":

                    var serverRuntimeJmsObj = safeAttribute(domainRuntimeService, "ServerRuntimes");

                    if (serverRuntimeJmsObj instanceof Object[])
                    {

                        var serverRuntime = (ObjectName) ((Object[]) serverRuntimeJmsObj)[0];

                        var jmsServersArray = new JSONArray();

                        var jmsConnectionsArray = new JSONArray();

                        try {

                            var jmsRuntime = (ObjectName) safeAttribute(serverRuntime, "JMSRuntime");

                            if (jmsRuntime != null) {

                                var jmsServers = (ObjectName[]) safeAttribute(jmsRuntime, "JMSServers");

                                if (jmsServers != null) {

                                    for (var jmsServer : jmsServers) {

                                        var jmsServerObj = new JSONObject();

                                        var jmsServerName = escapeJson(safeStringAttribute(jmsServer, "Name"));

                                        putField(jmsServerObj, JMSSERVER, jmsServerName);

                                        var destinationsObj = safeAttribute(jmsServer, "Destinations");

                                        putField(jmsServerObj, JMSSERVER_TOTAL_COUNT, destinationsObj instanceof Object[] ? ((Object[]) destinationsObj).length : 0);

                                        jmsServersArray.put(jmsServerObj);

                                    }

                                }

                            }

                            var jmsConnectionFactoriesSet = connection.queryNames(new ObjectName("com.bea:Type=JMSConnectionFactoryRuntime,Location=" + safeStringAttribute(serverRuntime, "Name") + ",*"), null);

                            var jmsConnectionFactories = new ArrayList<ObjectName>(jmsConnectionFactoriesSet);

                            for (var cf : jmsConnectionFactories) {

                                var jmsConnObj = new JSONObject();

                                var connName = escapeJson(safeStringAttribute(cf, "Name"));

                                putField(jmsConnObj, JMSCONNECTION, connName);

                                putField(jmsConnObj, JMSCONNECTION_CURRENT_COUNT, safeIntAttribute(cf, "ConnectionsCurrentCount"));

                                putField(jmsConnObj, JMSCONNECTION_TOTAL_COUNT, safeIntAttribute(cf, "ConnectionsHighCount"));

                                jmsConnectionsArray.put(jmsConnObj);

                            }

                        }
                        catch (Exception e)
                        {

                            // Suppress exception

                        }

                        if (jmsServersArray.length() > 0) data.put(JMSSERVER, jmsServersArray);

                        if (jmsConnectionsArray.length() > 0) data.put(JMSCONNECTION, jmsConnectionsArray);

                    }

                    break;

                default:

                    data.put("error", "Unsupported plugin type: " + pluginType);

                    break;

            }

        }
        catch (Exception e)
        {

            // Suppress exception

        }
        finally
        {

            try
            {

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

            }
            catch (Exception e)
            {

                // Suppress exception

            }

        }

        System.out.println(data.toString(2));

    }

    public static void initConnection(String host, String port, String username, String password) throws IOException {

        System.setProperty("javax.management.remote.protocol.provider.pkgs", "weblogic.management.remote");

        var protocol = "t3";

        var jndiroot = "/jndi/";

        var mserver = "weblogic.management.mbeanservers.domainruntime";

        var serviceURL = (JMXServiceURL) null;

        try
        {

            serviceURL = new JMXServiceURL(protocol, host, Integer.parseInt(port), jndiroot + mserver);

        }
        catch (Exception e)
        {

            // Suppress exception

        }

        var env = new Hashtable<String, String>();

        env.put(Context.SECURITY_PRINCIPAL, username);

        env.put(Context.SECURITY_CREDENTIALS, password);

        env.put(JMXConnectorFactory.PROTOCOL_PROVIDER_PACKAGES, "weblogic.management.remote");

        env.put("weblogic.jndi.connectTimeout", "30000");

        env.put("weblogic.jndi.responseReadTimeout", "30000");

        var executor = Executors.newSingleThreadExecutor();

        Future<JMXConnector> future = null;

        try
        {

            final JMXServiceURL finalServiceURL = serviceURL;

            final Hashtable<String, String> finalEnv = env;

            future = executor.submit(() -> JMXConnectorFactory.connect(finalServiceURL, finalEnv));


            connector = future.get(30, TimeUnit.SECONDS);

            connection = connector.getMBeanServerConnection();

        }

        catch (TimeoutException e)
        {
            future.cancel(true);

            String errorCode = "MD004";

            var errorObj = new JSONObject()
                    .put("error.code", errorCode)
                    .put("message", "connection timeout");

            var errors = new JSONArray().put(errorObj);

            var response = new JSONObject().put("errors", errors).put("status","fail");

            System.out.println(response.toString(2));

            System.exit(0);
        }

        catch (Exception e)
        {
            var cause = e.getCause();

            String errorCode;

            String message;

            if (cause instanceof SecurityException ||  (cause != null && cause.getMessage() != null && cause.getMessage().toLowerCase().contains("authentication")))
            {
                errorCode = "MD003";

                message = "Invalid credentials";
            }
            else
            {
                errorCode = "MD047";

                message = "connection failed";
            }

            var errorObj = new JSONObject().put("error.code", errorCode).put("message", message);

            var errors = new JSONArray().put(errorObj);

            var response = new JSONObject().put("errors", errors).put("status","fail");

            System.out.println(response.toString(2));

            System.exit(0);
        }

        finally
        {
            executor.shutdownNow();
        }

    }

    public static void main(String[] args) {

        var result = new JSONObject();

        result.put("status", "fail");

        if (args.length != 1) {

            System.out.println(new JSONObject().toString(2));

            return;

        }

        try
        {

            var decodedArgs = new String(Base64.getDecoder().decode(args[0]));

            var json = new JSONObject(decodedArgs);

            var mode = json.getString("event.type");
            var ip = json.getString("ip");
            var port = String.valueOf(json.getInt("port"));


            switch (mode) {

                case "discovery":

                    var credentials = json.getString("profiles");

                    var decodedCreds = new String(Base64.getDecoder().decode(credentials));

                    discover(ip, port, new JSONArray(decodedCreds));

                    break;

                case "metric.poll":

                    var pluginType = json.getString("plugin.type");

                    var username = json.getString("username");

                    var password = json.getString("password");

                    poll(ip, port, username, password, pluginType);

                    break;

                default:

                    return;

            }

        }
        catch (Exception e)
        {

            System.out.println(new JSONObject().toString(2));

            return;

        }

    }

    private static Object safeAttribute(ObjectName obj, String attr) {

        if (obj == null) return null;

        try {

            var value = connection.getAttribute(obj, attr);

            if (value instanceof Object[]) {

                return value;

            }

            return value;

        }
        catch (Exception e)
        {

            return null;

        }

    }

    private static Integer safeIntAttribute(ObjectName obj, String attr) {

        if (obj == null) return 0;

        try
        {

            var value = connection.getAttribute(obj, attr);

            return value instanceof Number ? ((Number) value).intValue() : 0;

        }
        catch (Exception e)
        {

            return 0;

        }

    }

    private static Long safeLongAttribute(ObjectName obj, String attr) {

        if (obj == null) return -1L;

        try
        {

            var value = connection.getAttribute(obj, attr);

            return value instanceof Number ? ((Number) value).longValue() : -1L;

        }
        catch (Exception e)
        {

            return -1L;

        }

    }

    private static Double safeDoubleAttribute(ObjectName obj, String attr) {

        if (obj == null) return -1.0;

        try
        {

            var value = connection.getAttribute(obj, attr);

            return value instanceof Number ? ((Number) value).doubleValue() : -1.0;

        }
        catch (Exception e)
        {

            return -1.0;

        }

    }

    private static String safeStringAttribute(ObjectName obj, String attr) {

        if (obj == null) return "N/A";

        try
        {

            var value = connection.getAttribute(obj, attr);

            return value != null ? value.toString() : "N/A";

        }
        catch (Exception e)
        {

            return "N/A";

        }

    }

    private static CompositeData safeCompositeDataAttribute(ObjectName obj, String attr) {

        if (obj == null) return null;

        try
        {

            var value = connection.getAttribute(obj, attr);

            return value instanceof CompositeData ? (CompositeData) value : null;

        }
        catch (Exception e)
        {

            return null;

        }

    }

    private static String escapeJson(String str)
    {

        return str != null && !str.equals("N/A") ? str.replace("\"", "\\\"").replace("\n", "\\n") : "N/A";

    }

    private static void putField(JSONObject jsonObj, String fieldName, Object value) {

        if (value != null)
        {

            var isValid = true;

            if (value instanceof Number)
            {

                var numericValue = ((Number) value).doubleValue();

                isValid = numericValue != -1 && numericValue != -1.0 && !Double.isNaN(numericValue);

                isValid = isValid || numericValue == 0;

            }
            else if (value instanceof String)
            {

                isValid = !"N/A".equals(value);

            }

            if (isValid)
            {

                jsonObj.put(fieldName, value);

            }

        }

    }

}