blob: a90eb7c93a6c10bafba6f4759d34273da47ff8af [file] [log] [blame]
/*
* 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.log;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Dictionary;
import java.util.List;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.Filter;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration;
import org.osgi.framework.wiring.BundleWiring;
import org.osgi.service.log.LogLevel;
import org.osgi.util.tracker.ServiceTracker;
import org.osgi.util.tracker.ServiceTrackerCustomizer;
public class ConfigurationListenerImpl {
private static final String CONFIGURATION_CLASS = "org.osgi.service.cm.Configuration";
private static final String CONFIGURATION_ADMIN_CLASS = "org.osgi.service.cm.ConfigurationAdmin";
private static final String CONFIGURATION_EVENT_CLASS = "org.osgi.service.cm.ConfigurationEvent";
private static final String CONFIGURATION_LISTENER_CLASS = "org.osgi.service.cm.ConfigurationListener";
private static final String LOGGER_ADMIN_PID = "org.osgi.service.log.admin";
private static final String LOGGER_ADMIN_PID_PREFIX = LOGGER_ADMIN_PID + '|';
/** ConfigurationAdmin tracker */
final ServiceTracker<?, ?> m_cmtracker;
final BundleContext m_context;
final Log m_log;
final LoggerAdminImpl m_loggerAdmin;
public ConfigurationListenerImpl(final BundleContext context, final Log log, final LoggerAdminImpl loggerAdmin) throws Exception {
m_context = context;
m_log = log;
m_loggerAdmin = loggerAdmin;
Filter filter = context.createFilter(String.format("(%s=%s)", Constants.OBJECTCLASS, CONFIGURATION_ADMIN_CLASS));
m_cmtracker = new ServiceTracker<>(m_context, filter, new CLCustomizer());
m_cmtracker.open();
}
public void close() {
m_cmtracker.close();
}
class CLProxy {
private final Object m_ca;
private final ServiceReference<?> m_caReference;
private final ServiceRegistration<?> m_registration;
private final Class<?> m_caClass;
private final Class<?> m_ceClass;
private final Class<?> m_clClass;
private final Class<?> m_configurationClass;
private final Method m_caGetConfiguration;
private final Method m_caListConfigurations;
private final Method m_ceGetPid;
private final Method m_ceGetType;
private final Method m_clConfigurationEvent;
private final Method m_configurationGetProperties;
private final Method m_configurationGetProcessedProperties;
public CLProxy(ServiceReference<?> caReference) {
m_caReference = caReference;
m_ca = m_context.getService(m_caReference);
try {
m_caClass = m_caReference.getBundle().loadClass(CONFIGURATION_ADMIN_CLASS);
m_caGetConfiguration = m_caClass.getMethod("getConfiguration", String.class, String.class);
m_caListConfigurations = m_caClass.getMethod("listConfigurations", String.class);
m_configurationClass = m_caReference.getBundle().loadClass(CONFIGURATION_CLASS);
m_configurationGetProperties = m_configurationClass.getMethod("getProperties");
Method configurationGetProcessedProperties = null;
try {
// try for the 1.6 method
configurationGetProcessedProperties = m_configurationClass.getMethod("getProcessedProperties", ServiceReference.class);
}
catch (NoSuchMethodException nsme) {
// ignore
}
m_configurationGetProcessedProperties = configurationGetProcessedProperties;
m_ceClass = m_caReference.getBundle().loadClass(CONFIGURATION_EVENT_CLASS);
m_ceGetPid = m_ceClass.getMethod("getPid");
m_ceGetType = m_ceClass.getMethod("getType");
m_clClass = m_caReference.getBundle().loadClass(CONFIGURATION_LISTENER_CLASS);
m_clConfigurationEvent = m_clClass.getMethod("configurationEvent", m_ceClass);
}
catch (ClassNotFoundException | NoSuchMethodException | SecurityException e) {
throw new IllegalStateException(
"Failure reflecting over API from Configuration Admin service bundle", e);
}
m_registration = m_context.registerService(CONFIGURATION_LISTENER_CLASS, configurationListenerProxy(), null);
}
private Object configurationListenerProxy() {
ClassLoader classLoader = m_caReference.getBundle().adapt(BundleWiring.class).getClassLoader();
return Proxy.newProxyInstance(
classLoader, new Class<?>[] {m_clClass},
new InvocationHandler() {
@SuppressWarnings("unchecked")
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.equals(m_clConfigurationEvent)) {
String pid = (String)m_ceGetPid.invoke(args[0]);
if(!pid.startsWith(LOGGER_ADMIN_PID)) {
return null;
}
String configName = null;
String location = "?";
if (pid.startsWith(LOGGER_ADMIN_PID_PREFIX)) {
configName = pid.substring(LOGGER_ADMIN_PID_PREFIX.length());
if (configName.contains("|") && (configName.split("|").length == 3)) {
String[] parts = configName.split("|");
location = parts[2];
}
}
switch ((int)m_ceGetType.invoke(args[0])) {
case 2: // CM_DELETED
m_loggerAdmin.updateConfiguration(configName, null);
break;
default:
Object configObj = m_caGetConfiguration.invoke(m_ca, pid, location);
Object propertiesObj = getProperties(configObj);
m_loggerAdmin.updateConfiguration(configName, (Dictionary<String, Object>)propertiesObj);
}
}
return null;
}
}
);
}
@SuppressWarnings("unchecked")
private Dictionary<String, Object> getProperties(Object configObj) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
if (m_configurationGetProcessedProperties != null) {
return (Dictionary<String, Object>) m_configurationGetProcessedProperties.invoke(configObj, m_caReference);
}
return (Dictionary<String, Object>) m_configurationGetProperties.invoke(configObj);
}
@SuppressWarnings("rawtypes")
private List<Dictionary> listConfigurations(String filter) {
try {
Object result = m_caListConfigurations.invoke(m_ca, filter);
if (result != null) {
List<Dictionary> dictionaries = new ArrayList<>();
for (Object configObj : (Object[])result) {
dictionaries.add(getProperties(configObj));
}
return dictionaries;
}
}
catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
if (e instanceof InvocationTargetException) {
m_log.log(getClass().getName(), m_context.getBundle(), null, LogLevel.ERROR, "An error occured reflecting on ConfigurationAdmin.", ((InvocationTargetException)e).getTargetException());
}
else {
m_log.log(getClass().getName(), m_context.getBundle(), null, LogLevel.ERROR, "An error occured reflecting on ConfigurationAdmin.", e);
}
}
return Collections.emptyList();
}
private void unregister() {
m_registration.unregister();
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private void updateContext(String name, String pid, boolean delete) {
List<Dictionary> configurations = listConfigurations(String.format("(%s=%s)", Constants.SERVICE_PID, pid));
if (!configurations.isEmpty()) {
m_loggerAdmin.updateConfiguration(name, delete ? null : configurations.get(0));
}
}
}
class CLCustomizer implements ServiceTrackerCustomizer<Object, CLProxy> {
@Override
public CLProxy addingService(ServiceReference<Object> reference) {
CLProxy clProxy = new CLProxy(reference);
// configure ROOT context
clProxy.updateContext(null, "org.osgi.service.log.admin", false);
// configure bundle contexts
for (String name : m_loggerAdmin.getLoggerContextNames()) {
String pid = "org.osgi.service.log.admin|" + name;
clProxy.updateContext(name, pid, false);
}
return clProxy;
}
@Override
public void modifiedService(ServiceReference<Object> reference, CLProxy clProxy) {
}
@Override
public void removedService(ServiceReference<Object> reference, CLProxy clProxy) {
// un-configure bundle contexts
for (String name : m_loggerAdmin.getLoggerContextNames()) {
String pid = "org.osgi.service.log.admin|" + name;
clProxy.updateContext(name, pid, true);
}
// un-configure ROOT context
clProxy.updateContext(null, "org.osgi.service.log.admin", true);
// un-register the configuration listener
clProxy.unregister();
}
}
}