blob: 798ae15bd6d53b2c45b8c2071b88a76b8e233194 [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.webconsole.internal.servlet;
import java.io.IOException;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import javax.servlet.GenericServlet;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.felix.webconsole.AbstractWebConsolePlugin;
import org.apache.felix.webconsole.Action;
import org.apache.felix.webconsole.Render;
import org.apache.felix.webconsole.WebConsoleConstants;
import org.apache.felix.webconsole.internal.Logger;
import org.apache.felix.webconsole.internal.OsgiManagerPlugin;
import org.apache.felix.webconsole.internal.Util;
import org.apache.felix.webconsole.internal.core.BundlesServlet;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.http.HttpContext;
import org.osgi.service.http.HttpService;
import org.osgi.service.log.LogService;
import org.osgi.util.tracker.ServiceTracker;
/**
* The <code>OSGi Manager</code> TODO
*
* @scr.component ds="no" label="%manager.name"
* description="%manager.description"
*/
public class OsgiManager extends GenericServlet
{
/** Pseudo class version ID to keep the IDE quite. */
private static final long serialVersionUID = 1L;
public static final String ATTR_LABEL_MAP = OsgiManager.class.getName() + ".labelMap";
public static final String ATTR_APP_ROOT = OsgiManager.class.getName() + ".appRoot";
/**
* The name and value of a parameter which will prevent redirection to a
* render after the action has been executed (value is "_noredir_"). This
* may be used by programmatic action submissions.
*/
public static final String PARAM_NO_REDIRECT_AFTER_ACTION = "_noredir_";
/**
* @scr.property valueRef="DEFAULT_MANAGER_ROOT"
*/
private static final String PROP_MANAGER_ROOT = "manager.root";
/**
* @scr.property valueRef="DEFAULT_PAGE"
*/
private static final String PROP_DEFAULT_RENDER = "default.render";
/**
* @scr.property valueRef="DEFAULT_REALM"
*/
private static final String PROP_REALM = "realm";
/**
* @scr.property valueRef="DEFAULT_USER_NAME"
*/
private static final String PROP_USER_NAME = "username";
/**
* @scr.property valueRef="DEFAULT_PASSWORD"
*/
private static final String PROP_PASSWORD = "password";
private static final String DEFAULT_PAGE = BundlesServlet.NAME;
private static final String DEFAULT_REALM = "OSGi Management Console";
private static final String DEFAULT_USER_NAME = "admin";
private static final String DEFAULT_PASSWORD = "admin";
/**
* The default value for the {@link #PROP_MANAGER_ROOT} configuration
* property (value is "/system/console").
*/
private static final String DEFAULT_MANAGER_ROOT = "/system/console";
private static final String[] PLUGIN_CLASSES =
{ "org.apache.felix.webconsole.internal.compendium.ComponentConfigurationPrinter",
"org.apache.felix.webconsole.internal.compendium.ComponentsServlet",
"org.apache.felix.webconsole.internal.compendium.ConfigManager",
"org.apache.felix.webconsole.internal.core.BundlesServlet",
"org.apache.felix.webconsole.internal.core.InstallAction",
"org.apache.felix.webconsole.internal.core.SetStartLevelAction",
"org.apache.felix.webconsole.internal.misc.LicenseServlet",
"org.apache.felix.webconsole.internal.misc.ConfigurationRender",
"org.apache.felix.webconsole.internal.misc.ShellServlet",
"org.apache.felix.webconsole.internal.obr.BundleRepositoryRender",
"org.apache.felix.webconsole.internal.obr.InstallFromRepoAction",
"org.apache.felix.webconsole.internal.obr.RefreshRepoAction",
"org.apache.felix.webconsole.internal.system.GCAction",
"org.apache.felix.webconsole.internal.system.ShutdownAction",
"org.apache.felix.webconsole.internal.system.ShutdownRender",
"org.apache.felix.webconsole.internal.system.VMStatRender", };
private BundleContext bundleContext;
private Logger log;
private ServiceTracker httpServiceTracker;
private HttpService httpService;
private ServiceTracker operationsTracker;
private ServiceTracker rendersTracker;
private ServiceTracker pluginsTracker;
private ServiceRegistration configurationListener;
private Map plugins = new HashMap();
private Map labelMap = new HashMap();
private Map operations = new HashMap();
private Servlet defaultRender;
private String defaultRenderName;
private String webManagerRoot;
private Dictionary configuration;
public OsgiManager( BundleContext bundleContext )
{
this.bundleContext = bundleContext;
this.log = new Logger( bundleContext );
updateConfiguration( null );
try
{
this.configurationListener = ConfigurationListener.create( this );
}
catch ( Throwable t )
{
// might be caused by CM not available
}
// track renders and operations
operationsTracker = new OperationServiceTracker( this );
operationsTracker.open();
rendersTracker = new RenderServiceTracker( this );
rendersTracker.open();
pluginsTracker = new PluginServiceTracker( this );
pluginsTracker.open();
httpServiceTracker = new HttpServiceTracker( this );
httpServiceTracker.open();
ClassLoader classLoader = getClass().getClassLoader();
for ( int i = 0; i < PLUGIN_CLASSES.length; i++ )
{
String pluginClassName = PLUGIN_CLASSES[i];
try
{
Class pluginClass = classLoader.loadClass( pluginClassName );
Object plugin = pluginClass.newInstance();
if ( plugin instanceof OsgiManagerPlugin )
{
( ( OsgiManagerPlugin ) plugin ).activate( bundleContext );
}
if ( plugin instanceof AbstractWebConsolePlugin )
{
AbstractWebConsolePlugin amp = ( AbstractWebConsolePlugin ) plugin;
bindServlet( amp.getLabel(), amp );
}
else
{
if ( plugin instanceof Action )
{
bindOperation( ( Action ) plugin );
}
if ( plugin instanceof Render )
{
bindRender( ( Render ) plugin );
}
}
}
catch ( Throwable t )
{
log( "Failed to instantiate plugin " + pluginClassName + ". Reason: " + t );
}
}
}
public void dispose()
{
if ( configurationListener != null )
{
configurationListener.unregister();
configurationListener = null;
}
if ( operationsTracker != null )
{
operationsTracker.close();
operationsTracker = null;
}
if ( rendersTracker != null )
{
rendersTracker.close();
rendersTracker = null;
}
if ( pluginsTracker != null )
{
pluginsTracker.close();
pluginsTracker = null;
}
if ( httpServiceTracker != null )
{
httpServiceTracker.close();
httpServiceTracker = null;
}
// deactivate any remaining plugins
for ( Iterator pi = plugins.values().iterator(); pi.hasNext(); )
{
Object plugin = pi.next();
if ( plugin instanceof OsgiManagerPlugin )
{
( ( OsgiManagerPlugin ) plugin ).deactivate();
}
}
// simply remove all operations, we should not be used anymore
this.defaultRender = null;
this.plugins.clear();
this.labelMap.clear();
this.operations.clear();
if ( log != null )
{
log.dispose();
}
this.bundleContext = null;
}
public void service( ServletRequest req, ServletResponse res ) throws ServletException, IOException
{
HttpServletRequest request = ( HttpServletRequest ) req;
HttpServletResponse response = ( HttpServletResponse ) res;
// handle the request action, terminate if done
if ( this.handleAction( request, response ) )
{
return;
}
// check whether we are not at .../{webManagerRoot}
if ( request.getPathInfo() == null || request.getPathInfo().equals( "/" ) )
{
String path = request.getRequestURI();
if ( !path.endsWith( "/" ) )
{
path = path.concat( "/" );
}
path = path.concat( defaultRenderName );
response.sendRedirect( path );
return;
}
String label = request.getPathInfo();
int slash = label.indexOf( "/", 1 );
if ( slash < 2 )
{
slash = label.length();
}
label = label.substring( 1, slash );
Servlet plugin = ( Servlet ) plugins.get( label );
if ( plugin != null )
{
req.setAttribute( ATTR_LABEL_MAP, labelMap );
req.setAttribute( ATTR_APP_ROOT, request.getContextPath() + request.getServletPath() );
plugin.service( req, res );
}
else
{
response.sendError( HttpServletResponse.SC_NOT_FOUND );
return;
}
}
protected boolean handleAction( HttpServletRequest req, HttpServletResponse resp ) throws IOException
{
// check action
String actionName = AbstractWebConsolePlugin.getParameter( req, Util.PARAM_ACTION );
if ( actionName != null )
{
Action action = ( Action ) this.operations.get( actionName );
if ( action != null )
{
boolean redirect = true;
try
{
redirect = action.performAction( req, resp );
}
catch ( IOException ioe )
{
this.log( ioe.getMessage(), ioe );
}
catch ( ServletException se )
{
this.log( se.getMessage(), se.getRootCause() );
}
// maybe overwrite redirect
if ( PARAM_NO_REDIRECT_AFTER_ACTION.equals( AbstractWebConsolePlugin.getParameter( req,
PARAM_NO_REDIRECT_AFTER_ACTION ) ) )
{
resp.setStatus( HttpServletResponse.SC_OK );
resp.setContentType( "text/html" );
resp.getWriter().println( "Ok" );
return true;
}
if ( redirect )
{
String uri = req.getRequestURI();
// Object pars =
// req.getAttribute(Action.ATTR_REDIRECT_PARAMETERS);
// if (pars instanceof String) {
// uri += "?" + pars;
// }
resp.sendRedirect( uri );
}
return true;
}
}
return false;
}
BundleContext getBundleContext()
{
return bundleContext;
}
private static class HttpServiceTracker extends ServiceTracker
{
private final OsgiManager osgiManager;
HttpServiceTracker( OsgiManager osgiManager )
{
super( osgiManager.getBundleContext(), HttpService.class.getName(), null );
this.osgiManager = osgiManager;
}
public Object addingService( ServiceReference reference )
{
Object operation = super.addingService( reference );
if ( operation instanceof HttpService )
{
osgiManager.bindHttpService( ( HttpService ) operation );
}
return operation;
}
public void removedService( ServiceReference reference, Object service )
{
if ( service instanceof HttpService )
{
osgiManager.unbindHttpService( ( HttpService ) service );
}
super.removedService( reference, service );
}
}
private static class OperationServiceTracker extends ServiceTracker
{
private final OsgiManager osgiManager;
OperationServiceTracker( OsgiManager osgiManager )
{
super( osgiManager.getBundleContext(), Action.SERVICE, null );
this.osgiManager = osgiManager;
}
public Object addingService( ServiceReference reference )
{
Object operation = super.addingService( reference );
if ( operation instanceof Action )
{
osgiManager.bindOperation( ( Action ) operation );
}
return operation;
}
public void removedService( ServiceReference reference, Object service )
{
if ( service instanceof Action )
{
osgiManager.bindOperation( ( Action ) service );
}
super.removedService( reference, service );
}
}
private static class RenderServiceTracker extends ServiceTracker
{
private final OsgiManager osgiManager;
RenderServiceTracker( OsgiManager osgiManager )
{
super( osgiManager.getBundleContext(), Render.SERVICE, null );
this.osgiManager = osgiManager;
}
public Object addingService( ServiceReference reference )
{
Object operation = super.addingService( reference );
if ( operation instanceof Render )
{
osgiManager.bindRender( ( Render ) operation );
}
return operation;
}
public void removedService( ServiceReference reference, Object service )
{
if ( service instanceof Render )
{
osgiManager.bindRender( ( Render ) service );
}
super.removedService( reference, service );
}
}
private static class PluginServiceTracker extends ServiceTracker
{
private final OsgiManager osgiManager;
PluginServiceTracker( OsgiManager osgiManager )
{
super( osgiManager.getBundleContext(), WebConsoleConstants.SERVICE_NAME, null );
this.osgiManager = osgiManager;
}
public Object addingService( ServiceReference reference )
{
Object label = reference.getProperty( WebConsoleConstants.PLUGIN_LABEL );
if ( label instanceof String )
{
Object operation = super.addingService( reference );
if ( operation instanceof Servlet )
{
// TODO: check reference properties !!
osgiManager.bindServlet( ( String ) label, ( Servlet ) operation );
}
return operation;
}
return null;
}
public void removedService( ServiceReference reference, Object service )
{
Object label = reference.getProperty( WebConsoleConstants.PLUGIN_LABEL );
if ( label instanceof String )
{
// TODO: check reference properties !!
osgiManager.unbindServlet( ( String ) label );
}
super.removedService( reference, service );
}
}
protected synchronized void bindHttpService( HttpService httpService )
{
Dictionary config = getConfiguration();
// get authentication details
String realm = this.getProperty( config, PROP_REALM, DEFAULT_REALM );
String userId = this.getProperty( config, PROP_USER_NAME, DEFAULT_USER_NAME );
String password = this.getProperty( config, PROP_PASSWORD, DEFAULT_PASSWORD );
// register the servlet and resources
try
{
HttpContext httpContext = new OsgiManagerHttpContext( httpService, realm, userId, password );
Dictionary servletConfig = toStringConfig( config );
httpService.registerServlet( this.webManagerRoot, this, servletConfig, httpContext );
httpService.registerResources( this.webManagerRoot + "/res", "/res", httpContext );
}
catch ( Exception e )
{
log.log( LogService.LOG_ERROR, "Problem setting up", e );
}
this.httpService = httpService;
}
protected synchronized void unbindHttpService( HttpService httpService )
{
httpService.unregister( this.webManagerRoot + "/res" );
httpService.unregister( this.webManagerRoot );
if ( this.httpService == httpService )
{
this.httpService = null;
}
}
protected void bindServlet( String label, Servlet servlet )
{
try
{
servlet.init( getServletConfig() );
plugins.put( label, servlet );
if ( servlet instanceof GenericServlet )
{
String title = ( ( GenericServlet ) servlet ).getServletName();
if ( title != null )
{
labelMap.put( label, title );
}
}
if ( this.defaultRender == null )
{
this.defaultRender = servlet;
}
else if ( label.equals( this.defaultRenderName ) )
{
this.defaultRender = servlet;
}
}
catch ( ServletException se )
{
// TODO: log
}
}
protected void unbindServlet( String label )
{
Servlet servlet = ( Servlet ) plugins.remove( label );
if ( servlet != null )
{
labelMap.remove( label );
if ( this.defaultRender == servlet )
{
if ( this.plugins.isEmpty() )
{
this.defaultRender = null;
}
else
{
this.defaultRender = ( Servlet ) plugins.values().iterator().next();
}
}
servlet.destroy();
}
}
protected void bindOperation( Action operation )
{
operations.put( operation.getName(), operation );
}
protected void unbindOperation( Action operation )
{
operations.remove( operation.getName() );
}
protected void bindRender( Render render )
{
RenderBridge bridge = new RenderBridge( render );
bridge.activate( getBundleContext() );
bindServlet( render.getName(), bridge );
}
protected void unbindRender( Render render )
{
unbindServlet( render.getName() );
}
private Dictionary getConfiguration()
{
return configuration;
}
void updateConfiguration( Dictionary config )
{
if ( config == null )
{
config = new Hashtable();
}
configuration = config;
defaultRenderName = getProperty( config, PROP_DEFAULT_RENDER, DEFAULT_PAGE );
if ( defaultRenderName != null && plugins.get( defaultRenderName ) != null )
{
defaultRender = ( Servlet ) plugins.get( defaultRenderName );
}
// get the web manager root path
webManagerRoot = this.getProperty( config, PROP_MANAGER_ROOT, DEFAULT_MANAGER_ROOT );
if ( !webManagerRoot.startsWith( "/" ) )
{
webManagerRoot = "/" + webManagerRoot;
}
// might update http service registration
HttpService httpService = this.httpService;
if ( httpService != null )
{
synchronized ( this )
{
unbindHttpService( httpService );
bindHttpService( httpService );
}
}
}
/**
* Returns the named property from the configuration. If the property does
* not exist, the default value <code>def</code> is returned.
*
* @param config The properties from which to returned the named one
* @param name The name of the property to return
* @param def The default value if the named property does not exist
* @return The value of the named property as a string or <code>def</code>
* if the property does not exist
*/
private String getProperty( Dictionary config, String name, String def )
{
Object value = config.get( name );
if ( value instanceof String )
{
return ( String ) value;
}
if ( value == null )
{
return def;
}
return String.valueOf( value );
}
private Dictionary toStringConfig( Dictionary config )
{
Dictionary stringConfig = new Hashtable();
for ( Enumeration ke = config.keys(); ke.hasMoreElements(); )
{
Object key = ke.nextElement();
stringConfig.put( key.toString(), String.valueOf( config.get( key ) ) );
}
return stringConfig;
}
}