Resolved FELIX-1441 /Search manifest entries of bundles/


git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@925123 13f79535-47bb-0310-9956-ffa450edef68
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 b868234..c8bb670 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
@@ -64,6 +64,8 @@
 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;
@@ -88,6 +90,9 @@
     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";
@@ -175,7 +180,7 @@
         try
         {
             StringWriter w = new StringWriter();
-            writeJSON( w, null, null, null, true, Locale.ENGLISH );
+            writeJSON( w, null, null, null, true, Locale.ENGLISH, null );
             String jsonString = w.toString();
             JSONObject json = new JSONObject( jsonString );
 
@@ -255,7 +260,14 @@
         {
             final String pluginRoot = ( String ) request.getAttribute( WebConsoleConstants.ATTR_PLUGIN_ROOT );
             final String servicesRoot = getServicesRoot( request );
-            this.renderJSON(response, reqInfo.bundle, pluginRoot, servicesRoot, request.getLocale());
+            try
+            {
+                this.renderJSON(response, reqInfo.bundle, pluginRoot, servicesRoot, request.getLocale(), request.getParameter(FILTER_PARAM) );
+            }
+            catch (InvalidSyntaxException e)
+            {
+                response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Invalid LDAP filter specified");
+            }
 
             // nothing more to do
             return;
@@ -371,7 +383,14 @@
             }
             final String pluginRoot = ( String ) req.getAttribute( WebConsoleConstants.ATTR_PLUGIN_ROOT );
             final String servicesRoot = getServicesRoot( req );
-            this.renderJSON( resp, null, pluginRoot, servicesRoot, req.getLocale() );
+            try
+            {
+                this.renderJSON( resp, null, pluginRoot, servicesRoot, req.getLocale(), req.getParameter(FILTER_PARAM) );
+            }
+            catch (InvalidSyntaxException e)
+            {
+                resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Invalid LDAP filter specified");
+            }
         }
         else
         {
@@ -447,7 +466,7 @@
     /**
      * @see org.apache.felix.webconsole.AbstractWebConsolePlugin#renderContent(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
      */
-    protected void renderContent( HttpServletRequest request, HttpServletResponse response ) throws IOException
+    protected void renderContent( HttpServletRequest request, HttpServletResponse response ) throws IOException, ServletException
     {
         // get request info from request attribute
         final RequestInfo reqInfo = getRequestInfo(request);
@@ -463,38 +482,70 @@
         final String pluginRoot = ( String ) request.getAttribute( WebConsoleConstants.ATTR_PLUGIN_ROOT );
         final String servicesRoot = getServicesRoot ( request );
         StringWriter w = new StringWriter();
-        writeJSON(w, reqInfo.bundle, pluginRoot, servicesRoot, request.getLocale() );
+        try
+        {
+            writeJSON(w, reqInfo.bundle, pluginRoot, servicesRoot, request.getLocale(), request.getParameter(FILTER_PARAM) );
+        }
+        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 )
-        throws IOException
+    private void renderJSON( final HttpServletResponse response, final Bundle bundle, final String pluginRoot, final String servicesRoot, final Locale locale, final String filter )
+        throws IOException, InvalidSyntaxException
     {
         response.setContentType( "application/json" );
         response.setCharacterEncoding( "UTF-8" );
 
         final PrintWriter pw = response.getWriter();
-        writeJSON(pw, bundle, pluginRoot, servicesRoot, locale);
+        writeJSON(pw, bundle, pluginRoot, servicesRoot, locale, filter);
     }
 
 
-    private void writeJSON( final Writer pw, final Bundle bundle, final String pluginRoot, final String servicesRoot, final Locale locale )
-        throws IOException
+    private void writeJSON( final Writer pw, final Bundle bundle, final String pluginRoot, final String servicesRoot, final Locale locale, final String filter )
+        throws IOException, InvalidSyntaxException
     {
-        writeJSON( pw, bundle, pluginRoot, servicesRoot, false, locale );
+        writeJSON( pw, bundle, pluginRoot, servicesRoot, false, locale, filter );
     }
 
 
     private void writeJSON( final Writer pw, final Bundle bundle, final String pluginRoot,
-        final String servicesRoot, final boolean fullDetails, final Locale locale ) throws IOException
+        final String servicesRoot, final boolean fullDetails, final Locale locale, final String filter ) throws IOException, InvalidSyntaxException
     {
         final Bundle[] allBundles = this.getBundles();
         final Object[] status = getStatusLine(allBundles);
         final String statusLine = (String) status[5];
-        final Bundle[] bundles = ( bundle != null ) ? new Bundle[]
-            { bundle } : allBundles;
+        // filter bundles by headers
+        final Bundle[] bundles;
+        if (bundle != null)
+        {
+            bundles = new Bundle[] { bundle };
+        }
+        else if (filter != null)
+        {
+            Filter f = getBundleContext().createFilter(filter);
+            ArrayList list = new ArrayList(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 = new Bundle[list.size()];
+            list.toArray(bundles);
+        }
+        else
+        {
+            bundles = allBundles;
+        }
+        
         Util.sort( bundles, locale );
 
         final JSONWriter jw = new JSONWriter( pw );
@@ -618,6 +669,8 @@
         jw.value( Util.getHeaderValue(bundle, Constants.BUNDLE_VERSION) );
         jw.key( "symbolicName" );
         jw.value( Util.getHeaderValue(bundle, Constants.BUNDLE_SYMBOLICNAME) );
+        jw.key( "category" );
+        jw.value( Util.getHeaderValue(bundle, Constants.BUNDLE_CATEGORY) );
 
         if ( details )
         {
diff --git a/webconsole/src/main/native2ascii/OSGI-INF/l10n/bundle_bg.properties b/webconsole/src/main/native2ascii/OSGI-INF/l10n/bundle_bg.properties
index d12234d..91adb01 100644
--- a/webconsole/src/main/native2ascii/OSGI-INF/l10n/bundle_bg.properties
+++ b/webconsole/src/main/native2ascii/OSGI-INF/l10n/bundle_bg.properties
@@ -114,6 +114,7 @@
 bundles.install_update=Инсталиране/обновяване...
 bundles.refreshPkg=Обновяване пакети
 bundles.name=Име
+bundles.category=Категория
 bundles.name.symb=Символно име
 bundles.status=Статус
 bundles.actions=Действия
@@ -144,6 +145,8 @@
 bundles.filter.apply=Прилагане филтър
 bundles.filter.clear=Изчистване филтър
 bundles.filter.help=Низ или регулярен израз, който се съдържа в ID-то, името, символното име или версията на бъндъла.
+bundles.filter.ldap=Филтриране всичко
+bundles.filter.ldap.tip=Когато натиснете този бутон се прилага допълнително филтриране, спрямо *всички* хедъри на бундълите. Очаква се изразът в полето да бъде валиден OSGi LDAP филтър.
 # states
 bundles.state.1=Деинсталиран
 bundles.state.2=Инсталиран
diff --git a/webconsole/src/main/resources/OSGI-INF/l10n/bundle.properties b/webconsole/src/main/resources/OSGI-INF/l10n/bundle.properties
index cd33fd6..8c05008 100644
--- a/webconsole/src/main/resources/OSGI-INF/l10n/bundle.properties
+++ b/webconsole/src/main/resources/OSGI-INF/l10n/bundle.properties
@@ -115,6 +115,7 @@
 bundles.refreshPkg=Refresh Packages
 bundles.name=Name
 bundles.name.symb=Symbolic Name
+bundles.category=Category
 bundles.status=Status
 bundles.actions=Actions
 # bundle details
@@ -143,7 +144,9 @@
 # filter
 bundles.filter.apply=Apply Filter
 bundles.filter.clear=Clear Filter
-bundles.filter.help=A string or RegExp expression which is matched againse bundle id, name, symbolic name or version.
+bundles.filter.help=A string or RegExp expression which is matched againse bundle id, name, symbolic name or version. In case 'Filter All' is used, the value must be a valid LDAP string.
+bundles.filter.ldap=Filter All
+bundles.filter.ldap.tip=When clicked, a server-side matching is performed agains *all* bundle manifest headers. The expression is expected to be a valid OSGi Filter
 # states
 bundles.state.1=Uninstalled
 bundles.state.2=Installed
diff --git a/webconsole/src/main/resources/res/ui/bundles.css b/webconsole/src/main/resources/res/ui/bundles.css
index 086a196..6193ed9 100644
--- a/webconsole/src/main/resources/res/ui/bundles.css
+++ b/webconsole/src/main/resources/res/ui/bundles.css
@@ -24,3 +24,7 @@
 .col_Actions { width: 121px; }
 .filterBox      { float: left; margin-left: 1em }
 #uploadDialog, div.ui-dialog-buttonpane button { font-size: 50% }
+.symName                 { font-style: italic }
+.symName:before { content: " (" }
+.symName:after   { content: ")"  }
+.filterClear        { display: inline-block; vertical-align: middle }
diff --git a/webconsole/src/main/resources/res/ui/bundles.js b/webconsole/src/main/resources/res/ui/bundles.js
index b7467d3..c739abd 100644
--- a/webconsole/src/main/resources/res/ui/bundles.js
+++ b/webconsole/src/main/resources/res/ui/bundles.js
@@ -31,6 +31,7 @@
         }
     }
     if ( drawDetails && eventData.data.length == 1 ) {
+		$('.filterBox input, .filterBox button').addClass('ui-state-disabled');
         renderDetails(eventData.data[0]);    
     } else if ( currentBundle != null ) {
         var id = currentBundle;
@@ -42,7 +43,7 @@
 
 function entry( /* Object */ bundle, filter ) {
 	var matches = !(filter && typeof filter.test == 'function') ? true :
-		filter.test(bundle.id) || filter.test(bundle.name) || filter.test(bundle.symbolicName) || filter.test(bundle.version);
+		filter.test(bundle.id) || filter.test(bundle.name) || filter.test(bundle.symbolicName) || filter.test(bundle.version) || filter.test(bundle.category);
 
 	if (matches) entryInternal( bundle ).appendTo(bundlesBody);
 }
@@ -60,14 +61,14 @@
 function entryInternal( /* Object */ bundle ) {
 	var tr = bundlesTemplate.clone();
     var id = bundle.id;
-    var name = bundle.name;
+    var name = bundle.name + '<span class="symName">' + bundle.symbolicName + '</span>';
 
 	tr.attr('id', 'entry'+id);
 	tr.find('td:eq(0)').text(id);
 	tr.find('td:eq(1) span:eq(0)').attr('id', 'img'+id).click(function() {showDetails(id)});
 	tr.find('td:eq(1) span:eq(1)').html( drawDetails ? name : '<a href="' + pluginRoot + '/' + id + '">' + name + '</a>' );
 	tr.find('td:eq(2)').text( bundle.version );
-	tr.find('td:eq(3)').text( bundle.symbolicName );
+	tr.find('td:eq(3)').text( bundle.category );
 	tr.find('td:eq(4)').text( stateString(bundle) );
 	if (id == 0) { // system bundle has no actions
 		tr.find('td:eq(5) ul').addClass('ui-helper-hidden');
@@ -184,8 +185,8 @@
 	});
 
 	// filter
-	$('input.filter').click(function() {$(this).val('')});
 	$('.filterApply').click(function() {
+		if ($(this).hasClass('ui-state-disabled')) return;
 		var el = $(this).parent().find('input.filter');
 		var filter = el.length && el.val() ? new RegExp(el.val()) : false;
 		renderData(lastBundleData, filter);
@@ -195,8 +196,15 @@
 		return false;
 	});
 	$('.filterClear').click(function() {
+		if ($(this).hasClass('ui-state-disabled')) return;
 		$('input.filter').val('');
-		renderData(lastBundleData);
+		loadData();
+	});
+	$('.filterLDAP').click(function() {
+		if ($(this).hasClass('ui-state-disabled')) return;
+		var el = $(this).parent().find('input.filter');
+		var filter = el.val();
+		if (filter) $.get(pluginRoot + '/.json', { 'filter' : filter }, renderData, 'json');
 	});
 
 	// upload dialog
diff --git a/webconsole/src/main/resources/templates/bundles.html b/webconsole/src/main/resources/templates/bundles.html
index a62da40..8f956db 100644
--- a/webconsole/src/main/resources/templates/bundles.html
+++ b/webconsole/src/main/resources/templates/bundles.html
@@ -54,11 +54,12 @@
 <form method="post" enctype="multipart/form-data" action="" class="filterForm">
 	<div class="ui-widget-header ui-corner-top buttonGroup">
 		<div class="filterBox">
-			<input class="filter" value="^Apache" title="${bundles.filter.help}" />
-			<button class="filterApply" type="button">${bundles.filter.apply}</button>
-			<button class="filterClear" type="button">${bundles.filter.clear}</button>
+			<input  class="filter" value="" title="${bundles.filter.help}" />
+			<span   class="filterClear ui-icon ui-icon-close" title="${bundles.filter.clear}">&nbsp;</span>
+			<button class="filterApply" title="${bundles.filter.help}">${bundles.filter.apply}</button>
+			<button class="filterLDAP"  title="${bundles.filter.ldap.tip}">${bundles.filter.ldap}</button>
 		</div>
-		<button class="reloadButton" type="button" style="margin-left: 60px;">${reload}</button>
+		<button class="reloadButton" type="button">${reload}</button>
 		<button class="installButton" type="button">${bundles.install_update}</button>
 		<button class="refreshPackages" type="button">${bundles.refreshPkg}</button>
 	</div>
@@ -70,7 +71,7 @@
 			<th class="col_Id">${id}</th>
 			<th class="col_Name">${bundles.name}</th>
 			<th class="col_Version">${version}</th>
-			<th class="col_Symbolic_Name">${bundles.name.symb}</th>
+			<th class="col_Symbolic_Name">${bundles.category}</th>
 			<th class="col_Status">${bundles.status}</th>
 			<th class="col_Actions">${bundles.actions}</th>
 		</tr>
@@ -102,11 +103,12 @@
 <form method="post" enctype="multipart/form-data" action="" class="filterForm">
 	<div class="ui-widget-header ui-corner-bottom buttonGroup">
 		<div class="filterBox">
-			<input class="filter" value="^Apache" title="${bundles.filter.help}" />
-			<button class="filterApply" type="button">${bundles.filter.apply}</button>
-			<button class="filterClear" type="button">${bundles.filter.clear}</button>
+			<input  class="filter" value="" title="${bundles.filter.help}" />
+			<span   class="filterClear ui-icon ui-icon-close" title="${bundles.filter.clear}">&nbsp;</span>
+			<button class="filterApply" title="${bundles.filter.help}">${bundles.filter.apply}</button>
+			<button class="filterLDAP"  title="${bundles.filter.ldap.tip}">${bundles.filter.ldap}</button>
 		</div>
-		<button class="reloadButton" type="button" style="margin-left: 60px;">${reload}</button>
+		<button class="reloadButton" type="button">${reload}</button>
 		<button class="installButton" type="button">${bundles.install_update}</button>
 		<button class="refreshPackages" type="button">${bundles.refreshPkg}</button>
 	</div>