diff options
4 files changed, 298 insertions, 13 deletions
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/log/ConfigAdminListener.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/log/ConfigAdminListener.java new file mode 100644 index 000000000..3c3fbb2ab --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/log/ConfigAdminListener.java @@ -0,0 +1,290 @@ +/******************************************************************************* + * Copyright (c) 2017 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.osgi.internal.log; + +import java.lang.reflect.*; +import java.util.*; +import org.osgi.framework.*; +import org.osgi.service.log.LogLevel; +import org.osgi.util.tracker.ServiceTracker; +import org.osgi.util.tracker.ServiceTrackerCustomizer; + +class ConfigAdminListener implements ServiceTrackerCustomizer<Object, ServiceRegistration<?>> { + + private static final String CLASS_CONFIG_ADMIN = "org.osgi.service.cm.ConfigurationAdmin"; //$NON-NLS-1$ + private static final String METHOD_CONFIG_ADMIN_GET_CONFIGURATION = "getConfiguration"; //$NON-NLS-1$ + private static final String METHOD_CONFIG_ADMIN_LIST_CONFIGURATIONS = "listConfigurations"; //$NON-NLS-1$ + + private static final String CLASS_SYNC_CONFIG_LISTENER = "org.osgi.service.cm.SynchronousConfigurationListener"; //$NON-NLS-1$ + private static final String CLASS_CONFIG_EVENT = "org.osgi.service.cm.ConfigurationEvent"; //$NON-NLS-1$ + private static final String METHOD_CONFIG_EVENT_GET_PID = "getPid"; //$NON-NLS-1$ + private static final String METHOD_CONFIG_EVENT_GET_FACTORY_PID = "getFactoryPid"; //$NON-NLS-1$ + private static final String METHOD_CONFIG_EVENT_GET_REFERENCE = "getReference"; //$NON-NLS-1$ + private static final String METHOD_CONFIG_EVENT_GET_TYPE = "getType"; //$NON-NLS-1$ + private static final int CM_UPDATED = 1; + private static final int CM_DELETED = 2; + private static final int CM_LOCATION_CHANGED = 3; + + private static final String CLASS_CONFIG = "org.osgi.service.cm.Configuration"; //$NON-NLS-1$ + private static final String METHOD_CONFIG_GET_PROPERTIES = "getProperties"; //$NON-NLS-1$ + private static final String METHOD_CONFIG_GET_PID = "getPid"; //$NON-NLS-1$ + private static final String METHOD_CONFIG_GET_FACTORY_PID = "getFactoryPid"; //$NON-NLS-1$ + + private static final String PID_PREFIX_LOG_ADMIN = "org.osgi.service.log.admin"; //$NON-NLS-1$ + private static final String NULL_LEVEL = "NULL"; //$NON-NLS-1$ + // using String constructor here to avoid interning + private static final String PID_FILTER = '(' + Constants.SERVICE_PID + '=' + PID_PREFIX_LOG_ADMIN + '*' + ')'; + + private final ServiceTracker<Object, ServiceRegistration<?>> configTracker; + final ExtendedLogServiceFactory factory; + final BundleContext context; + + ConfigAdminListener(BundleContext context, ExtendedLogServiceFactory factory) { + this.context = context; + this.configTracker = new ServiceTracker<>(context, CLASS_CONFIG_ADMIN, this); + this.factory = factory; + } + + void start() { + configTracker.open(); + } + + void stop() { + configTracker.close(); + } + + private ServiceRegistration<?> registerConfigurationListener(ServiceReference<?> configRef) { + try { + Class<?> listenerClass = configRef.getBundle().loadClass(CLASS_SYNC_CONFIG_LISTENER); + return registerProxyConfigListener(configRef, listenerClass); + } catch (ClassNotFoundException | NoSuchMethodException e) { + throw new RuntimeException(CLASS_SYNC_CONFIG_LISTENER, e); + } + } + + private ServiceRegistration<?> registerProxyConfigListener(ServiceReference<?> configRef, Class<?> listenerClass) throws ClassNotFoundException, NoSuchMethodException { + LoggerContextConfiguration loggerConfiguration = new LoggerContextConfiguration(listenerClass, configRef); + return loggerConfiguration.register(); + } + + @Override + public ServiceRegistration<?> addingService(ServiceReference<Object> configRef) { + return registerConfigurationListener(configRef); + } + + @Override + public void modifiedService(ServiceReference<Object> configRef, ServiceRegistration<?> configReg) { + // Nothing to do + } + + @Override + public void removedService(ServiceReference<Object> configRef, ServiceRegistration<?> loggerConfiguration) { + loggerConfiguration.unregister(); + } + + class LoggerContextConfiguration implements InvocationHandler { + private final Object listenerProxy; + private final Object configAdmin; + private final ServiceReference<?> configAdminRef; + + private final Class<?> configClass; + private final Method getConfigProperties; + private final Method getConfigPid; + private final Method getConfigFactoryPid; + + private final Class<?> configAdminClass; + private final Method getConfiguration; + private final Method listConfigurations; + + private final Class<?> configEventClass; + private final Method getEventPid; + private final Method getEventFactoryPid; + private final Method getEventReference; + private final Method getEventType; + + public LoggerContextConfiguration(Class<?> listenerClass, ServiceReference<?> ref) throws ClassNotFoundException, NoSuchMethodException { + listenerProxy = Proxy.newProxyInstance(listenerClass.getClassLoader(), new Class[] {listenerClass}, this); + configAdminRef = ref; + ClassLoader cl = listenerClass.getClassLoader(); + + configClass = cl.loadClass(CLASS_CONFIG); + getConfigProperties = configClass.getMethod(METHOD_CONFIG_GET_PROPERTIES); + getConfigFactoryPid = configClass.getMethod(METHOD_CONFIG_GET_FACTORY_PID); + getConfigPid = configClass.getMethod(METHOD_CONFIG_GET_PID); + + configAdminClass = cl.loadClass(CLASS_CONFIG_ADMIN); + getConfiguration = configAdminClass.getMethod(METHOD_CONFIG_ADMIN_GET_CONFIGURATION, String.class, String.class); + listConfigurations = configAdminClass.getMethod(METHOD_CONFIG_ADMIN_LIST_CONFIGURATIONS, String.class); + + configEventClass = cl.loadClass(CLASS_CONFIG_EVENT); + getEventPid = configEventClass.getMethod(METHOD_CONFIG_EVENT_GET_PID); + getEventFactoryPid = configEventClass.getMethod(METHOD_CONFIG_EVENT_GET_FACTORY_PID); + getEventReference = configEventClass.getMethod(METHOD_CONFIG_EVENT_GET_REFERENCE); + getEventType = configEventClass.getMethod(METHOD_CONFIG_EVENT_GET_TYPE); + + configAdmin = context.getService(ref); + } + + public ServiceRegistration<?> register() { + // register it with the config admin context to ensure consistent class space + Bundle configBundle = configAdminRef.getBundle(); + BundleContext configContext = configBundle != null ? configAdminRef.getBundle().getBundleContext() : null; + if (configContext == null) { + // seems the bundle has stopped! + return null; + } + ServiceRegistration<?> registration = configContext.registerService(CLASS_SYNC_CONFIG_LISTENER, listenerProxy, null); + + try { + Object[] configs = (Object[]) listConfigurations.invoke(configAdmin, PID_FILTER); + if (configs != null) { + for (Object config : configs) { + String factoryPid = (String) getConfigFactoryPid.invoke(config); + if (factoryPid != null) { + continue; + } + String pid = (String) getConfigPid.invoke(config); + String contextName = getContextName(pid); + @SuppressWarnings("unchecked") + Dictionary<String, Object> configDictionary = (Dictionary<String, Object>) getConfigProperties.invoke(config); + if (configDictionary != null) { + setLogLevels(contextName, getLogLevels(configDictionary)); + } + } + } + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + throw new RuntimeException(e); + } + return registration; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] event) throws Throwable { + // There is only one method on ConfigurationListener, no need to check the method type + // ConfigurationListener::configurationEvent(ConfigurationEvent) + if (!configAdminRef.equals(getReference(event))) { + // ignore other config admin events + return null; + } + + String pid = getEventPid(event); + if (pid == null) { + // a factory pid or more likely doesn't have the correct prefix; ignore + return null; + } + + int type = getType(event); + if (type == CM_LOCATION_CHANGED) { + // TODO not sure if we should check location or not + return null; + } + + String contextName = getContextName(pid); + if (type == CM_DELETED) { + setLogLevels(contextName, Collections.<String, LogLevel> emptyMap()); + return null; + } + + if (type == CM_UPDATED) { + Dictionary<String, Object> configDictionary = findConfiguration(pid); + if (configDictionary == null) { + // Configuration got deleted before we could get it so treat as deleted + setLogLevels(contextName, Collections.<String, LogLevel> emptyMap()); + return null; + } + + Map<String, LogLevel> levelConfig = getLogLevels(configDictionary); + setLogLevels(contextName, levelConfig); + } + return null; + } + + private String getContextName(String pid) { + if (PID_PREFIX_LOG_ADMIN.equals(pid)) { + return null; + } + char separator = pid.charAt(PID_PREFIX_LOG_ADMIN.length()); + if (separator != '|') { + return null; + } + int startName = PID_PREFIX_LOG_ADMIN.length() + 1; + return pid.substring(startName); + } + + private Map<String, LogLevel> getLogLevels(Dictionary<String, Object> configDictionary) { + Map<String, LogLevel> result = new HashMap<>(configDictionary.size()); + for (Enumeration<String> keys = configDictionary.keys(); keys.hasMoreElements();) { + String key = keys.nextElement(); + Object v = configDictionary.get(key); + if (v instanceof String) { + if (NULL_LEVEL.equals(v)) { + result.put(key, null); + } else { + try { + result.put(key, LogLevel.valueOf((String) v)); + } catch (IllegalArgumentException e) { + // ignore invalid values + } + } + } + } + return result; + } + + private Object getReference(Object[] event) { + try { + return getEventReference.invoke(event[0]); + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + throw new RuntimeException(e); + } + } + + private String getEventPid(Object[] event) { + try { + String factoryPid = (String) getEventFactoryPid.invoke(event[0]); + if (factoryPid != null) { + // ignore factory pids + return null; + } + String pid = (String) getEventPid.invoke(event[0]); + if (pid.startsWith(PID_PREFIX_LOG_ADMIN)) { + return pid; + } + return null; + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + throw new RuntimeException(e); + } + } + + private int getType(Object[] event) { + try { + Integer type = (Integer) getEventType.invoke(event[0]); + return type == null ? 0 : type.intValue(); + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + throw new RuntimeException(e); + } + } + + @SuppressWarnings("unchecked") + private Dictionary<String, Object> findConfiguration(String pid) { + try { + Object config = getConfiguration.invoke(configAdmin, pid, null); + return (Dictionary<String, Object>) getConfigProperties.invoke(config); + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + throw new RuntimeException(e); + } + } + + private void setLogLevels(String contextName, Map<String, LogLevel> logLevels) { + factory.getLoggerAdmin().getLoggerContext(contextName).setLogLevels(logLevels); + } + } +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/log/ExtendedLogServiceFactory.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/log/ExtendedLogServiceFactory.java index 11feeb46d..a33f86c38 100644 --- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/log/ExtendedLogServiceFactory.java +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/log/ExtendedLogServiceFactory.java @@ -143,7 +143,7 @@ public class ExtendedLogServiceFactory implements ServiceFactory<ExtendedLogServ } if (level == null && contextName != null) { // non-null context name is a non-root context; - // must not check the root context + // must check the root context for non-root contexts EquinoxLoggerContext rootContext = loggerContextTargetMap.getRootLoggerContext(); if (rootContext != null) { level = rootContext.getEffectiveLogLevel(name); @@ -170,17 +170,6 @@ public class ExtendedLogServiceFactory implements ServiceFactory<ExtendedLogServ @Override public void setLogLevels(Map<String, LogLevel> logLevels) { - if (!setWithConfigAdmin(logLevels)) { - doSetLogLevels(logLevels); - } - } - - private boolean setWithConfigAdmin(Map<String, LogLevel> logLevels) { - // TODO Auto-generated method stub - return false; - } - - private void doSetLogLevels(Map<String, LogLevel> logLevels) { boolean readLocked = false; try { contextsLock.writeLock().lock(); @@ -203,7 +192,7 @@ public class ExtendedLogServiceFactory implements ServiceFactory<ExtendedLogServ @Override public void clear() { - doSetLogLevels(Collections.<String, LogLevel> emptyMap()); + setLogLevels(Collections.<String, LogLevel> emptyMap()); } @Override diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/log/LogServiceManager.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/log/LogServiceManager.java index 7994ad4b5..98baab7fd 100644 --- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/log/LogServiceManager.java +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/log/LogServiceManager.java @@ -33,6 +33,7 @@ public class LogServiceManager implements BundleListener, FrameworkListener, Ser private final ExtendedLogServiceFactory logServiceFactory; private final ExtendedLogServiceImpl systemBundleLog; private EventAdminAdapter eventAdminAdapter; + private ConfigAdminListener configAdminListener; public LogServiceManager(int maxHistory, LogListener... systemListeners) { logReaderServiceFactory = new ExtendedLogReaderServiceFactory(maxHistory); @@ -63,9 +64,13 @@ public class LogServiceManager implements BundleListener, FrameworkListener, Ser eventAdminAdapter = new EventAdminAdapter(context, logReaderServiceFactory); eventAdminAdapter.start(); + configAdminListener = new ConfigAdminListener(context, logServiceFactory); + configAdminListener.start(); } public void stop(BundleContext context) { + configAdminListener.stop(); + configAdminListener = null; eventAdminAdapter.stop(); eventAdminAdapter = null; loggerAdminRegistration.unregister(); diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/log/LoggerContextTargetMap.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/log/LoggerContextTargetMap.java index 15234d6ca..46a2ece04 100755 --- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/log/LoggerContextTargetMap.java +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/log/LoggerContextTargetMap.java @@ -104,6 +104,7 @@ public class LoggerContextTargetMap { logServices.clear(); qualifiedNameToTargets.clear(); targetToQualifiedNames.clear(); + loggerContexts.clear(); } LoggerContext createLoggerContext(String name, ExtendedLogServiceFactory factory) { |