/* * 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.felix.eventadmin.impl.util; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import org.osgi.framework.BundleContext; import org.osgi.framework.BundleException; import org.osgi.framework.Constants; import org.osgi.framework.InvalidSyntaxException; import org.osgi.framework.ServiceEvent; import org.osgi.framework.ServiceListener; import org.osgi.framework.ServiceReference; /** * This class mimics the standard OSGi <tt>LogService</tt> interface. An * instance of this class will be used by the EventAdmin for all logging. The * implementation of this class sends log messages to standard output, if no * <tt>LogService</tt> is present; it uses a log service if one is * installed in the framework. To do that without creating a hard dependency on the * package it uses fully qualified class names and registers a listener with the * framework hence, it does not need access to the <tt>LogService</tt> class but will * use it if the listener is informed about an available service. By using a * DynamicImport-Package dependency we don't need the package but * use it if present. Additionally, all log methods prefix the log message with * <tt>EventAdmin: </tt>. * * There is one difference in behavior from the standard OSGi LogService. * This logger has a {@link #m_logLevel} property which decides what messages * get logged. * * @see org.osgi.service.log.LogService * * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a> **/ // TODO: At the moment we log a message to all currently available LogServices. // Maybe, we should only log to the one with the highest ranking instead? // What is the best practice in this case? public class LogWrapper { /** * ERROR LEVEL * * @see org.osgi.service.log.LogService#LOG_ERROR */ public static final int LOG_ERROR = 1; /** * WARNING LEVEL * * @see org.osgi.service.log.LogService#LOG_WARNING */ public static final int LOG_WARNING = 2; /** * INFO LEVEL * * @see org.osgi.service.log.LogService#LOG_INFO */ public static final int LOG_INFO = 3; /** * DEBUG LEVEL * * @see org.osgi.service.log.LogService#LOG_DEBUG */ public static final int LOG_DEBUG = 4; // A set containing the currently available LogServices. Furthermore used as lock private final Set<ServiceReference> m_loggerRefs = new HashSet<ServiceReference>(); // Only null while not set and m_loggerRefs is empty hence, only needs to be // checked in case m_loggerRefs is empty otherwise it will not be null. private BundleContext m_context; private ServiceListener m_logServiceListener; /** * Current log level. Message with log level less than or equal to * current log level will be logged. * The default value is {@link #LOG_WARNING} * * @see #setLogLevel(int) */ private int m_logLevel = LOG_WARNING; /* * A thread save variant of the double checked locking singleton. */ private static class LogWrapperLoader { static final LogWrapper m_singleton = new LogWrapper(); } /** * Returns the singleton instance of this LogWrapper that can be used to send * log messages to all currently available LogServices or to standard output, * respectively. * * @return the singleton instance of this LogWrapper. */ public static LogWrapper getLogger() { return LogWrapperLoader.m_singleton; } /** * Set the <tt>BundleContext</tt> of the bundle. This method registers a service * listener for LogServices with the framework that are subsequently used to * log messages. * <p> * If the bundle context is <code>null</code>, the service listener is * unregistered and all remaining references to LogServices dropped before * internally clearing the bundle context field. * * @param context The context of the bundle. */ public static void setContext( final BundleContext context ) { LogWrapper logWrapper = LogWrapperLoader.m_singleton; // context is removed, unregister and drop references if ( context == null ) { if ( logWrapper.m_logServiceListener != null ) { logWrapper.m_context.removeServiceListener( logWrapper.m_logServiceListener ); logWrapper.m_logServiceListener = null; } logWrapper.removeLoggerRefs(); } // set field logWrapper.setBundleContext( context ); // context is set, register and get existing services if ( context != null ) { try { ServiceListener listener = new ServiceListener() { // Add a newly available LogService reference to the singleton. @Override public void serviceChanged( final ServiceEvent event ) { if ( ServiceEvent.REGISTERED == event.getType() ) { LogWrapperLoader.m_singleton.addLoggerRef( event.getServiceReference() ); } // unregistered services are handled in the next log operation. } }; context.addServiceListener( listener, "(" + Constants.OBJECTCLASS + "=org.osgi.service.log.LogService)" ); logWrapper.m_logServiceListener = listener; // Add all available LogService references to the singleton. final ServiceReference[] refs = context.getServiceReferences( "org.osgi.service.log.LogService", null ); if ( null != refs ) { for ( int i = 0; i < refs.length; i++ ) { logWrapper.addLoggerRef( refs[i] ); } } } catch ( InvalidSyntaxException e ) { // this never happens } } } /* * The private singleton constructor. */ LogWrapper() { // Singleton } /* * Removes all references to LogServices still kept */ void removeLoggerRefs() { synchronized ( m_loggerRefs ) { m_loggerRefs.clear(); } } /* * Add a reference to a newly available LogService */ void addLoggerRef( final ServiceReference ref ) { synchronized (m_loggerRefs) { m_loggerRefs.add(ref); } } /* * Set the context of the bundle in the singleton implementation. */ private void setBundleContext(final BundleContext context) { synchronized(m_loggerRefs) { m_context = context; } } /** * Log a message with the given log level. Note that this will prefix the message * with <tt>EventAdmin: </tt>. * * @param level The log level with which to log the msg. * @param msg The message to log. */ public void log(final int level, final String msg) { // The method will remove any unregistered service reference as well. synchronized(m_loggerRefs) { if (level > m_logLevel) { return; // don't log } final String logMsg = "EventAdmin: " + msg; if (!m_loggerRefs.isEmpty()) { // There is at least one LogService available hence, we can use the // class as well. for (Iterator<ServiceReference> iter = m_loggerRefs.iterator(); iter.hasNext();) { final ServiceReference next = iter.next(); org.osgi.service.log.LogService logger = (org.osgi.service.log.LogService) m_context.getService(next); if (null != logger) { logger.log(level, logMsg); m_context.ungetService(next); } else { // The context returned null for the reference - it follows // that the service is unregistered and we can remove it iter.remove(); } } } else { _log(null, level, logMsg, null); } } } /** * Log a message with the given log level and the associated exception. Note that * this will prefix the message with <tt>EventAdmin: </tt>. * * @param level The log level with which to log the msg. * @param msg The message to log. * @param ex The exception associated with the message. */ public void log(final int level, final String msg, final Throwable ex) { // The method will remove any unregistered service reference as well. synchronized(m_loggerRefs) { if (level > m_logLevel) { return; // don't log } final String logMsg = "EventAdmin: " + msg; if (!m_loggerRefs.isEmpty()) { // There is at least one LogService available hence, we can use the // class as well. for (Iterator<ServiceReference> iter = m_loggerRefs.iterator(); iter.hasNext();) { final ServiceReference next = iter.next(); org.osgi.service.log.LogService logger = (org.osgi.service.log.LogService) m_context.getService(next); if (null != logger) { logger.log(level, logMsg, ex); m_context.ungetService(next); } else { // The context returned null for the reference - it follows // that the service is unregistered and we can remove it iter.remove(); } } } else { _log(null, level, logMsg, ex); } } } /** * Log a message with the given log level together with the associated service * reference. Note that this will prefix the message with <tt>EventAdmin: </tt>. * * @param sr The reference of the service associated with this message. * @param level The log level with which to log the msg. * @param msg The message to log. */ public void log(final ServiceReference sr, final int level, final String msg) { // The method will remove any unregistered service reference as well. synchronized(m_loggerRefs) { if (level > m_logLevel) { return; // don't log } final String logMsg = "EventAdmin: " + msg; if (!m_loggerRefs.isEmpty()) { // There is at least one LogService available hence, we can use the // class as well. for (Iterator<ServiceReference> iter = m_loggerRefs.iterator(); iter.hasNext();) { final ServiceReference next = iter.next(); org.osgi.service.log.LogService logger = (org.osgi.service.log.LogService) m_context.getService(next); if (null != logger) { logger.log(sr, level, logMsg); m_context.ungetService(next); } else { // The context returned null for the reference - it follows // that the service is unregistered and we can remove it iter.remove(); } } } else { _log(sr, level, logMsg, null); } } } /** * Log a message with the given log level, the associated service reference and * exception. Note that this will prefix the message with <tt>EventAdmin: </tt>. * * @param sr The reference of the service associated with this message. * @param level The log level with which to log the msg. * @param msg The message to log. * @param ex The exception associated with the message. */ public void log(final ServiceReference sr, final int level, final String msg, final Throwable ex) { // The method will remove any unregistered service reference as well. synchronized(m_loggerRefs) { if (level > m_logLevel) { return; // don't log } final String logMsg = "EventAdmin: " + msg; if (!m_loggerRefs.isEmpty()) { // There is at least one LogService available hence, we can use the // class as well. for (Iterator<ServiceReference> iter = m_loggerRefs.iterator(); iter.hasNext();) { final ServiceReference next = iter.next(); org.osgi.service.log.LogService logger = (org.osgi.service.log.LogService) m_context.getService(next); if (null != logger) { logger.log(sr, level, logMsg, ex); m_context.ungetService(next); } else { // The context returned null for the reference - it follows // that the service is unregistered and we can remove it iter.remove(); } } } else { _log(sr, level, logMsg, ex); } } } /* * Log the message to standard output. This appends the level to the message. * null values are handled appropriate. */ private void _log(final ServiceReference sr, final int level, final String msg, Throwable ex) { String s = (sr == null) ? null : "SvcRef " + sr; s = (s == null) ? msg : s + " " + msg; s = (ex == null) ? s : s + " (" + ex + ")"; switch (level) { case LOG_DEBUG: System.out.println("DEBUG: " + s); break; case LOG_ERROR: System.out.println("ERROR: " + s); if (ex != null) { if ((ex instanceof BundleException) && (((BundleException) ex).getNestedException() != null)) { ex = ((BundleException) ex).getNestedException(); } ex.printStackTrace(); } break; case LOG_INFO: System.out.println("INFO: " + s); break; case LOG_WARNING: System.out.println("WARNING: " + s); break; default: System.out.println("UNKNOWN[" + level + "]: " + s); } } /** * Change the current log level. Log level decides what messages gets * logged. Any message with a log level higher than the currently set * log level is not logged. * * @param logLevel new log level */ public void setLogLevel(int logLevel) { synchronized (m_loggerRefs) { m_logLevel = logLevel; } } /** * @return current log level. */ public int getLogLevel() { synchronized (m_loggerRefs) { return m_logLevel; } } }