package org.jolokia.osgi;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Dictionary;
import java.util.Hashtable;
import javax.servlet.ServletException;
import org.jolokia.config.ConfigKey;
import org.jolokia.osgi.security.*;
import org.jolokia.osgi.servlet.JolokiaContext;
import org.jolokia.osgi.servlet.JolokiaServlet;
import org.jolokia.restrictor.Restrictor;
import org.jolokia.util.NetworkUtil;
import org.osgi.framework.*;
import org.osgi.service.cm.Configuration;
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.http.*;
import org.osgi.service.log.LogService;
import org.osgi.util.tracker.ServiceTracker;
import org.osgi.util.tracker.ServiceTrackerCustomizer;
import static org.jolokia.config.ConfigKey.*;
/*
* Copyright 2009-2013 Roland Huss
*
* Licensed 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.
*/
/**
* OSGi Activator for the Jolokia Agent
*
* @author roland
* @since Dec 27, 2009
*/
public class JolokiaActivator implements BundleActivator, JolokiaContext {
// Base filter to use for filtering out HttpServices
public static final String HTTP_SERVICE_FILTER_BASE =
"(" + Constants.OBJECTCLASS + "=" + HttpService.class.getName() + ")";
// Context associated with this activator
private BundleContext bundleContext;
// Tracker for HttpService
private ServiceTracker httpServiceTracker;
// Tracker for ConfigAdmin Service
private ServiceTracker configAdminTracker;
// Prefix used for configuration values
private static final String CONFIG_PREFIX = "org.jolokia";
// Prefix used for ConfigurationAdmin pid
private static final String CONFIG_ADMIN_PID = "org.jolokia.osgi";
// HttpContext used for authorization
private HttpContext jolokiaHttpContext;
// Registration object for this JolokiaContext
private ServiceRegistration jolokiaServiceRegistration;
// Restrictor and associated service tracker when tracking restrictor
// services
private Restrictor restrictor = null;
/** {@inheritDoc} */
public void start(BundleContext pBundleContext) {
bundleContext = pBundleContext;
//Track ConfigurationAdmin service
configAdminTracker = new ServiceTracker(pBundleContext,
"org.osgi.service.cm.ConfigurationAdmin",
null);
configAdminTracker.open();
if (Boolean.parseBoolean(getConfiguration(USE_RESTRICTOR_SERVICE))) {
// If no restrictor is set in the constructor and we are enabled to listen for a restrictor
// service, a delegating restrictor is installed
restrictor = new DelegatingRestrictor(bundleContext);
}
// Track HttpService
if (Boolean.parseBoolean(getConfiguration(LISTEN_FOR_HTTP_SERVICE))) {
httpServiceTracker = new ServiceTracker(pBundleContext,
buildHttpServiceFilter(pBundleContext),
new HttpServiceCustomizer(pBundleContext));
httpServiceTracker.open();
// Register us as JolokiaContext
jolokiaServiceRegistration = pBundleContext.registerService(JolokiaContext.class.getCanonicalName(), this, null);
}
}
/** {@inheritDoc} */
public void stop(BundleContext pBundleContext) {
assert pBundleContext.equals(bundleContext);
if (httpServiceTracker != null) {
// Closing the tracker will also call {@link HttpServiceCustomizer#removedService()}
// for every active service which in turn unregisters the servlet
httpServiceTracker.close();
httpServiceTracker = null;
}
if (jolokiaServiceRegistration != null) {
jolokiaServiceRegistration.unregister();
jolokiaServiceRegistration = null;
}
//Shut this down last to make sure nobody calls for a property after this is shutdown
if (configAdminTracker != null) {
configAdminTracker.close();
configAdminTracker = null;
}
restrictor = null;
bundleContext = null;
}
/**
* Get the security context for out servlet. Dependent on the configuration,
* this is either a no-op context or one which authenticates with a given user
*
* @return the HttpContext with which the agent servlet gets registered.
*/
public synchronized HttpContext getHttpContext() {
if (jolokiaHttpContext == null) {
final String user = getConfiguration(USER);
if (user == null) {
jolokiaHttpContext = new DefaultHttpContext();
} else {
jolokiaHttpContext = new BasicAuthenticationHttpContext(getConfiguration(REALM),
createAuthenticator());
}
}
return jolokiaHttpContext;
}
/**
* Get the servlet alias under which the agent servlet is registered
* @return get the servlet alias
*/
public String getServletAlias() {
return getConfiguration(AGENT_CONTEXT);
}
// ==================================================================================
// Customizer for registering servlet at a HttpService
private Dictionary<String,String> getConfiguration() {
Dictionary<String,String> config = new Hashtable<String,String>();
for (ConfigKey key : ConfigKey.values()) {
String value = getConfiguration(key);
if (value != null) {
config.put(key.getKeyValue(),value);
}
}
String jolokiaId = NetworkUtil.replaceExpression(config.get(ConfigKey.AGENT_ID.getKeyValue()));
if (jolokiaId == null) {
config.put(ConfigKey.AGENT_ID.getKeyValue(),
NetworkUtil.getAgentId(hashCode(),"osgi"));
}
config.put(ConfigKey.AGENT_TYPE.getKeyValue(),"osgi");
return config;
}
private String getConfiguration(ConfigKey pKey) {
// TODO: Use fragments if available.
String value = getConfigurationFromConfigAdmin(pKey);
if (value == null) {
value = bundleContext.getProperty(CONFIG_PREFIX + "." + pKey.getKeyValue());
}
if (value == null) {
value = pKey.getDefaultValue();
}
return value;
}
private String getConfigurationFromConfigAdmin(ConfigKey pkey) {
ConfigurationAdmin configAdmin = (ConfigurationAdmin) configAdminTracker.getService();
if (configAdmin == null) {
return null;
}
try {
Configuration config = configAdmin.getConfiguration(CONFIG_ADMIN_PID);
if (config == null) {
return null;
}
Dictionary<?, ?> props = config.getProperties();
if (props == null) {
return null;
}
return (String) props.get(CONFIG_PREFIX + "." + pkey.getKeyValue());
} catch (IOException e) {
return null;
}
}
private Filter buildHttpServiceFilter(BundleContext pBundleContext) {
String customFilter = getConfiguration(ConfigKey.HTTP_SERVICE_FILTER);
String filter = customFilter != null && customFilter.trim().length() > 0 ?
"(&" + HTTP_SERVICE_FILTER_BASE + customFilter + ")" :
HTTP_SERVICE_FILTER_BASE;
try {
return pBundleContext.createFilter(filter);
} catch (InvalidSyntaxException e) {
throw new IllegalArgumentException("Unable to parse filter " + filter,e);
}
}
private Authenticator createAuthenticator() {
Authenticator authenticator = createCustomAuthenticator();
if (authenticator == null) {
authenticator = createAuthenticatorFromAuthMode();
}
return authenticator;
}
private Authenticator createCustomAuthenticator() {
final String authenticatorClass = getConfiguration(ConfigKey.AUTH_CLASS);
if (authenticatorClass != null) {
try {
Class<?> authClass = Class.forName(authenticatorClass);
if (!Authenticator.class.isAssignableFrom(authClass)) {
throw new IllegalArgumentException("Provided authenticator class [" + authenticatorClass +
"] is not a subclass of Authenticator");
}
return lookupAuthenticator(authClass);
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException("Cannot find authenticator class", e);
}
}
return null;
}
private Authenticator lookupAuthenticator(final Class<?> pAuthClass) {
Authenticator authenticator = null;
try {
// prefer constructor that takes configuration
try {
final Constructor<?> constructorThatTakesConfiguration = pAuthClass.getConstructor(Configuration.class);
authenticator = (Authenticator) constructorThatTakesConfiguration.newInstance(getConfiguration());
} catch (NoSuchMethodException ignore) {
// Next try
authenticator = lookupAuthenticatorWithDefaultConstructor(pAuthClass, ignore);
} catch (InvocationTargetException e) {
throw new IllegalArgumentException("Cannot create an instance of custom authenticator class with configuration", e);
}
} catch (InstantiationException e) {
throw new IllegalArgumentException("Cannot create an instance of custom authenticator class", e);
} catch (IllegalAccessException e) {
throw new IllegalArgumentException("Cannot create an instance of custom authenticator class", e);
}
return authenticator;
}
private Authenticator lookupAuthenticatorWithDefaultConstructor(final Class<?> pAuthClass, final NoSuchMethodException ignore)
throws InstantiationException, IllegalAccessException {
// fallback to default constructor
try {
final Constructor<?> defaultConstructor = pAuthClass.getConstructor();
return (Authenticator) defaultConstructor.newInstance();
} catch (NoSuchMethodException e) {
e.initCause(ignore);
throw new IllegalArgumentException("Cannot create an instance of custom authenticator class, no default constructor to use", e);
} catch (InvocationTargetException e) {
e.initCause(ignore);
throw new IllegalArgumentException("Cannot create an instance of custom authenticator using default constructor", e);
}
}
private Authenticator createAuthenticatorFromAuthMode() {
Authenticator authenticator;
final String authMode = getConfiguration(AUTH_MODE);
if ("basic".equalsIgnoreCase(authMode)) {
authenticator = new BasicAuthenticator(getConfiguration(USER),getConfiguration(PASSWORD));
} else if ("jaas".equalsIgnoreCase(authMode)) {
authenticator = new JaasAuthenticator(getConfiguration(REALM));
} else {
throw new IllegalArgumentException("Unknown authentication method '" + authMode + "' configured");
}
return authenticator;
}
// =============================================================================
private class HttpServiceCustomizer implements ServiceTrackerCustomizer {
private final BundleContext context;
HttpServiceCustomizer(BundleContext pContext) {
context = pContext;
}
/** {@inheritDoc} */
public Object addingService(ServiceReference reference) {
HttpService service = (HttpService) context.getService(reference);
try {
service.registerServlet(getServletAlias(),
new JolokiaServlet(context,restrictor),
getConfiguration(),
getHttpContext());
} catch (ServletException e) {
logError("Servlet Exception: " + e, e);
} catch (NamespaceException e) {
logError("Namespace Exception: " + e, e);
}
return service;
}
/** {@inheritDoc} */
public void modifiedService(ServiceReference reference, Object service) {
}
/** {@inheritDoc} */
public void removedService(ServiceReference reference, Object service) {
HttpService httpService = (HttpService) service;
httpService.unregister(getServletAlias());
}
}
@SuppressWarnings("PMD.SystemPrintln")
private void logError(String message,Throwable throwable) {
ServiceReference lRef = bundleContext.getServiceReference(LogService.class.getName());
if (lRef != null) {
try {
LogService logService = (LogService) bundleContext.getService(lRef);
if (logService != null) {
logService.log(LogService.LOG_ERROR,message,throwable);
return;
}
} finally {
bundleContext.ungetService(lRef);
}
}
System.err.println("Jolokia-Error: " + message + " : " + throwable.getMessage());
}
}