blob: e288b86b47adfea2b66791ed36e9b0216923567d [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.core;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.reflect.Array;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.TreeMap;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.fileupload.FileItem;
import org.apache.felix.framework.util.VersionRange;
import org.apache.felix.utils.json.JSONWriter;
import org.apache.felix.utils.manifest.Clause;
import org.apache.felix.utils.manifest.Parser;
import org.apache.felix.webconsole.AbstractWebConsolePlugin;
import org.apache.felix.webconsole.ConfigurationPrinter;
import org.apache.felix.webconsole.DefaultVariableResolver;
import org.apache.felix.webconsole.SimpleWebConsolePlugin;
import org.apache.felix.webconsole.WebConsoleConstants;
import org.apache.felix.webconsole.WebConsoleUtil;
import org.apache.felix.webconsole.bundleinfo.BundleInfo;
import org.apache.felix.webconsole.bundleinfo.BundleInfoProvider;
import org.apache.felix.webconsole.internal.OsgiManagerPlugin;
import org.apache.felix.webconsole.internal.Util;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.framework.Constants;
import org.osgi.framework.Filter;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration;
import org.osgi.framework.Version;
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.log.LogService;
import org.osgi.service.packageadmin.ExportedPackage;
import org.osgi.service.packageadmin.PackageAdmin;
import org.osgi.service.startlevel.StartLevel;
import org.osgi.util.tracker.ServiceTracker;
/**
* The <code>BundlesServlet</code> provides the bundles plugins, used to display
* the list of bundles, installed on the framework. It also adds ability to control
* the lifecycle of the bundles, like start, stop, uninstall, install.
*/
public class BundlesServlet extends SimpleWebConsolePlugin implements OsgiManagerPlugin, ConfigurationPrinter
{
/** the label of the bundles plugin - used by other plugins to reference to plugin details */
public static final String NAME = "bundles";
private static final String TITLE = "%bundles.pluginTitle";
private static final String CSS[] = { "/res/ui/bundles.css" };
// an LDAP filter, that is used to search manifest headers, see FELIX-1441
private static final String FILTER_PARAM = "filter";
private static final String FIELD_STARTLEVEL = "bundlestartlevel";
private static final String FIELD_START = "bundlestart";
private static final String FIELD_BUNDLEFILE = "bundlefile";
// set to ask for PackageAdmin.refreshPackages() after install/update
private static final String FIELD_REFRESH_PACKAGES = "refreshPackages";
// bootdelegation property entries. wildcards are converted to package
// name prefixes. whether an entry is a wildcard or not is set as a flag
// in the bootPkgWildcards array.
// see #activate and #isBootDelegated
private String[] bootPkgs;
// a flag for each entry in bootPkgs indicating whether the respective
// entry was declared as a wildcard or not
// see #activate and #isBootDelegated
private boolean[] bootPkgWildcards;
private ServiceRegistration configurationPrinter;
private ServiceTracker bundleInfoTracker;
// templates
private final String TEMPLATE_MAIN;
/** Default constructor */
public BundlesServlet()
{
super(NAME, TITLE, CATEGORY_OSGI, CSS);
// load templates
TEMPLATE_MAIN = readTemplateFile( "/templates/bundles.html" );
}
/**
* @see org.apache.felix.webconsole.AbstractWebConsolePlugin#activate(org.osgi.framework.BundleContext)
*/
@Override
public void activate( BundleContext bundleContext )
{
super.activate( bundleContext );
bundleInfoTracker = new ServiceTracker( bundleContext, BundleInfoProvider.class.getName(), null);
bundleInfoTracker.open();
// bootdelegation property parsing from Apache Felix R4SearchPolicyCore
String bootDelegation = bundleContext.getProperty( Constants.FRAMEWORK_BOOTDELEGATION );
bootDelegation = ( bootDelegation == null ) ? "java.*" : bootDelegation + ",java.*";
StringTokenizer st = new StringTokenizer( bootDelegation, " ," );
bootPkgs = new String[st.countTokens()];
bootPkgWildcards = new boolean[bootPkgs.length];
for ( int i = 0; i < bootPkgs.length; i++ )
{
bootDelegation = st.nextToken();
if ( bootDelegation.endsWith( "*" ) )
{
bootPkgWildcards[i] = true;
bootDelegation = bootDelegation.substring( 0, bootDelegation.length() - 1 );
}
bootPkgs[i] = bootDelegation;
}
Hashtable props = new Hashtable();
props.put( WebConsoleConstants.CONFIG_PRINTER_MODES, new String[] { ConfigurationPrinter.MODE_TXT,
ConfigurationPrinter.MODE_ZIP } );
configurationPrinter = bundleContext.registerService( ConfigurationPrinter.SERVICE, this, props );
}
/**
* @see org.apache.felix.webconsole.SimpleWebConsolePlugin#deactivate()
*/
@Override
public void deactivate()
{
if ( configurationPrinter != null )
{
configurationPrinter.unregister();
configurationPrinter = null;
}
if ( bundleInfoTracker != null)
{
bundleInfoTracker.close();
bundleInfoTracker = null;
}
super.deactivate();
}
//---------- ConfigurationPrinter
/**
* @see org.apache.felix.webconsole.ConfigurationPrinter#printConfiguration(java.io.PrintWriter)
*/
@Override
public void printConfiguration( PrintWriter pw )
{
try
{
final Map map = createObjectStructure(null, null, null, true, Locale.ENGLISH, null, null );
pw.println( "Status: " + map.get( "status" ) );
pw.println();
Object[] data = (Object[]) map.get( "data" );
for ( int i = 0; i < data.length; i++ )
{
Map bundle = (Map) data[i];
pw.println( MessageFormat.format( "Bundle {0} - {1} {2} (state: {3})", new Object[]
{ bundle.get( "id" ), bundle.get( "name" ), bundle.get( "version" ), bundle.get( "state" ) } ) );
Object[] props = (Object[]) bundle.get( "props" );
for ( int pi = 0; pi < props.length; pi++ )
{
Map entry = (Map) props[pi];
String key = ( String ) entry.get( "key" );
if ( "nfo".equals( key ) )
{
// BundleInfo (see #bundleInfo & #bundleInfoDetails
Map infos = ( Map ) entry.get( "value" );
Iterator infoKeys = infos.keySet().iterator();
while ( infoKeys.hasNext() )
{
String infoKey = ( String ) infoKeys.next();
pw.println( " " + infoKey + ": " );
Object[] infoA = (Object[]) infos.get(infoKey);
for ( int iai = 0; iai < infoA.length; iai++ )
{
if ( infoA[iai] != null )
{
Map info = (Map) infoA[iai];
pw.println( " " + info.get( "name" ) );
}
}
}
}
else
{
// regular data
pw.print( " " + key + ": " );
Object entryValue = entry.get( "value" );
if ( entryValue.getClass().isArray() )
{
pw.println();
for ( int ei = 0; ei < Array.getLength(entryValue); ei++ )
{
Object o = Array.get(entryValue, ei);
if ( o != null )
{
pw.println( " " + o );
}
}
}
else
{
pw.println( entryValue );
}
}
}
pw.println();
}
}
catch ( Exception e )
{
log( "Problem rendering Bundle details for configuration status", e );
}
}
//---------- BaseWebConsolePlugin
/**
* @see org.apache.felix.webconsole.AbstractWebConsolePlugin#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
*/
@Override
protected void doGet( HttpServletRequest request, HttpServletResponse response ) throws ServletException,
IOException
{
final RequestInfo reqInfo = new RequestInfo(request);
if ( "upload".equals(reqInfo.pathInfo) )
{
super.doGet(request, response);
return;
}
if ( reqInfo.bundle == null && reqInfo.bundleRequested )
{
response.sendError(404);
return;
}
if ( reqInfo.extension.equals("json") )
{
final String pluginRoot = ( String ) request.getAttribute( WebConsoleConstants.ATTR_PLUGIN_ROOT );
final String servicesRoot = getServicesRoot( request );
try
{
this.renderJSON(response, reqInfo.bundle, pluginRoot, servicesRoot, request.getLocale(), request.getParameter(FILTER_PARAM), null );
}
catch (InvalidSyntaxException e)
{
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Invalid LDAP filter specified");
}
// nothing more to do
return;
}
super.doGet( request, response );
}
/**
* @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
*/
@Override
protected void doPost( HttpServletRequest req, HttpServletResponse resp ) throws ServletException, IOException
{
boolean success = false;
BundleException bundleException = null;
final String action = WebConsoleUtil.getParameter( req, "action" );
if ( "refreshPackages".equals( action ) )
{
// refresh packages and give it most 15 seconds to finish
BaseUpdateInstallHelper.refreshPackages( getPackageAdmin(), getBundleContext(), 15000L, null );
success = true;
}
else if ( "install".equals( action ) )
{
installBundles( req );
if (req.getRequestURI().endsWith( "/install" )) {
// just send 200/OK, no content
resp.setContentLength( 0 );
} else {
// redirect to URL
resp.sendRedirect( req.getRequestURI() );
}
return;
}
else
{
final RequestInfo reqInfo = new RequestInfo( req );
if ( reqInfo.bundle == null && reqInfo.bundleRequested )
{
resp.sendError( 404 );
return;
}
final Bundle bundle = reqInfo.bundle;
if ( bundle != null )
{
if ( "start".equals( action ) )
{
// start bundle
try
{
bundle.start();
}
catch ( BundleException be )
{
bundleException = be;
log( "Cannot start", be );
}
}
else if ( "stop".equals( action ) )
{
// stop bundle
try
{
bundle.stop();
}
catch ( BundleException be )
{
bundleException = be;
log( "Cannot stop", be );
}
}
else if ( "refresh".equals( action ) )
{
// refresh bundle wiring and give at most 5 seconds to finish
BaseUpdateInstallHelper.refreshPackages( getPackageAdmin(), getBundleContext(), 5000L, bundle );
}
else if ( "update".equals( action ) )
{
// update the bundle
update( bundle );
}
else if ( "uninstall".equals( action ) )
{
// uninstall bundle
try
{
bundle.uninstall();
}
catch ( BundleException be )
{
bundleException = be;
log( "Cannot uninstall", be );
}
}
// write the state only
resp.setContentType( "application/json" ); //$NON-NLS-1$
resp.setCharacterEncoding( "UTF-8" ); //$NON-NLS-1$
if ( null == getBundleContext() )
{
// refresh package on the web console itself or some of it's dependencies
resp.getWriter().print("false"); //$NON-NLS-1$
}
else
{
resp.getWriter().print( "{\"fragment\":" + isFragmentBundle( bundle ) //$NON-NLS-1$
+ ",\"stateRaw\":" + bundle.getState() + "}" ); //$NON-NLS-1$ //$NON-NLS-2$
}
return;
}
}
if ( success && null != getBundleContext() )
{
final String pluginRoot = ( String ) req.getAttribute( WebConsoleConstants.ATTR_PLUGIN_ROOT );
final String servicesRoot = getServicesRoot( req );
try
{
this.renderJSON( resp, null, pluginRoot, servicesRoot, req.getLocale(), req.getParameter(FILTER_PARAM), bundleException );
}
catch (InvalidSyntaxException e)
{
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Invalid LDAP filter specified");
}
}
else
{
super.doPost( req, resp );
}
}
private String getServicesRoot(HttpServletRequest request)
{
return ( ( String ) request.getAttribute( WebConsoleConstants.ATTR_APP_ROOT ) ) +
"/" + ServicesServlet.LABEL + "/";
}
Bundle getBundle( String pathInfo )
{
// only use last part of the pathInfo
pathInfo = pathInfo.substring( pathInfo.lastIndexOf( '/' ) + 1 );
// assume bundle Id
try
{
final long bundleId = Long.parseLong( pathInfo );
if ( bundleId >= 0 )
{
return BundleContextUtil.getWorkingBundleContext(this.getBundleContext()).getBundle( bundleId );
}
}
catch ( NumberFormatException nfe )
{
// check if this follows the pattern {symbolic-name}[:{version}]
final int pos = pathInfo.indexOf(':');
final String symbolicName;
final String version;
if ( pos == -1 ) {
symbolicName = pathInfo;
version = null;
} else {
symbolicName = pathInfo.substring(0, pos);
version = pathInfo.substring(pos+1);
}
// search
final Bundle[] bundles = BundleContextUtil.getWorkingBundleContext(this.getBundleContext()).getBundles();
for(int i=0; i<bundles.length; i++)
{
final Bundle bundle = bundles[i];
// check symbolic name first
if ( symbolicName.equals(bundle.getSymbolicName()) )
{
if ( version == null || version.equals(bundle.getHeaders().get(Constants.BUNDLE_VERSION)) )
{
return bundle;
}
}
}
}
return null;
}
private void appendBundleInfoCount( final StringBuffer buf, String msg, int count )
{
buf.append(count);
buf.append(" bundle");
if ( count != 1 )
buf.append( 's' );
buf.append(' ');
buf.append(msg);
}
/**
* @see org.apache.felix.webconsole.AbstractWebConsolePlugin#renderContent(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
*/
@Override
protected void renderContent( HttpServletRequest request, HttpServletResponse response ) throws IOException
{
// get request info from request attribute
final RequestInfo reqInfo = getRequestInfo(request);
final int startLevel = getStartLevel().getInitialBundleStartLevel();
// prepare variables
DefaultVariableResolver vars = ( ( DefaultVariableResolver ) WebConsoleUtil.getVariableResolver( request ) );
vars.put( "startLevel", String.valueOf(startLevel));
vars.put( "drawDetails", reqInfo.bundleRequested ? Boolean.TRUE : Boolean.FALSE );
vars.put( "currentBundle", (reqInfo.bundleRequested && reqInfo.bundle != null ? String.valueOf(reqInfo.bundle.getBundleId()) : "null"));
final String pluginRoot = ( String ) request.getAttribute( WebConsoleConstants.ATTR_PLUGIN_ROOT );
final String servicesRoot = getServicesRoot ( request );
StringWriter w = new StringWriter();
try
{
writeJSON(w, reqInfo.bundle, pluginRoot, servicesRoot, request.getLocale(), request.getParameter(FILTER_PARAM), null );
}
catch (InvalidSyntaxException e)
{
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Invalid LDAP filter specified");
return;
}
vars.put( "__bundles__", w.toString());
response.getWriter().print(TEMPLATE_MAIN);
}
private void renderJSON( final HttpServletResponse response, final Bundle bundle, final String pluginRoot, final String servicesRoot, final Locale locale, final String filter, final BundleException be )
throws IOException, InvalidSyntaxException
{
response.setContentType( "application/json" );
response.setCharacterEncoding( "UTF-8" );
final PrintWriter pw = response.getWriter();
writeJSON(pw, bundle, pluginRoot, servicesRoot, locale, filter, be);
}
private void writeJSON( final Writer pw, final Bundle bundle, final String pluginRoot, final String servicesRoot, final Locale locale, final String filter, final BundleException be )
throws IOException, InvalidSyntaxException
{
final Map<String, Object> map = createObjectStructure( bundle, pluginRoot, servicesRoot, false, locale, filter, be );
final JSONWriter writer = new JSONWriter(pw);
writer.value(map);
}
private Map<String, Object> createObjectStructure( final Bundle bundle, final String pluginRoot,
final String servicesRoot, final boolean fullDetails, final Locale locale, final String filter, final BundleException be ) throws IOException, InvalidSyntaxException
{
final Bundle[] allBundles = this.getBundles();
final List<Object> status = getStatusLine(allBundles);
final String statusLine = (String) status.remove(5);
// filter bundles by headers
final Bundle[] bundles;
if (bundle != null)
{
bundles = new Bundle[] { bundle };
}
else if (filter != null)
{
Filter f = getBundleContext().createFilter(filter);
ArrayList<Bundle> list = new ArrayList<Bundle>(allBundles.length);
final String localeString = locale.toString();
for (int i = 0, size = allBundles.length; i < size; i++)
{
if (f.match(allBundles[i].getHeaders(localeString)))
{
list.add(allBundles[i]);
}
}
bundles = list.toArray(new Bundle[list.size()]);
}
else
{
bundles = allBundles;
}
Util.sort( bundles, locale );
final Map<String, Object> map = new LinkedHashMap<String, Object>();
if (null != be)
{
final StringWriter s = new StringWriter();
final Throwable t = be.getNestedException() != null ? be.getNestedException() : be;
t.printStackTrace( new PrintWriter(s) );
map.put("error", s.toString());
}
map.put("status", statusLine);
// add raw status
map.put( "s", status.toArray() );
final Object[] bundlesArray = new Object[bundles.length];
for ( int i = 0; i < bundles.length; i++ )
{
bundlesArray[i] =
bundleInfo( bundles[i], fullDetails || bundle != null, pluginRoot, servicesRoot, locale );
}
map.put("data", bundlesArray);
return map;
}
private List<Object> getStatusLine(final Bundle[] bundles)
{
List<Object> ret = new ArrayList<Object>();
int active = 0, installed = 0, resolved = 0, fragments = 0;
for ( int i = 0; i < bundles.length; i++ )
{
switch ( bundles[i].getState() )
{
case Bundle.ACTIVE:
active++;
break;
case Bundle.INSTALLED:
installed++;
break;
case Bundle.RESOLVED:
if ( isFragmentBundle( bundles[i] ) )
{
fragments++;
}
else
{
resolved++;
}
break;
}
}
final StringBuffer buffer = new StringBuffer();
buffer.append("Bundle information: ");
appendBundleInfoCount(buffer, "in total", bundles.length);
if ( active == bundles.length || active + fragments == bundles.length )
{
buffer.append(" - all ");
appendBundleInfoCount(buffer, "active.", bundles.length);
}
else
{
if ( active != 0 )
{
buffer.append(", ");
appendBundleInfoCount(buffer, "active", active);
}
if ( fragments != 0 )
{
buffer.append(", ");
appendBundleInfoCount(buffer, "active fragments", fragments);
}
if ( resolved != 0 )
{
buffer.append(", ");
appendBundleInfoCount(buffer, "resolved", resolved);
}
if ( installed != 0 )
{
buffer.append(", ");
appendBundleInfoCount(buffer, "installed", installed);
}
buffer.append('.');
}
ret.add(new Integer(bundles.length));
ret.add(new Integer(active));
ret.add(new Integer(fragments));
ret.add(new Integer(resolved));
ret.add(new Integer(installed));
ret.add(buffer.toString());
return ret;
}
private Map<String, Object> bundleInfo( final Bundle bundle,
final boolean details,
final String pluginRoot,
final String servicesRoot,
final Locale locale )
{
final Map<String, Object> result = new LinkedHashMap<String, Object>();
result.put("id", bundle.getBundleId() );
result.put("name", Util.getName( bundle, locale ) );
result.put("fragment", isFragmentBundle(bundle) );
result.put("stateRaw", bundle.getState() );
result.put("state", toStateString( bundle ) );
result.put("version", Util.getHeaderValue(bundle, Constants.BUNDLE_VERSION) );
if ( bundle.getSymbolicName() != null )
{
result.put("symbolicName", bundle.getSymbolicName() );
}
result.put("category", Util.getHeaderValue(bundle, Constants.BUNDLE_CATEGORY) );
if ( details )
{
bundleDetails( result, bundle, pluginRoot, servicesRoot, locale );
}
return result;
}
private final Bundle[] getBundles()
{
return BundleContextUtil.getWorkingBundleContext(this.getBundleContext()).getBundles();
}
private String toStateString( final Bundle bundle )
{
switch ( bundle.getState() )
{
case Bundle.INSTALLED:
return "Installed";
case Bundle.RESOLVED:
if ( isFragmentBundle(bundle) )
{
return "Fragment";
}
return "Resolved";
case Bundle.STARTING:
return "Starting";
case Bundle.ACTIVE:
return "Active";
case Bundle.STOPPING:
return "Stopping";
case Bundle.UNINSTALLED:
return "Uninstalled";
default:
return "Unknown: " + bundle.getState();
}
}
private final boolean isFragmentBundle( Bundle bundle )
{
// Workaround for FELIX-3670
if ( bundle.getState() == Bundle.UNINSTALLED )
{
return bundle.getHeaders().get( Constants.FRAGMENT_HOST ) != null;
}
return getPackageAdmin().getBundleType( bundle ) == PackageAdmin.BUNDLE_TYPE_FRAGMENT;
}
private void keyVal(final List<Map<String, Object>> props, final String key, final Object val)
{
if ( val != null )
{
final Map<String, Object> obj = new LinkedHashMap<String, Object>();
obj.put("key", key);
obj.put("value", val);
props.add(obj);
}
}
private final void bundleDetails( final Map<String, Object> result,
final Bundle bundle,
final String pluginRoot,
final String servicesRoot,
final Locale locale)
{
final Dictionary<String, String> headers = bundle.getHeaders( locale == null ? null : locale.toString() );
final List<Map<String, Object>> props = new ArrayList<Map<String, Object>>();
keyVal( props, "Symbolic Name", bundle.getSymbolicName() );
keyVal( props, "Version", headers.get( Constants.BUNDLE_VERSION ) );
keyVal( props, "Bundle Location", bundle.getLocation() );
keyVal( props, "Last Modification", new Date( bundle.getLastModified() ) );
String docUrl = headers.get( Constants.BUNDLE_DOCURL );
if ( docUrl != null )
{
keyVal( props, "Bundle Documentation", docUrl );
}
keyVal( props, "Vendor", headers.get( Constants.BUNDLE_VENDOR ) );
keyVal( props, "Copyright", headers.get( Constants.BUNDLE_COPYRIGHT ) );
keyVal( props, "Description", headers.get( Constants.BUNDLE_DESCRIPTION ) );
keyVal( props, "Start Level", getStartLevel( bundle ) );
keyVal( props, "Bundle Classpath", headers.get( Constants.BUNDLE_CLASSPATH ) );
listFragmentInfo( props, bundle, pluginRoot );
if ( bundle.getState() == Bundle.INSTALLED )
{
listImportExportsUnresolved( props, bundle, pluginRoot );
}
else
{
listImportExport( props, bundle, pluginRoot );
}
if ( bundle.getState() != Bundle.UNINSTALLED )
{
listServices( props, bundle, servicesRoot );
}
listHeaders( props, bundle );
final String appRoot = ( pluginRoot == null ) ? "" : pluginRoot.substring( 0, pluginRoot.lastIndexOf( "/" ) );
bundleInfoDetails( props, bundle, appRoot, locale );
result.put( "props", props.toArray(new Object[props.size()]));
}
private final void bundleInfoDetails( List<Map<String, Object>> props, Bundle bundle, String appRoot, final Locale locale)
{
final Map<String, Object> val = new LinkedHashMap<String, Object>();
val.put("key", "nfo");
final Map<String, Object[]> value = new LinkedHashMap<String, Object[]>();
final Object[] bundleInfoProviders = bundleInfoTracker.getServices();
for ( int i = 0; bundleInfoProviders != null && i < bundleInfoProviders.length; i++ )
{
final BundleInfoProvider infoProvider = (BundleInfoProvider) bundleInfoProviders[i];
final BundleInfo[] infos = infoProvider.getBundleInfo(bundle, appRoot, locale);
if ( null != infos && infos.length > 0)
{
final Object[] infoArray = new Object[infos.length];
for ( int j = 0; j < infos.length; j++ )
{
infoArray[j] = bundleInfo( infos[j] );
}
value.put(infoProvider.getName(locale), infoArray);
}
}
val.put("value", value);
props.add(val);
}
private static final Map<String, Object> bundleInfo( BundleInfo info )
{
final Map<String, Object> val = new LinkedHashMap<String, Object>();
val.put( "name", info.getName() );
val.put( "description", info.getDescription() );
val.put( "type", info.getType().getName() );
val.put( "value", info.getValue() );
return val;
}
private final Integer getStartLevel( Bundle bundle )
{
if ( bundle.getState() != Bundle.UNINSTALLED )
{
StartLevel sl = getStartLevel();
if ( sl != null )
{
return new Integer( sl.getBundleStartLevel( bundle ) );
}
}
// bundle has been uninstalled or StartLevel service is not available
return null;
}
private void listImportExport( List props, Bundle bundle, final String pluginRoot )
{
PackageAdmin packageAdmin = getPackageAdmin();
if ( packageAdmin == null )
{
return;
}
Map usingBundles = new TreeMap();
ExportedPackage[] exports = packageAdmin.getExportedPackages( bundle );
if ( exports != null && exports.length > 0 )
{
// do alphabetical sort
Arrays.sort( exports, new Comparator()
{
public int compare( ExportedPackage p1, ExportedPackage p2 )
{
return p1.getName().compareTo( p2.getName() );
}
@Override
public int compare( Object o1, Object o2 )
{
return compare( ( ExportedPackage ) o1, ( ExportedPackage ) o2 );
}
} );
Object[] val = new Object[exports.length];
for ( int j = 0; j < exports.length; j++ )
{
ExportedPackage export = exports[j];
val[j] = collectExport( export.getName(), export.getVersion() );
Bundle[] ubList = export.getImportingBundles();
if ( ubList != null )
{
for ( int i = 0; i < ubList.length; i++ )
{
Bundle ub = ubList[i];
String name = ub.getSymbolicName();
if (name == null) name = ub.getLocation();
usingBundles.put( name, ub );
}
}
}
keyVal( props, "Exported Packages", val );
}
else
{
keyVal( props, "Exported Packages", "---" );
}
exports = packageAdmin.getExportedPackages( ( Bundle ) null );
if ( exports != null && exports.length > 0 )
{
// collect import packages first
final List imports = new ArrayList();
for ( int i = 0; i < exports.length; i++ )
{
final ExportedPackage ep = exports[i];
final Bundle[] importers = ep.getImportingBundles();
for ( int j = 0; importers != null && j < importers.length; j++ )
{
if ( importers[j].getBundleId() == bundle.getBundleId() )
{
imports.add( ep );
break;
}
}
}
// now sort
Object[] val;
if ( imports.size() > 0 )
{
final ExportedPackage[] packages = ( ExportedPackage[] ) imports.toArray( new ExportedPackage[imports
.size()] );
Arrays.sort( packages, new Comparator()
{
public int compare( ExportedPackage p1, ExportedPackage p2 )
{
return p1.getName().compareTo( p2.getName() );
}
@Override
public int compare( Object o1, Object o2 )
{
return compare( ( ExportedPackage ) o1, ( ExportedPackage ) o2 );
}
} );
// and finally print out
val = new Object[packages.length];
for ( int i = 0; i < packages.length; i++ )
{
ExportedPackage ep = packages[i];
val[i] = collectImport( ep.getName(), ep.getVersion(), false, ep, pluginRoot );
}
}
else
{
// add description if there are no imports
val = new Object[1];
val[0] = "None";
}
keyVal( props, "Imported Packages", val );
}
if ( !usingBundles.isEmpty() )
{
Object[] val = new Object[usingBundles.size()];
int index = 0;
for ( Iterator ui = usingBundles.values().iterator(); ui.hasNext(); )
{
Bundle usingBundle = ( Bundle ) ui.next();
val[index] = getBundleDescriptor( usingBundle, pluginRoot );
index++;
}
keyVal( props, "Importing Bundles", val );
}
}
private void listImportExportsUnresolved( final List props, Bundle bundle, final String pluginRoot )
{
Dictionary dict = bundle.getHeaders();
String target = ( String ) dict.get( Constants.EXPORT_PACKAGE );
if ( target != null )
{
Clause[] pkgs = Parser.parseHeader( target );
if ( pkgs != null && pkgs.length > 0 )
{
// do alphabetical sort
Arrays.sort( pkgs, new Comparator()
{
public int compare( Clause p1, Clause p2 )
{
return p1.getName().compareTo( p2.getName() );
}
@Override
public int compare( Object o1, Object o2 )
{
return compare( ( Clause) o1, ( Clause ) o2 );
}
} );
Object[] val = new Object[pkgs.length];
for ( int i = 0; i < pkgs.length; i++ )
{
Clause export = new Clause( pkgs[i].getName(), pkgs[i].getDirectives(), pkgs[i].getAttributes() );
val[i] = collectExport( export.getName(), export.getAttribute( Constants.VERSION_ATTRIBUTE ) );
}
keyVal( props, "Exported Packages", val );
}
else
{
keyVal( props, "Exported Packages", "---" );
}
}
target = ( String ) dict.get( Constants.IMPORT_PACKAGE );
if ( target != null )
{
Clause[] pkgs = Parser.parseHeader( target );
if ( pkgs != null && pkgs.length > 0 )
{
Map imports = new TreeMap();
for ( int i = 0; i < pkgs.length; i++ )
{
Clause pkg = pkgs[i];
imports.put( pkg.getName(), new Clause( pkg.getName(), pkg.getDirectives(), pkg.getAttributes() ) );
}
// collect import packages first
final Map candidates = new HashMap();
PackageAdmin packageAdmin = getPackageAdmin();
if ( packageAdmin != null )
{
ExportedPackage[] exports = packageAdmin.getExportedPackages( ( Bundle ) null );
if ( exports != null && exports.length > 0 )
{
for ( int i = 0; i < exports.length; i++ )
{
final ExportedPackage ep = exports[i];
Clause imp = ( Clause ) imports.get( ep.getName() );
if ( imp != null && isSatisfied( imp, ep ) )
{
candidates.put( ep.getName(), ep );
}
}
}
}
// now sort
Object[] val;
if ( imports.size() > 0 )
{
final List importList = new ArrayList();
for ( Iterator ii = imports.values().iterator(); ii.hasNext(); )
{
Clause r4Import = ( Clause ) ii.next();
ExportedPackage ep = ( ExportedPackage ) candidates.get( r4Import.getName() );
// if there is no matching export, check whether this
// bundle has the package, ignore the entry in this case
if ( ep == null )
{
String path = r4Import.getName().replace( '.', '/' );
if ( bundle.getEntry( path ) != null )
{
continue;
}
}
importList.add(collectImport( r4Import.getName(), r4Import.getAttribute( Constants.VERSION_ATTRIBUTE ),
Constants.RESOLUTION_OPTIONAL.equals( r4Import
.getDirective( Constants.RESOLUTION_DIRECTIVE ) ), ep, pluginRoot ));
}
val = importList.toArray(new Object[importList.size()]);
}
else
{
// add description if there are no imports
val = new Object[1];
val[0] = "---" ;
}
keyVal( props, "Imported Packages", val );
}
}
}
private String getServiceID(ServiceReference ref, final String servicesRoot)
{
String id = ref.getProperty( Constants.SERVICE_ID ).toString();
StringBuffer val = new StringBuffer();
if ( servicesRoot != null )
{
val.append( "<a href='" ).append( servicesRoot ).append( id ).append( "'>" );
val.append( id );
val.append( "</a>" );
return val.toString();
}
return id;
}
private void listServices( List props, Bundle bundle, final String servicesRoot )
{
ServiceReference[] refs = bundle.getRegisteredServices();
if ( refs == null || refs.length == 0 )
{
return;
}
for ( int i = 0; i < refs.length; i++ )
{
String key = "Service ID " + getServiceID( refs[i], servicesRoot );
List val = new ArrayList();
appendProperty( val, refs[i], Constants.OBJECTCLASS, "Types" );
appendProperty( val, refs[i], Constants.SERVICE_PID, "Service PID" );
appendProperty( val, refs[i], "org.apache.felix.karaf.features.configKey", "Feature PID" );
appendProperty( val, refs[i], ConfigurationAdmin.SERVICE_FACTORYPID, "Factory PID" );
appendProperty( val, refs[i], "component.name", "Component Name" );
appendProperty( val, refs[i], "component.id", "Component ID" );
appendProperty( val, refs[i], "component.factory", "Component Factory" );
appendProperty( val, refs[i], Constants.SERVICE_DESCRIPTION, "Description" );
appendProperty( val, refs[i], Constants.SERVICE_VENDOR, "Vendor" );
keyVal( props, key, val.toArray(new Object[val.size()]));
}
}
private void listHeaders( List props, Bundle bundle )
{
List val = new ArrayList();
Dictionary headers = bundle.getHeaders(""); // don't localize at all - raw headers
Enumeration he = headers.keys();
while ( he.hasMoreElements() )
{
Object header = he.nextElement();
String value = String.valueOf(headers.get( header ));
// Package headers may be long, support line breaking by
// ensuring blanks after comma and semicolon.
value = enableLineWrapping(value);
val.add( header + ": " + value );
}
keyVal( props, "Manifest Headers", val.toArray(new Object[val.size()]) );
}
private static final String enableLineWrapping(final String value)
{
StringBuffer sb = new StringBuffer(value.length() * 2 / 3);
synchronized (sb)
{ // faster
for (int i = 0; i < value.length(); i++)
{
final char ch = value.charAt( i );
sb.append( ch );
if ( ch == ';' || ch == ',' )
{
sb.append( ' ' );
}
}
return sb.toString();
}
}
private void listFragmentInfo( final List props, final Bundle bundle, final String pluginRoot )
{
if ( isFragmentBundle( bundle ) )
{
Bundle[] hostBundles = getPackageAdmin().getHosts( bundle );
if ( hostBundles != null )
{
final Object[] val = new Object[hostBundles.length];
for ( int i = 0; i < hostBundles.length; i++ )
{
val[i] = getBundleDescriptor( hostBundles[i], pluginRoot );
}
keyVal( props, "Host Bundles", val );
}
}
else
{
Bundle[] fragmentBundles = getPackageAdmin().getFragments( bundle );
if ( fragmentBundles != null )
{
final Object[] val = new Object[fragmentBundles.length];
for ( int i = 0; i < fragmentBundles.length; i++ )
{
val[i] = getBundleDescriptor( fragmentBundles[i], pluginRoot );
}
keyVal( props, "Fragments Attached", val );
}
}
}
private void appendProperty( final List props, ServiceReference ref, String name, String label )
{
StringBuffer dest = new StringBuffer();
Object value = ref.getProperty( name );
if ( value instanceof Object[] )
{
Object[] values = ( Object[] ) value;
dest.append( label ).append( ": " );
for ( int j = 0; j < values.length; j++ )
{
if ( j > 0 )
dest.append( ", " );
dest.append( values[j] );
}
props.add(dest.toString());
}
else if ( value != null )
{
dest.append( label ).append( ": " ).append( value );
props.add(dest.toString());
}
}
private Object collectExport( String name, Version version )
{
return collectExport( name, ( version == null ) ? null : version.toString() );
}
private Object collectExport( String name, String version )
{
StringBuffer val = new StringBuffer();
boolean bootDel = isBootDelegated( name );
if ( bootDel )
{
val.append( "!! " );
}
val.append( name );
if ( version != null )
{
val.append( ",version=" ).append( version );
}
if ( bootDel )
{
val.append( " -- Overwritten by Boot Delegation" );
}
return val.toString();
}
private Object collectImport(String name, Version version, boolean optional,
ExportedPackage export, final String pluginRoot )
{
return collectImport( name, ( version == null ) ? null : version.toString(), optional, export, pluginRoot );
}
private Object collectImport( String name, String version, boolean optional, ExportedPackage export,
final String pluginRoot )
{
StringBuffer val = new StringBuffer();
boolean bootDel = isBootDelegated( name );
String marker = null;
val.append( name );
if ( version != null )
{
val.append( ",version=" ).append( version );
}
if ( export != null )
{
val.append( " from " );
val.append( getBundleDescriptor( export.getExportingBundle(), pluginRoot ) );
if ( bootDel )
{
val.append( " -- Overwritten by Boot Delegation" );
marker = "INFO";
}
}
else
{
val.append( " -- Cannot be resolved" );
marker = "ERROR";
if ( optional )
{
val.append( " but is not required" );
}
if ( bootDel )
{
val.append( " and overwritten by Boot Delegation" );
}
}
if ( marker != null ) {
val.insert(0, ": ");
val.insert(0, marker);
}
return val;
}
// returns true if the package is listed in the bootdelegation property
private boolean isBootDelegated( String pkgName )
{
// bootdelegation analysis from Apache Felix R4SearchPolicyCore
// Only consider delegation if we have a package name, since
// we don't want to promote the default package. The spec does
// not take a stand on this issue.
if ( pkgName.length() > 0 )
{
// Delegate any packages listed in the boot delegation
// property to the parent class loader.
for ( int i = 0; i < bootPkgs.length; i++ )
{
// A wildcarded boot delegation package will be in the form of
// "foo.", so if the package is wildcarded do a startsWith() or
// a regionMatches() to ignore the trailing "." to determine if
// the request should be delegated to the parent class loader.
// If the package is not wildcarded, then simply do an equals()
// test to see if the request should be delegated to the parent
// class loader.
if ( ( bootPkgWildcards[i] && ( pkgName.startsWith( bootPkgs[i] ) || bootPkgs[i].regionMatches( 0,
pkgName, 0, pkgName.length() ) ) )
|| ( !bootPkgWildcards[i] && bootPkgs[i].equals( pkgName ) ) )
{
return true;
}
}
}
return false;
}
private boolean isSatisfied( Clause imported, ExportedPackage exported )
{
if ( imported.getName().equals( exported.getName() ) )
{
String versionAttr = imported.getAttribute( Constants.VERSION_ATTRIBUTE );
if ( versionAttr == null )
{
// no specific version required, this export surely satisfies it
return true;
}
VersionRange required = VersionRange.parse( versionAttr );
return required.isInRange( exported.getVersion() );
}
// no this export does not satisfy the import
return false;
}
private String getBundleDescriptor( Bundle bundle, final String pluginRoot )
{
StringBuffer val = new StringBuffer();
if ( pluginRoot != null )
{
val.append( "<a href='" ).append( pluginRoot ).append( '/' ).append( bundle.getBundleId() ).append( "'>" );
}
if ( bundle.getSymbolicName() != null )
{
// list the bundle name if not null
val.append( bundle.getSymbolicName() );
val.append( " (" ).append( bundle.getBundleId() );
val.append( ")" );
}
else if ( bundle.getLocation() != null )
{
// otherwise try the location
val.append( bundle.getLocation() );
val.append( " (" ).append( bundle.getBundleId() );
val.append( ")" );
}
else
{
// fallback to just the bundle id
// only append the bundle
val.append( bundle.getBundleId() );
}
if ( pluginRoot != null )
{
val.append( "</a>" );
}
return val.toString();
}
private void update( final Bundle bundle )
{
UpdateHelper t = new UpdateHelper( this, bundle, false );
t.start();
}
private final class RequestInfo
{
public final String extension;
public final Bundle bundle;
public final boolean bundleRequested;
public final String pathInfo;
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";
}
// we only accept direct requests to a bundle if they have a slash after the label
String bundleInfo = null;
if (info.startsWith("/") )
{
bundleInfo = info.substring(1);
}
if ( bundleInfo == null || bundleInfo.length() == 0 )
{
bundle = null;
bundleRequested = false;
pathInfo = null;
}
else
{
bundle = getBundle(bundleInfo);
bundleRequested = true;
pathInfo = bundleInfo;
}
request.setAttribute(BundlesServlet.class.getName(), this);
}
}
static final RequestInfo getRequestInfo(final HttpServletRequest request)
{
return (RequestInfo)request.getAttribute( BundlesServlet.class.getName() );
}
private final PackageAdmin getPackageAdmin()
{
return ( PackageAdmin ) getService( PackageAdmin.class.getName() );
}
private final StartLevel getStartLevel()
{
return ( StartLevel ) getService( StartLevel.class.getName() );
}
//---------- Bundle Installation handler (former InstallAction)
private void installBundles( HttpServletRequest request ) throws IOException
{
// get the uploaded data
final Map params = ( Map ) request.getAttribute( AbstractWebConsolePlugin.ATTR_FILEUPLOAD );
if ( params == null )
{
return;
}
final FileItem startItem = getParameter( params, FIELD_START );
final FileItem startLevelItem = getParameter( params, FIELD_STARTLEVEL );
final FileItem[] bundleItems = getFileItems( params, FIELD_BUNDLEFILE );
final FileItem refreshPackagesItem = getParameter( params, FIELD_REFRESH_PACKAGES );
// don't care any more if no bundle item
if ( bundleItems.length == 0 )
{
return;
}
// default values
// it exists
int startLevel = -1;
String bundleLocation = "inputstream:";
// convert the start level value
if ( startLevelItem != null )
{
try
{
startLevel = Integer.parseInt( startLevelItem.getString() );
}
catch ( NumberFormatException nfe )
{
log( LogService.LOG_INFO, "Cannot parse start level parameter " + startLevelItem
+ " to a number, not setting start level" );
}
}
for ( int i = 0; i < bundleItems.length; i++ )
{
final FileItem bundleItem = bundleItems[i];
// write the bundle data to a temporary file to ease processing
File tmpFile = null;
try
{
// copy the data to a file for better processing
tmpFile = File.createTempFile( "install", ".tmp" );
tmpFile.delete(); // FIX for FILEUPLOAD-293
bundleItem.write( tmpFile );
}
catch ( Exception e )
{
log( LogService.LOG_ERROR, "Problem accessing uploaded bundle file: " + bundleItem.getName(), e );
// remove the tmporary file
if ( tmpFile != null )
{
tmpFile.delete();
tmpFile = null;
}
}
// install or update the bundle now
if ( tmpFile != null )
{
// start, refreshPackages just needs to exist, don't care for value
final boolean start = startItem != null;
final boolean refreshPackages = refreshPackagesItem != null;
bundleLocation = "inputstream:" + bundleItem.getName();
installBundle( bundleLocation, tmpFile, startLevel, start, refreshPackages );
}
}
}
private FileItem getParameter( Map params, String name )
{
FileItem[] items = ( FileItem[] ) params.get( name );
if ( items != null )
{
for ( int i = 0; i < items.length; i++ )
{
if ( items[i].isFormField() )
{
return items[i];
}
}
}
// nothing found, fail
return null;
}
private FileItem[] getFileItems( Map params, String name )
{
final List files = new ArrayList();
FileItem[] items = ( FileItem[] ) params.get( name );
if ( items != null )
{
for ( int i = 0; i < items.length; i++ )
{
if ( !items[i].isFormField() && items[i].getSize() > 0 )
{
files.add( items[i] );
}
}
}
return ( FileItem[] ) files.toArray( new FileItem[files.size()] );
}
private void installBundle( String location, File bundleFile, int startLevel, boolean start, boolean refreshPackages )
throws IOException
{
if ( bundleFile != null )
{
// try to get the bundle name, fail if none
String symbolicName = getSymbolicName( bundleFile );
if ( symbolicName == null )
{
bundleFile.delete();
throw new IOException( Constants.BUNDLE_SYMBOLICNAME + " header missing, cannot install bundle" );
}
// check for existing bundle first
Bundle updateBundle = null;
if ( Constants.SYSTEM_BUNDLE_SYMBOLICNAME.equals( symbolicName ) )
{
updateBundle = getBundleContext().getBundle( 0 );
}
else
{
Bundle[] bundles = BundleContextUtil.getWorkingBundleContext(this.getBundleContext()).getBundles();
for ( int i = 0; i < bundles.length; i++ )
{
if ( ( bundles[i].getLocation() != null && bundles[i].getLocation().equals( location ) )
|| ( bundles[i].getSymbolicName() != null && bundles[i].getSymbolicName().equals( symbolicName ) ) )
{
updateBundle = bundles[i];
break;
}
}
}
if ( updateBundle != null )
{
updateBackground( updateBundle, bundleFile, refreshPackages );
}
else
{
installBackground( bundleFile, location, startLevel, start, refreshPackages );
}
}
}
private String getSymbolicName( File bundleFile )
{
JarFile jar = null;
try
{
jar = new JarFile( bundleFile );
Manifest m = jar.getManifest();
if ( m != null )
{
String sn = m.getMainAttributes().getValue( Constants.BUNDLE_SYMBOLICNAME );
if ( sn != null )
{
final int paramPos = sn.indexOf(';');
if ( paramPos != -1 )
{
sn = sn.substring(0, paramPos);
}
}
return sn;
}
}
catch ( IOException ioe )
{
log( LogService.LOG_WARNING, "Cannot extract symbolic name of bundle file " + bundleFile, ioe );
}
finally
{
if ( jar != null )
{
try
{
jar.close();
}
catch ( IOException ioe )
{
// ignore
}
}
}
// fall back to "not found"
return null;
}
private void installBackground( final File bundleFile, final String location, final int startlevel,
final boolean doStart, final boolean refreshPackages )
{
InstallHelper t = new InstallHelper( this, getBundleContext(), bundleFile, location, startlevel, doStart,
refreshPackages );
t.start();
}
private void updateBackground( final Bundle bundle, final File bundleFile, final boolean refreshPackages )
{
UpdateHelper t = new UpdateHelper( this, bundle, bundleFile, refreshPackages );
t.start();
}
}