blob: 08d743ec0fe02579e039d7121f117e2340fdbc1d [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.compendium;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Dictionary;
import java.util.Iterator;
import java.util.TreeSet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.felix.scr.Component;
import org.apache.felix.scr.Reference;
import org.apache.felix.scr.ScrService;
import org.apache.felix.webconsole.DefaultVariableResolver;
import org.apache.felix.webconsole.SimpleWebConsolePlugin;
import org.apache.felix.webconsole.WebConsoleUtil;
import org.apache.felix.webconsole.internal.OsgiManagerPlugin;
import org.apache.felix.webconsole.internal.Util;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONWriter;
import org.osgi.framework.Bundle;
import org.osgi.framework.Constants;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import org.osgi.service.cm.Configuration;
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.component.ComponentConstants;
import org.osgi.service.metatype.MetaTypeInformation;
import org.osgi.service.metatype.MetaTypeService;
/**
* ComponentsServlet provides a plugin for managing Service Components Runtime.
*/
public class ComponentsServlet extends SimpleWebConsolePlugin implements OsgiManagerPlugin
{
private static final long serialVersionUID = 1L;
private static final String LABEL = "components";
private static final String TITLE = "%components.pluginTitle";
private static final String CSS[] = { "/res/ui/bundles.css" }; // yes, it's correct!
// actions
private static final String OPERATION = "action";
private static final String OPERATION_ENABLE = "enable";
private static final String OPERATION_DISABLE = "disable";
//private static final String OPERATION_CONFIGURE = "configure";
// needed services
private static final String SCR_SERVICE = "org.apache.felix.scr.ScrService";
private static final String META_TYPE_NAME = "org.osgi.service.metatype.MetaTypeService";
private static final String CONFIGURATION_ADMIN_NAME = "org.osgi.service.cm.ConfigurationAdmin";
// templates
private final String TEMPLATE;
/** Default constructor */
public ComponentsServlet()
{
super(LABEL, TITLE, CSS);
// load templates
TEMPLATE = readTemplateFile( "/templates/components.html" );
}
/**
* @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
*/
protected void doPost( HttpServletRequest request, HttpServletResponse response ) throws IOException
{
final RequestInfo reqInfo = new RequestInfo(request);
if ( reqInfo.component == null && reqInfo.componentRequested ) {
response.sendError(404);
return;
}
if ( !reqInfo.componentRequested ) {
response.sendError(500);
return;
}
String op = request.getParameter( OPERATION );
if ( OPERATION_ENABLE.equals( op ) )
{
reqInfo.component.enable();
}
else if ( OPERATION_DISABLE.equals( op ) )
{
reqInfo.component.disable();
}
final PrintWriter pw = response.getWriter();
response.setContentType( "application/json" );
response.setCharacterEncoding( "UTF-8" );
renderResult( pw, null);
}
/**
* @see org.apache.felix.webconsole.AbstractWebConsolePlugin#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
*/
protected void doGet( HttpServletRequest request, HttpServletResponse response ) throws ServletException,
IOException {
final RequestInfo reqInfo = new RequestInfo(request);
if ( reqInfo.component == null && reqInfo.componentRequested ) {
response.sendError(404);
return;
}
if ( reqInfo.extension.equals("json") )
{
response.setContentType( "application/json" );
response.setCharacterEncoding( "UTF-8" );
this.renderResult(response.getWriter(), reqInfo.component);
// nothing more to do
return;
}
super.doGet( request, response );
}
/**
* @see org.apache.felix.webconsole.AbstractWebConsolePlugin#renderContent(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
*/
protected void renderContent( HttpServletRequest request, HttpServletResponse response ) throws IOException
{
// get request info from request attribute
final RequestInfo reqInfo = getRequestInfo(request);
StringWriter w = new StringWriter();
PrintWriter w2 = new PrintWriter(w);
renderResult( w2, reqInfo.component );
// prepare variables
DefaultVariableResolver vars = ( ( DefaultVariableResolver ) WebConsoleUtil.getVariableResolver( request ) );
vars.put( "__drawDetails__", reqInfo.componentRequested ? Boolean.TRUE : Boolean.FALSE );
vars.put( "__data__", w.toString() );
response.getWriter().print( TEMPLATE );
}
private void renderResult( final PrintWriter pw,
final Component component) throws IOException
{
final JSONWriter jw = new JSONWriter( pw );
try
{
jw.object();
final ScrService scrService = getScrService();
if ( scrService == null )
{
jw.key( "status" );
jw.value( -1 );
}
else
{
final Component[] components = scrService.getComponents();
if ( components == null || components.length == 0 )
{
jw.key( "status" );
jw.value( 0 );
}
else
{
// order components by name
sortComponents( components );
final StringBuffer buffer = new StringBuffer();
buffer.append( components.length );
buffer.append( " component" );
if ( components.length != 1 )
{
buffer.append( 's' );
}
buffer.append( " installed." );
jw.key( "status" );
jw.value( components.length );
// render components
jw.key( "data" );
jw.array();
if ( component != null )
{
component( jw, component, true );
}
else
{
for ( int i = 0; i < components.length; i++ )
{
component( jw, components[i], false );
}
}
jw.endArray();
}
}
jw.endObject();
}
catch ( JSONException je )
{
throw new IOException( je.toString() );
}
}
private void sortComponents( Component[] components )
{
Arrays.sort( components, new Comparator()
{
public int compare( Object o0, Object o1 )
{
final Component c0 = ( Component ) o0;
final Component c1 = ( Component ) o1;
final int nameCmp = c0.getName().compareTo( c1.getName() );
if ( nameCmp != 0 )
{
return nameCmp;
}
return ( c0.getId() < c1.getId() ) ? -1 : ( ( c0.getId() > c1.getId() ) ? 1 : 0 );
}
} );
}
private void component( JSONWriter jw, Component component, boolean details ) throws JSONException
{
String id = String.valueOf( component.getId() );
String name = component.getName();
int state = component.getState();
jw.object();
// component information
jw.key( "id" );
jw.value( id );
jw.key( "name" );
jw.value( name );
jw.key( "state" );
jw.value( ComponentConfigurationPrinter.toStateString( state ) );
jw.key( "stateRaw" );
jw.value( state );
final Dictionary props = component.getProperties();
final String pid = (String) (props != null ? props.get( Constants.SERVICE_PID ) : null);
if ( pid != null )
{
jw.key("pid");
jw.value(pid);
if ( isConfigurable( component.getBundle(), pid ) )
{
jw.key("configurable");
jw.value(pid);
}
}
// component details
if ( details )
{
gatherComponentDetails( jw, component );
}
jw.endObject();
}
private void gatherComponentDetails( JSONWriter jw, Component component ) throws JSONException
{
jw.key( "props" );
jw.array();
keyVal( jw, "Bundle", component.getBundle().getSymbolicName() + " (" + component.getBundle().getBundleId()
+ ")" );
keyVal( jw, "Implementation Class", component.getClassName() );
if (component.getFactory() != null) {
keyVal( jw, "Component Factory Name", component.getFactory() );
}
keyVal( jw, "Default State", component.isDefaultEnabled() ? "enabled" : "disabled" );
keyVal( jw, "Activation", component.isImmediate() ? "immediate" : "delayed" );
try {
keyVal( jw, "Configuration Policy", component.getConfigurationPolicy() );
} catch (Throwable t) {
// missing implementation of said method in the actually bound API
// ignore this and just don't display the information
}
listServices( jw, component );
listReferences( jw, component );
listProperties( jw, component );
jw.endArray();
}
private void listServices( JSONWriter jw, Component component )
{
String[] services = component.getServices();
if ( services == null )
{
return;
}
keyVal( jw, "Service Type", component.isServiceFactory() ? "service factory" : "service" );
JSONArray buf = new JSONArray();
for ( int i = 0; i < services.length; i++ )
{
buf.put( services[i] );
}
keyVal( jw, "Services", buf );
}
private void listReferences( JSONWriter jw, Component component )
{
Reference[] refs = component.getReferences();
if ( refs != null )
{
for ( int i = 0; i < refs.length; i++ )
{
JSONArray buf = new JSONArray();
buf.put( refs[i].isSatisfied() ? "Satisfied" : "Unsatisfied" );
buf.put( "Service Name: " + refs[i].getServiceName());
if ( refs[i].getTarget() != null )
{
buf.put( "Target Filter: " + refs[i].getTarget() );
}
buf.put( "Multiple: " + (refs[i].isMultiple() ? "multiple" : "single" ));
buf.put( "Optional: " + (refs[i].isOptional() ? "optional" : "mandatory" ));
buf.put( "Policy: " + (refs[i].isStatic() ? "static" : "dynamic" ));
// list bound services
ServiceReference[] boundRefs = refs[i].getServiceReferences();
if ( boundRefs != null && boundRefs.length > 0 )
{
for ( int j = 0; j < boundRefs.length; j++ )
{
final StringBuffer b = new StringBuffer();
b.append( "Bound Service ID " );
b.append( boundRefs[j].getProperty( Constants.SERVICE_ID ) );
String name = ( String ) boundRefs[j].getProperty( ComponentConstants.COMPONENT_NAME );
if ( name == null )
{
name = ( String ) boundRefs[j].getProperty( Constants.SERVICE_PID );
if ( name == null )
{
name = ( String ) boundRefs[j].getProperty( Constants.SERVICE_DESCRIPTION );
}
}
if ( name != null )
{
b.append( " (" );
b.append( name );
b.append( ")" );
}
buf.put(b.toString());
}
}
else
{
buf.put( "No Services bound" );
}
keyVal( jw, "Reference " + refs[i].getName(), buf.toString() );
}
}
}
private void listProperties( JSONWriter jw, Component component )
{
Dictionary props = component.getProperties();
if ( props != null )
{
JSONArray buf = new JSONArray();
TreeSet keys = new TreeSet( Util.list( props.keys() ) );
for ( Iterator ki = keys.iterator(); ki.hasNext(); )
{
final String key = ( String ) ki.next();
final StringBuffer b = new StringBuffer();
b.append( key ).append( " = " );
Object prop = props.get( key );
prop = WebConsoleUtil.toString( prop );
b.append( prop );
buf.put(b.toString());
}
keyVal( jw, "Properties", buf );
}
}
private void keyVal( JSONWriter jw, String key, Object value )
{
try
{
WebConsoleUtil.keyVal( jw, key, value );
}
catch ( JSONException je )
{
// don't care
}
}
/**
* Check if the component with the specified pid is
* configurable
* @param providingBundle The Bundle providing the component. This may be
* theoretically be <code>null</code>.
* @param pid A non null pid
* @return <code>true</code> if the component is configurable.
*/
private boolean isConfigurable( final Bundle providingBundle, final String pid )
{
// we first check if the config admin has something for this pid
final ConfigurationAdmin ca = this.getConfigurationAdmin();
if ( ca != null )
{
try
{
// we use listConfigurations to not create configuration
// objects persistently without the user providing actual
// configuration
String filter = "(" + Constants.SERVICE_PID + "=" + pid + ")";
Configuration[] configs = ca.listConfigurations( filter );
if ( configs != null && configs.length > 0 )
{
return true;
}
}
catch ( InvalidSyntaxException ise )
{
// should print message
}
catch ( IOException ioe )
{
// should print message
}
}
// second check is using the meta type service
if ( providingBundle != null )
{
final MetaTypeService mts = this.getMetaTypeService();
if ( mts != null )
{
final MetaTypeInformation mti = mts.getMetaTypeInformation( providingBundle );
if ( mti != null )
{
return mti.getObjectClassDefinition( pid, null ) != null;
}
}
}
return false;
}
private final ConfigurationAdmin getConfigurationAdmin()
{
return ( ConfigurationAdmin ) getService( CONFIGURATION_ADMIN_NAME );
}
final ScrService getScrService()
{
return ( ScrService ) getService( SCR_SERVICE );
}
private final MetaTypeService getMetaTypeService()
{
return ( MetaTypeService ) getService( META_TYPE_NAME );
}
private final class RequestInfo
{
public final String extension;
public final Component component;
public final boolean componentRequested;
protected RequestInfo( final HttpServletRequest request )
{
String info = request.getPathInfo();
// remove label and starting slash
info = info.substring( getLabel().length() + 1 );
// get extension
if ( info.endsWith( ".json" ) )
{
extension = "json";
info = info.substring( 0, info.length() - 5 );
}
else
{
extension = "html";
}
if ( info.length() > 0 && info.startsWith( "/" ) )
{
this.componentRequested = true;
info = info.substring( 1 );
Component component = getComponentId( info );
if ( component == null )
{
component = getComponentByName( info );
}
this.component = component;
}
else
{
this.componentRequested = false;
this.component = null;
}
request.setAttribute( ComponentsServlet.this.getClass().getName(), this );
}
protected Component getComponentId( final String componentIdPar )
{
final ScrService scrService = getScrService();
if ( scrService != null )
{
try
{
final long componentId = Long.parseLong( componentIdPar );
return scrService.getComponent( componentId );
}
catch ( NumberFormatException nfe )
{
// don't care
}
}
return null;
}
protected Component getComponentByName( final String names )
{
if ( names.length() > 0 )
{
final ScrService scrService = getScrService();
if ( scrService != null )
{
final int slash = names.lastIndexOf( '/' );
final String componentName;
final String pid;
if ( slash > 0 )
{
componentName = names.substring( 0, slash );
pid = names.substring( slash + 1 );
}
else
{
componentName = names;
pid = null;
}
Component[] components;
try
{
components = scrService.getComponents( componentName );
}
catch ( Throwable t )
{
// not implemented in the used API versio
components = null;
}
if ( components != null )
{
if ( pid != null )
{
for ( int i = 0; i < components.length; i++ )
{
Component component = components[i];
if ( pid.equals( component.getProperties().get( Constants.SERVICE_PID ) ) )
{
return component;
}
}
}
else if ( components.length > 0 )
{
return components[0];
}
}
}
}
return null;
}
}
static RequestInfo getRequestInfo(final HttpServletRequest request)
{
return (RequestInfo)request.getAttribute(ComponentsServlet.class.getName());
}
}