LoggerAdminI.java

// Copyright (c) ZeroC, Inc.

package com.zeroc.Ice;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;

final class LoggerAdminI implements LoggerAdmin {
    @Override
    public void attachRemoteLogger(
            RemoteLoggerPrx prx,
            LogMessageType[] messageTypes,
            String[] categories,
            int messageMax,
            Current current)
        throws RemoteLoggerAlreadyAttachedException {
        if (prx == null) {
            return; // can't send this null RemoteLogger anything!
        }

        RemoteLoggerPrx remoteLogger = RemoteLoggerPrx.uncheckedCast(prx.ice_twoway());

        Filters filters = new Filters(messageTypes, categories);
        List<LogMessage> initLogMessages = null;

        synchronized (this) {
            if (_sendLogCommunicator == null) {
                if (_destroyed) {
                    throw new ObjectNotExistException();
                }

                _sendLogCommunicator =
                    createSendLogCommunicator(
                        current.adapter.getCommunicator(), _logger.getLocalLogger());
            }

            Identity remoteLoggerId = remoteLogger.ice_getIdentity();

            if (_remoteLoggerMap.containsKey(remoteLoggerId)) {
                if (_traceLevel > 0) {
                    _logger.trace(
                        _traceCategory,
                        "rejecting `"
                            + remoteLogger.toString()
                            + "' with RemoteLoggerAlreadyAttachedException");
                }

                throw new RemoteLoggerAlreadyAttachedException();
            }

            _remoteLoggerMap.put(
                remoteLoggerId,
                new RemoteLoggerData(
                    changeCommunicator(remoteLogger, _sendLogCommunicator), filters));

            if (messageMax != 0) {
                initLogMessages = new LinkedList<>(_queue); // copy
            } else {
                initLogMessages = new LinkedList<>();
            }
        }

        if (_traceLevel > 0) {
            _logger.trace(_traceCategory, "attached `" + remoteLogger.toString() + "'");
        }

        if (!initLogMessages.isEmpty()) {
            filterLogMessages(
                initLogMessages, filters.messageTypes, filters.traceCategories, messageMax);
        }

        try {
            remoteLogger
                .initAsync(_logger.getPrefix(), initLogMessages.toArray(new LogMessage[0]))
                .whenComplete(
                    (Void v, Throwable ex) -> {
                        if (ex != null) {
                            if (ex instanceof LocalException) {
                                deadRemoteLogger(
                                    remoteLogger, _logger, (LocalException) ex, "init");
                            } else {
                                deadRemoteLogger(
                                    remoteLogger,
                                    _logger,
                                    new UnknownException(ex),
                                    "init");
                            }
                        } else {
                            if (_traceLevel > 1) {
                                _logger.trace(
                                    _traceCategory,
                                    "init on `"
                                        + remoteLogger.toString()
                                        + "' completed successfully");
                            }
                        }
                    });
        } catch (LocalException ex) {
            deadRemoteLogger(remoteLogger, _logger, ex, "init");
            throw ex;
        }
    }

    @Override
    public boolean detachRemoteLogger(RemoteLoggerPrx remoteLogger, Current current) {
        if (remoteLogger == null) {
            return false;
        }

        //
        // No need to convert the proxy as we only use its identity
        //
        boolean found = removeRemoteLogger(remoteLogger);

        if (_traceLevel > 0) {
            if (found) {
                _logger.trace(_traceCategory, "detached `" + remoteLogger.toString() + "'");
            } else {
                _logger.trace(
                    _traceCategory,
                    "cannot detach `" + remoteLogger.toString() + "': not found");
            }
        }

        return found;
    }

    @Override
    public LoggerAdmin.GetLogResult getLog(
            LogMessageType[] messageTypes, String[] categories, int messageMax, Current current) {
        LoggerAdmin.GetLogResult r = new LoggerAdmin.GetLogResult();

        List<LogMessage> logMessages = null;
        synchronized (this) {
            if (messageMax != 0) {
                logMessages = new LinkedList<>(_queue);
            } else {
                logMessages = new LinkedList<>();
            }
        }

        r.prefix = _logger.getPrefix();

        if (!logMessages.isEmpty()) {
            Filters filters = new Filters(messageTypes, categories);
            filterLogMessages(
                logMessages, filters.messageTypes, filters.traceCategories, messageMax);
        }
        r.returnValue = logMessages.toArray(new LogMessage[0]);
        return r;
    }

    LoggerAdminI(Properties props, LoggerAdminLoggerI logger) {
        _maxLogCount = props.getIcePropertyAsInt("Ice.Admin.Logger.KeepLogs");
        _maxTraceCount = props.getIcePropertyAsInt("Ice.Admin.Logger.KeepTraces");
        _traceLevel = props.getIcePropertyAsInt("Ice.Trace.Admin.Logger");
        _logger = logger;
    }

    void destroy() {
        Communicator sendLogCommunicator = null;

        synchronized (this) {
            if (!_destroyed) {
                _destroyed = true;
                sendLogCommunicator = _sendLogCommunicator;
                _sendLogCommunicator = null;
            }
        }

        //
        // Destroy outside lock to avoid deadlock when there are outstanding two-way log calls sent
        // to remote loggers
        //
        if (sendLogCommunicator != null) {
            sendLogCommunicator.destroy();
        }
    }

    synchronized List<RemoteLoggerPrx> log(LogMessage logMessage) {
        List<RemoteLoggerPrx> remoteLoggers = null;

        //
        // Put message in _queue
        //
        if ((logMessage.type != LogMessageType.TraceMessage && _maxLogCount > 0)
            || (logMessage.type == LogMessageType.TraceMessage && _maxTraceCount > 0)) {
            _queue.add(logMessage); // add at the end

            if (logMessage.type != LogMessageType.TraceMessage) {
                assert (_maxLogCount > 0);
                if (_logCount == _maxLogCount) {
                    //
                    // Need to remove the oldest log from the queue
                    //
                    assert (_oldestLog != -1);
                    _queue.remove(_oldestLog);
                    int qs = _queue.size();

                    while (_oldestLog < qs
                        && _queue.get(_oldestLog).type == LogMessageType.TraceMessage) {
                        _oldestLog++;
                    }
                    assert (_oldestLog < qs); // remember: we just added a log message at end
                } else {
                    assert (_logCount < _maxLogCount);
                    _logCount++;
                    if (_oldestLog == -1) {
                        _oldestLog = _queue.size() - 1;
                    }
                }
            } else {
                assert (_maxTraceCount > 0);
                if (_traceCount == _maxTraceCount) {
                    //
                    // Need to remove the oldest trace from the queue
                    //
                    assert (_oldestTrace != -1);
                    _queue.remove(_oldestTrace);
                    int qs = _queue.size();
                    while (_oldestTrace < qs
                        && _queue.get(_oldestTrace).type != LogMessageType.TraceMessage) {
                        _oldestTrace++;
                    }
                    assert (_oldestTrace < qs); // remember: we just added a trace message at end
                } else {
                    assert (_traceCount < _maxTraceCount);
                    _traceCount++;
                    if (_oldestTrace == -1) {
                        _oldestTrace = _queue.size() - 1;
                    }
                }
            }

            //
            // Queue updated, now find which remote loggers want this message
            //
            for (RemoteLoggerData p : _remoteLoggerMap.values()) {
                Filters filters = p.filters;

                if (filters.messageTypes.isEmpty()
                    || filters.messageTypes.contains(logMessage.type)) {
                    if (logMessage.type != LogMessageType.TraceMessage
                        || filters.traceCategories.isEmpty()
                        || filters.traceCategories.contains(logMessage.traceCategory)) {
                        if (remoteLoggers == null) {
                            remoteLoggers = new ArrayList<>();
                        }
                        remoteLoggers.add(p.remoteLogger);
                    }
                }
            }
        }

        return remoteLoggers;
    }

    void deadRemoteLogger(
            RemoteLoggerPrx remoteLogger, Logger logger, LocalException ex, String operation) {
        //
        // No need to convert remoteLogger as we only use its identity
        //
        if (removeRemoteLogger(remoteLogger)) {
            if (_traceLevel > 0) {
                logger.trace(
                    _traceCategory,
                    "detached `"
                        + remoteLogger.toString()
                        + "' because "
                        + operation
                        + " raised:\n"
                        + ex.toString());
            }
        }
    }

    int getTraceLevel() {
        return _traceLevel;
    }

    private synchronized boolean removeRemoteLogger(RemoteLoggerPrx remoteLogger) {
        return _remoteLoggerMap.remove(remoteLogger.ice_getIdentity()) != null;
    }

    private static void filterLogMessages(
            List<LogMessage> logMessages,
            Set<LogMessageType> messageTypes,
            Set<String> traceCategories,
            int messageMax) {
        assert (!logMessages.isEmpty() && messageMax != 0);

        //
        // Filter only if one of the 3 filters is set; messageMax < 0 means "give me all" that match
        // the other filters, if any.
        //
        if (!messageTypes.isEmpty() || !traceCategories.isEmpty() || messageMax > 0) {
            int count = 0;
            ListIterator<LogMessage> p = logMessages.listIterator(logMessages.size());
            while (p.hasPrevious()) {
                boolean keepIt = false;
                LogMessage msg = p.previous();
                if (messageTypes.isEmpty() || messageTypes.contains(msg.type)) {
                    if (msg.type != LogMessageType.TraceMessage
                        || traceCategories.isEmpty()
                        || traceCategories.contains(msg.traceCategory)) {
                        keepIt = true;
                    }
                }

                if (keepIt) {
                    ++count;
                    if (messageMax > 0 && count >= messageMax) {
                        if (p.hasPrevious()) {
                            int removeCount = p.previousIndex() + 1;
                            for (int i = 0; i < removeCount; i++) {
                                logMessages.remove(0);
                            }
                        }
                        break; // while
                    }
                } else {
                    p.remove();
                }
            }
        }
        // else, don't need any filtering
    }

    //
    // Change this proxy's communicator, while keeping its invocation timeout
    //
    private static RemoteLoggerPrx changeCommunicator(
            RemoteLoggerPrx prx, Communicator communicator) {
        if (prx == null) {
            return null;
        }

        return RemoteLoggerPrx.createProxy(
            communicator, prx.toString()).ice_invocationTimeout(prx.ice_getInvocationTimeout());
    }

    private static void copyProperties(String prefix, Properties from, Properties to) {
        for (Map.Entry<String, String> p :
                from.getPropertiesForPrefix(prefix).entrySet()) {
            to.setProperty(p.getKey(), p.getValue());
        }
    }

    private static Communicator createSendLogCommunicator(
            Communicator communicator, Logger logger) {
        InitializationData initData = new InitializationData();
        initData.logger = logger;
        initData.properties = new Properties();

        Properties mainProps = communicator.getProperties();

        copyProperties("Ice.Default.Locator", mainProps, initData.properties);
        copyProperties("IceSSL.", mainProps, initData.properties);

        String[] extraProps = mainProps.getIcePropertyAsList("Ice.Admin.Logger.Properties");

        if (extraProps.length > 0) {
            for (int i = 0; i < extraProps.length; i++) {
                String p = extraProps[i];
                if (!p.startsWith("--")) {
                    extraProps[i] = "--" + p;
                }
            }
            initData.properties.parseCommandLineOptions("", extraProps);
        }
        return Util.initialize(initData);
    }

    private final List<LogMessage> _queue = new LinkedList<>();
    private int _logCount; // non-trace messages
    private final int _maxLogCount;
    private int _traceCount;
    private final int _maxTraceCount;
    private final int _traceLevel;

    private int _oldestTrace = -1;
    private int _oldestLog = -1;

    private static class Filters {
        Filters(LogMessageType[] m, String[] c) {
            messageTypes = new HashSet<>(Arrays.asList(m));
            traceCategories = new HashSet<>(Arrays.asList(c));
        }

        final Set<LogMessageType> messageTypes;
        final Set<String> traceCategories;
    }

    private static class RemoteLoggerData {
        RemoteLoggerData(RemoteLoggerPrx prx, Filters f) {
            remoteLogger = prx;
            filters = f;
        }

        final RemoteLoggerPrx remoteLogger;
        final Filters filters;
    }

    private final Map<Identity, RemoteLoggerData> _remoteLoggerMap =
        new HashMap<>();

    private final LoggerAdminLoggerI _logger;
    private Communicator _sendLogCommunicator;
    private boolean _destroyed;
    private static final String _traceCategory = "Admin.Logger";
}