Package org.apache.sling.commons.logservice.internal

Source Code of org.apache.sling.commons.logservice.internal.LogSupport

/*
* 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.sling.commons.logservice.internal;

import java.util.Collections;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

import org.osgi.framework.Bundle;
import org.osgi.framework.BundleEvent;
import org.osgi.framework.BundleException;
import org.osgi.framework.Constants;
import org.osgi.framework.FrameworkEvent;
import org.osgi.framework.FrameworkListener;
import org.osgi.framework.ServiceEvent;
import org.osgi.framework.ServiceListener;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.SynchronousBundleListener;
import org.osgi.service.component.ComponentConstants;
import org.osgi.service.log.LogEntry;
import org.osgi.service.log.LogListener;
import org.osgi.service.log.LogService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* The <code>LogReaderServiceFactory</code> TODO
*/
public class LogSupport implements SynchronousBundleListener, ServiceListener,
        FrameworkListener {

    /**
     * The service property name of the component name (value is
     * "component.name"). Note: We use a private constant here to not create a
     * unneded dependency on the org.osgi.service.component package.
     */
    private static final String COMPONENT_NAME = ComponentConstants.COMPONENT_NAME; // "component.name";

    /**
     * The empty enumeration currently returned on the {@link #getLog()} call
     * because we do not currently record the log events.
     */
    private final Enumeration<?> EMPTY = Collections.enumeration(Collections.emptyList());

    // The registered LogListeners
    private LogListenerProxy[] listeners;

    // The lock used to guard concurrent access to the listeners array
    private final Object listenersLock = new Object();

    // The loggers by bundle id used for logging messages originated from
    // specific bundles
    @SuppressWarnings("serial")
    private Map<Long, Logger> loggers = new LinkedHashMap<Long, Logger>(16,
        0.75f, true) {
        private static final int MAX_SIZE = 50;

        protected boolean removeEldestEntry(Map.Entry<Long, Logger> eldest) {
            return size() > MAX_SIZE;
        }
    };

    // the worker thread actually sending LogEvents to LogListeners
    private LogEntryDispatcher logEntryDispatcher;

    /* package */LogSupport() {
        logEntryDispatcher = new LogEntryDispatcher(this);
        logEntryDispatcher.start();
    }

    /* package */void shutdown() {

        // terminate the dispatcher and wait for its termination here
        logEntryDispatcher.terminate();
        try {
            logEntryDispatcher.join(1000L);
        } catch (InterruptedException ie) {
            // don't care
        }

        // drop all listeners
        synchronized (listenersLock) {
            listeners = null;
        }
    }

    // ---------- LogReaderService interface -----------------------------------

    /* package */void addLogListener(Bundle bundle, LogListener listener) {
        synchronized (listenersLock) {
            LogListenerProxy llp = new LogListenerProxy(bundle, listener);
            if (listeners == null) {
                listeners = new LogListenerProxy[] { llp };
            } else if (getListener(listener) < 0) {
                LogListenerProxy[] newListeners = new LogListenerProxy[listeners.length + 1];
                System.arraycopy(listeners, 0, newListeners, 0,
                    listeners.length);
                newListeners[listeners.length] = llp;
                listeners = newListeners;
            }
        }
    }

    /* package */void removeLogListener(LogListener listener) {
        synchronized (listenersLock) {
            // no listeners registered, nothing to do
            if (listeners == null) {
                return;
            }

            // listener is not registered, nothing to do
            int idx = getListener(listener);
            if (idx < 0) {
                return;
            }

            LogListenerProxy[] newListeners = new LogListenerProxy[listeners.length - 1];
            if (idx > 0) {
                System.arraycopy(listeners, 0, newListeners, 0, idx);
            }
            if (idx < listeners.length) {
                System.arraycopy(listeners, idx + 1, newListeners, 0,
                    newListeners.length - idx);
            }
            listeners = newListeners;
        }
    }

    /**
     * Removes all registered LogListeners belonging to the given bundle. This
     * is the task required by the specification from a Log Service
     * implemenation:
     * <p>
     * <blockquote> When a bundle which registers a LogListener object is
     * stopped or otherwise releases the Log Reader Service, the Log Reader
     * Service must remove all of the bundle's listeners.</blockquote>
     * <p>
     *
     * @param bundle The bundle whose listeners are to be removed.
     */
    /* package */void removeLogListeners(Bundle bundle) {
        // grab an immediate copy of the array
        LogListenerProxy[] current = getListeners();
        if (current == null) {
            return;
        }

        // check for listeners by bundle
        for (int i = 0; i < current.length; i++) {
            if (current[i].hasBundle(bundle)) {
                removeLogListener(current[i]);
            }
        }
    }

    private int getListener(LogListener listener) {
        if (listeners != null) {
            for (int i = 0; i < listeners.length; i++) {
                if (listeners[i].isSame(listener)) {
                    return i;
                }
            }
        }

        // fall back to not found
        return -1;
    }

    /**
     * Returns the currently registered LogListeners
     */
    private LogListenerProxy[] getListeners() {
        synchronized (listenersLock) {
            return listeners;
        }
    }

    /**
     * Returns an empty enumeration for now because we do not implement log
     * entry recording for the moment.
     */
    Enumeration<?> getLog() {
        return EMPTY;
    }

    // ---------- Firing a log event -------------------------------------------

    /**
     * Logs the given log entry to the log file and enqueues for the dispatching
     * to the registered LogListeners in a separate worker thread.
     */
    /* package */void fireLogEvent(LogEntry logEntry) {

        // actually log it to SLF4J
        logOut(logEntry);

        // enqueue for asynchronous delivery
        logEntryDispatcher.enqueLogEntry(logEntry);
    }

    // ---------- BundleListener -----------------------------------------------

    /**
     * Listens for Bundle events and logs the respective events according to the
     * Log Service specification. In addition, all LogListener instances
     * registered for stopped bundles are removed by this method.
     */
    public void bundleChanged(BundleEvent event) {
        String message;
        switch (event.getType()) {
            case BundleEvent.INSTALLED:
                message = "BundleEvent INSTALLED";
                break;
            case BundleEvent.RESOLVED:
                message = "BundleEvent RESOLVED";
                break;
            case BundleEvent.STARTING:
                message = "BundleEvent STARTING";
                break;
            case BundleEvent.STARTED:
                message = "BundleEvent STARTED";
                break;
            case BundleEvent.STOPPING:
                message = "BundleEvent STOPPING";
                break;
            case BundleEvent.STOPPED:
                // this is special, as we have to fix the listener list for
                // stopped bundles
                removeLogListeners(event.getBundle());
                message = "BundleEvent STOPPED";
                break;
            case BundleEvent.UNRESOLVED:
                message = "BundleEvent UNRESOLVED";
                break;
            case BundleEvent.UPDATED:
                message = "BundleEvent UPDATED";
                break;
            case BundleEvent.UNINSTALLED:
                // remove any cached logger for the uninstalled bundle
                ungetLogger(event.getBundle());
                message = "BundleEvent UNINSTALLED";
                break;
            default:
                message = "BundleEvent " + event.getType();
        }

        LogEntry entry = new LogEntryImpl(event.getBundle(), null,
            LogService.LOG_INFO, message, null);
        fireLogEvent(entry);
    }

    // ---------- ServiceListener ----------------------------------------------

    /**
     * Listens for Service events and logs the respective events according to
     * the Log Service specification.
     */
    public void serviceChanged(ServiceEvent event) {
        int level = LogService.LOG_INFO;
        String message;
        switch (event.getType()) {
            case ServiceEvent.REGISTERED:
                message = "ServiceEvent REGISTERED";
                break;
            case ServiceEvent.MODIFIED:
                message = "ServiceEvent MODIFIED";
                level = LogService.LOG_DEBUG;
                break;
            case ServiceEvent.UNREGISTERING:
                message = "ServiceEvent UNREGISTERING";
                break;
            default:
                message = "ServiceEvent " + event.getType();
        }

        String s = (event.getServiceReference().getBundle() == null)
                ? null
                : "Bundle " + event.getServiceReference().getBundle();
        s = (s == null) ? message : s + " " + message;

        LogEntry entry = new LogEntryImpl(
            event.getServiceReference().getBundle(),
            event.getServiceReference(), level, message, null);
        fireLogEvent(entry);
    }

    // ---------- FrameworkListener --------------------------------------------

    /**
     * Listens for Framework events and logs the respective events according to
     * the Log Service specification.
     * <p>
     * In the case of a Framework ERROR which is a ClassNotFoundException for an
     * unresolved bundle, the message is logged at INFO level instead of ERROR
     * level as prescribed by the spec. This is because such a situation should
     * not really result in a Framework ERROR but the Apache Felix framework has
     * no means of controlling this at the moment (framework 1.0.4 release).
     */
    public void frameworkEvent(FrameworkEvent event) {
        int level = LogService.LOG_INFO;
        String message;
        Throwable exception = event.getThrowable();
        switch (event.getType()) {
            case FrameworkEvent.STARTED:
                message = "FrameworkEvent STARTED";
                break;
            case FrameworkEvent.ERROR:
                message = "FrameworkEvent ERROR";

                // special precaution for Felix.loadBundleClass event overkill
                // FIXME: actually, the error is ok, if the bundle failed to
                // resolve
                if (exception instanceof BundleException) {
                    StackTraceElement[] ste = exception.getStackTrace();
                    if (ste != null && ste.length > 0
                        && "loadBundleClass".equals(ste[0].getMethodName())) {
                        message += ": Class " + exception.getMessage()
                            + " not found";
                        if (event.getBundle() != null) {
                            message += " in bundle "
                                + event.getBundle().getSymbolicName() + " ("
                                + event.getBundle().getBundleId() + ")";
                        }
                        level = LogService.LOG_INFO;
                        exception = null; // don't care for a stack trace here
                        break;
                    }
                }

                level = LogService.LOG_ERROR;
                break;
            case FrameworkEvent.PACKAGES_REFRESHED:
                message = "FrameworkEvent PACKAGES REFRESHED";
                break;
            case FrameworkEvent.STARTLEVEL_CHANGED:
                message = "FrameworkEvent STARTLEVEL CHANGED";
                break;
            case FrameworkEvent.WARNING:
                message = "FrameworkEvent WARNING";
                break;
            case FrameworkEvent.INFO:
                message = "FrameworkEvent INFO";
                break;
            default:
                message = "FrameworkEvent " + event.getType();
        }

        String s = (event.getBundle() == null) ? null : "Bundle "
            + event.getBundle();
        s = (s == null) ? message : s + " " + message;

        LogEntry entry = new LogEntryImpl(event.getBundle(), null, level,
            message, exception);
        fireLogEvent(entry);
    }

    // ---------- Effective logging --------------------------------------------

    /**
     * Get a logger for messages orginating from the given bundle. If no bundle
     * is specified, we use the system bundle logger.
     *
     * @param bundle The bundle for which a logger is to be returned.
     * @return The Logger for the bundle.
     */
    private Logger getLogger(Bundle bundle) {
        Long bundleId = new Long((bundle == null) ? 0 : bundle.getBundleId());
        Logger log;
        synchronized (loggers) {
            log = loggers.get(bundleId);
        }
        if (log == null) {

            String name;
            if (bundle == null) {

                // if we have no bundle, use the system bundle's name
                name = Constants.SYSTEM_BUNDLE_SYMBOLICNAME;

            } else {

                // otherwise use the bundle symbolic name
                name = bundle.getSymbolicName();

                // if the bundle has no symbolic name, use the location
                if (name == null) {
                    name = bundle.getLocation();
                }

                // if the bundle also has no location, use the bundle Id
                if (name == null) {
                    name = String.valueOf(bundle.getBundleId());
                }
            }

            log = LoggerFactory.getLogger(name);
            synchronized (loggers) {
                loggers.put(bundleId, log);
            }
        }
        return log;
    }

    /**
     * Removes the cached logger for the given bundle, for example if the
     * bundle is uninstalled and thus there will be no more logs from this
     * bundle.
     */
    private void ungetLogger(Bundle bundle) {
        synchronized (loggers) {
            loggers.remove(bundle.getBundleId());
        }
    }

    /**
     * Actually logs the given log entry to the logger for the bundle recorded
     * in the log entry.
     */
    private void logOut(LogEntry logEntry) {
        // /* package */ void logOut(Bundle bundle, ServiceReference sr, int
        // level, String message, Throwable exception) {

        // get the logger for the bundle
        Logger log = getLogger(logEntry.getBundle());

        StringBuffer msg = new StringBuffer();

        ServiceReference sr = logEntry.getServiceReference();
        if (sr != null) {
            msg.append("Service [");
            if (sr.getProperty(Constants.SERVICE_PID) != null) {
                msg.append(sr.getProperty(Constants.SERVICE_PID)).append(',');
            } else if (sr.getProperty(COMPONENT_NAME) != null) {
                msg.append(sr.getProperty(COMPONENT_NAME)).append(',');
            } else if (sr.getProperty(Constants.SERVICE_DESCRIPTION) != null) {
                msg.append(sr.getProperty(Constants.SERVICE_DESCRIPTION)).append(
                    ',');
            }
            msg.append(sr.getProperty(Constants.SERVICE_ID)).append("] ");
        }

        if (logEntry.getMessage() != null) {
            msg.append(logEntry.getMessage());
        }

        Throwable exception = logEntry.getException();
        if (exception != null) {
            msg.append(" (").append(exception).append(')');
        }

        String message = msg.toString();
        switch (logEntry.getLevel()) {
            case LogService.LOG_DEBUG:
                log.debug(message, exception);
                break;
            case LogService.LOG_INFO:
                log.info(message, exception);
                break;
            case LogService.LOG_WARNING:
                log.warn(message, exception);
                break;
            case LogService.LOG_ERROR:
                log.error(message, exception);
                break;
            default:
                if (logEntry.getLevel() > LogService.LOG_DEBUG) {
                    log.trace(message, exception);
                } else if (logEntry.getLevel() < LogService.LOG_ERROR) {
                    log.error(message, exception);
                }
                break;
        }
    }

    // ---------- internal class -----------------------------------------------

    /**
     * The <code>LogListenerProxy</code> class is a proxy to the actually
     * registered <code>LogListener</code> which also records the bundle
     * registering the listener. This allows for the removal of the log
     * listeners registered by bundles which have not been removed before the
     * bundle has been stopped.
     */
    private static class LogListenerProxy implements LogListener {

        private final int runningBundle = Bundle.STARTING | Bundle.ACTIVE
            | Bundle.STOPPING;

        private final Bundle bundle;

        private final LogListener delegatee;

        public LogListenerProxy(Bundle bundle, LogListener delegatee) {
            this.bundle = bundle;
            this.delegatee = delegatee;
        }

        public void logged(LogEntry entry) {
            if ((bundle.getState() & runningBundle) != 0) {
                delegatee.logged(entry);
            }
        }

        /* package */boolean isSame(LogListener listener) {
            return listener == delegatee || listener == this;
        }

        /* package */boolean hasBundle(Bundle bundle) {
            return this.bundle == bundle;
        }
    }

    /**
     * The <code>LogEntryDispatcher</code> implements the worker thread
     * responsible for delivering log events to the log listeners.
     */
    private static class LogEntryDispatcher extends Thread {

        // provides the actual log listeners on demand
        private final LogSupport logSupport;

        // the queue of log events to be dispatched
        private final BlockingQueue<LogEntry> dispatchQueue;

        // true as long as the thread is active
        private boolean active;

        LogEntryDispatcher(LogSupport logSupport) {
            super("LogEntry Dispatcher");

            this.logSupport = logSupport;
            this.dispatchQueue = new LinkedBlockingQueue<LogEntry>();
            this.active = true;
        }

        /**
         * Add a log entry for dispatching.
         */
        void enqueLogEntry(LogEntry logEntry) {
            dispatchQueue.offer(logEntry);
        }

        /**
         * Get the next log entry for dispatching. This method blocks until an
         * event is available or the thread is interrupted.
         *
         * @return The next event to dispatch
         * @throws InterruptedException If the thread has been interrupted while
         *             waiting for a log event to dispatch.
         */
        LogEntry dequeueLogEntry() throws InterruptedException {
            return dispatchQueue.take();
        }

        /**
         * Terminates this work thread by resetting the active flag and
         * interrupting itself such that the {@link #dequeueLogEntry()} is
         * aborted for the thread to terminate.
         */
        void terminate() {
            active = false;
            interrupt();
        }

        /**
         * Runs the actual log event dispatching. This method continues to get
         * log events from the {@link #dequeueLogEntry()} method until the
         * active flag is reset.
         */
        @Override
        public void run() {
            while (active) {

                LogEntry logEntry = null;
                try {
                    logEntry = dequeueLogEntry();
                } catch (InterruptedException ie) {
                    // don't care, this is expected
                }

                // dispatch the log entry
                if (logEntry != null) {

                    // grab an immediate copy of the array
                    LogListener[] logListeners = logSupport.getListeners();

                    // fire the events outside of the listenersLock
                    if (logListeners != null) {
                        for (LogListener logListener : logListeners) {
                            try {
                                logListener.logged(logEntry);
                            } catch (Throwable t) {
                                // should we really care ??
                            }
                        }
                    }
                }
            }
        }
    }
}
TOP

Related Classes of org.apache.sling.commons.logservice.internal.LogSupport

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.