FELIX-3946 Prevent NPE in BundlesServlet.bundleDetails

* BundleInfoProvider: Clarify webConsoleRoot may be null
* BundlesServlet: Guard pluginRoot against null (only get substring if not null)
* BundlesServlet: Fix rendition of "nfo" structure generated based
  on BundleInforProvider data
* ServicesUsedInfoProvider: Generate BundleInfo of type VALUE if
  webConsoleRoot is null

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1452202 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/bundleinfo/BundleInfoProvider.java b/webconsole/src/main/java/org/apache/felix/webconsole/bundleinfo/BundleInfoProvider.java
index a8b123b..62178ef 100644
--- a/webconsole/src/main/java/org/apache/felix/webconsole/bundleinfo/BundleInfoProvider.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/bundleinfo/BundleInfoProvider.java
@@ -25,16 +25,16 @@
 /**

  * The bundle info provider allows the user to supply additional information

  * that will be used by the Web Console bundle plugin.

- * 

+ *

  * The API allows the user to register a special service, that could bind a

  * custom, implementation-specific information to a bundle.

- * 

+ *

  * A typical use-case for that API would be the Declarative Services, that could

  * provide information about the components provided by this bundle (and link to

  * the component plugin too). Another usage could be the ProSyst resource

  * manager, that would provide information about the memory and CPU usage of the

  * bundle.

- * 

+ *

  * @author Valentin Valchev

  */

 public interface BundleInfoProvider

@@ -49,7 +49,7 @@
 

     /**

      * Gets the name of the bundle info provider as localized string.

-     * 

+     *

      * @param locale

      *            the locale in which the name should be returned

      * @return the name of the bundle info provider.

@@ -60,11 +60,18 @@
     /**

      * Gets the associated bundle information with the specified bundle (by it's

      * ID)

-     * 

+     *

+     * The Service may also be called outside through the new Inventory bundle

+     * due to mapping the BundlesServlet to an InventoryPrinter and for example

+     * calling it from a Gogo Shell. In this case the {@code webConsoleRoot}

+     * parameter will be null a {@link BundleInfo} objects of type

+     * {@link BundleInfoType#LINK} must not be generated.

+     *

      * @param bundle

      *            the bundle, for which additional information is requested.

      * @param webConsoleRoot

-     *            the root alias of the web console itself.

+     *            the root alias of the web console itself or {@code null}

+     *            if this method is not called through the Web Console itself.

      * @param locale

      *            the locale in which the key-value pair should be returned.

      * @return array of available {@link BundleInfo} or empty array if none.

diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/BundlesServlet.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/BundlesServlet.java
index 3f4d07e..c8a4f48 100644
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/BundlesServlet.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/BundlesServlet.java
@@ -219,25 +219,50 @@
                         if ( !props.isNull( pi ) )
                         {
                             JSONObject entry = props.getJSONObject( pi );
-
-                            pw.print( "    " + entry.get( "key" ) + ": " );
-
-                            Object entryValue = entry.get( "value" );
-                            if ( entryValue instanceof JSONArray )
+                            String key = ( String ) entry.get( "key" );
+                            if ( "nfo".equals( key ) )
                             {
-                                pw.println();
-                                JSONArray entryArray = ( JSONArray ) entryValue;
-                                for ( int ei = 0; ei < entryArray.length(); ei++ )
+                                // BundleInfo (see #bundleInfo & #bundleInfoDetails
+                                JSONObject infos = ( JSONObject ) entry.get( "value" );
+                                Iterator infoKeys = infos.keys();
+                                while ( infoKeys.hasNext() )
                                 {
-                                    if ( !entryArray.isNull( ei ) )
+                                    String infoKey = ( String ) infoKeys.next();
+                                    pw.println( "    " + infoKey + ": " );
+
+                                    JSONArray infoA = infos.getJSONArray( infoKey );
+                                    for ( int iai = 0; iai < infoA.length(); iai++ )
                                     {
-                                        pw.println( "        " + entryArray.get( ei ) );
+                                        if ( !infoA.isNull( iai ) )
+                                        {
+                                            JSONObject info = infoA.getJSONObject( iai );
+                                            pw.println( "        " + info.get( "name" ) );
+                                        }
                                     }
                                 }
                             }
                             else
                             {
-                                pw.println( entryValue );
+                                // regular data
+                                pw.print( "    " + key + ": " );
+
+                                Object entryValue = entry.get( "value" );
+                                if ( entryValue instanceof JSONArray )
+                                {
+                                    pw.println();
+                                    JSONArray entryArray = ( JSONArray ) entryValue;
+                                    for ( int ei = 0; ei < entryArray.length(); ei++ )
+                                    {
+                                        if ( !entryArray.isNull( ei ) )
+                                        {
+                                            pw.println( "        " + entryArray.get( ei ) );
+                                        }
+                                    }
+                                }
+                                else
+                                {
+                                    pw.println( entryValue );
+                                }
                             }
                         }
                     }
@@ -803,7 +828,8 @@
         }
 
         listHeaders( jw, bundle );
-        bundleInfoDetails(jw, bundle, pluginRoot.substring(0, pluginRoot.lastIndexOf("/")), locale);
+        final String appRoot = ( pluginRoot == null ) ? null : pluginRoot.substring( 0, pluginRoot.lastIndexOf( "/" ) );
+        bundleInfoDetails( jw, bundle, appRoot, locale );
 
         jw.endArray();
     }
@@ -837,20 +863,22 @@
         jw.endObject();
     }
 
+
     private static final void bundleInfo( JSONWriter jw, BundleInfo info ) throws JSONException
     {
-	jw.object();
-	jw.key("name");
-	jw.value( info.getName() );
-	jw.key("description");
-	jw.value( info.getDescription() );
-	jw.key("type");
-	jw.value( info.getType().getName() );
-	jw.key("value");
-	jw.value( info.getValue() );
-	jw.endObject();
+        jw.object();
+        jw.key( "name" );
+        jw.value( info.getName() );
+        jw.key( "description" );
+        jw.value( info.getDescription() );
+        jw.key( "type" );
+        jw.value( info.getType().getName() );
+        jw.key( "value" );
+        jw.value( info.getValue() );
+        jw.endObject();
     }
 
+
     private final Integer getStartLevel( Bundle bundle )
     {
         if ( bundle.getState() != Bundle.UNINSTALLED )
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/ServicesUsedInfoProvider.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/ServicesUsedInfoProvider.java
index 170f45f..8ab3588 100644
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/ServicesUsedInfoProvider.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/ServicesUsedInfoProvider.java
@@ -18,6 +18,7 @@
  */

 package org.apache.felix.webconsole.internal.core;

 

+

 import java.text.MessageFormat;

 import java.util.Arrays;

 import java.util.Locale;

@@ -32,60 +33,67 @@
 import org.osgi.framework.ServiceReference;

 import org.osgi.framework.ServiceRegistration;

 

-final class ServicesUsedInfoProvider implements

-	BundleInfoProvider {

-    

+

+final class ServicesUsedInfoProvider implements BundleInfoProvider

+{

+

     private final LocalizationHelper localization;

-    

-    ServicesUsedInfoProvider(Bundle bundle) 

+

+

+    ServicesUsedInfoProvider( Bundle bundle )

     {

-	localization = new LocalizationHelper(bundle);

+        localization = new LocalizationHelper( bundle );

     }

 

+

     /*

      * (non-Javadoc)

-     * 

+     *

      * @see

      * org.apache.felix.webconsole.bundleinfo.BundleInfoProvider#getName(java

      * .util.Locale)

      */

-    public String getName(Locale locale) 

+    public String getName( Locale locale )

     {

-	return localization.getResourceBundle(locale).getString("services.info.name"); //$NON-NLS-1$;

+        return localization.getResourceBundle( locale ).getString( "services.info.name" ); //$NON-NLS-1$;

     }

 

-    public BundleInfo[] getBundleInfo(Bundle bundle, String webConsoleRoot,

-	    Locale locale) 

+

+    public BundleInfo[] getBundleInfo( Bundle bundle, String webConsoleRoot, Locale locale )

     {

-	final ServiceReference[] refs = bundle.getServicesInUse();

-	if (null == refs || refs.length == 0)

-	    return NO_INFO;

-	

-	BundleInfo[] ret = new BundleInfo[refs.length];

-	for ( int i=0; i < refs.length; i++ )

-	{

-	    ret[i] = toInfo(refs[i], webConsoleRoot, locale);

-	}

-	return ret;

+        final ServiceReference[] refs = bundle.getServicesInUse();

+        if ( null == refs || refs.length == 0 )

+            return NO_INFO;

+

+        BundleInfo[] ret = new BundleInfo[refs.length];

+        for ( int i = 0; i < refs.length; i++ )

+        {

+            ret[i] = toInfo( refs[i], webConsoleRoot, locale );

+        }

+        return ret;

     }

 

-    private BundleInfo toInfo(ServiceReference ref, String webConsoleRoot, Locale locale) 

+

+    private BundleInfo toInfo( ServiceReference ref, String webConsoleRoot, Locale locale )

     {

-	final String[] classes = (String[]) ref

-		.getProperty(Constants.OBJECTCLASS);

-	final Object id = ref.getProperty(Constants.SERVICE_ID);

-	final String descr =  localization.getResourceBundle(locale).getString("services.info.descr"); //$NON-NLS-1$;

-	String name = localization.getResourceBundle(locale).getString("services.info.key"); //$NON-NLS-1$;

-	name = MessageFormat.format(name, new Object[] {

-		id, Arrays.asList(classes).toString()

-	});

-	return new BundleInfo(name, webConsoleRoot + "/services/" + id, //$NON-NLS-1$

-		BundleInfoType.LINK, descr);

+        final String[] classes = ( String[] ) ref.getProperty( Constants.OBJECTCLASS );

+        final Object id = ref.getProperty( Constants.SERVICE_ID );

+        final String descr = localization.getResourceBundle( locale ).getString( "services.info.descr" ); //$NON-NLS-1$;

+        String name = localization.getResourceBundle( locale ).getString( "services.info.key" ); //$NON-NLS-1$;

+        name = MessageFormat.format( name, new Object[]

+            { id, Arrays.asList( classes ).toString() } );

+        if ( webConsoleRoot == null )

+        {

+            return new BundleInfo( name, id, BundleInfoType.VALUE, descr );

+        }

+        return new BundleInfo( name, webConsoleRoot + "/services/" + id, //$NON-NLS-1$

+            BundleInfoType.LINK, descr );

     }

-    

+

+

     ServiceRegistration register( BundleContext context )

     {

-	return context.registerService(BundleInfoProvider.class.getName(), this, null);

+        return context.registerService( BundleInfoProvider.class.getName(), this, null );

     }

 

 }