FELIX-1988 Apply 16.license_plugin.patch by Valentin Valchev (thanks)
FELIX-1910 Refactor LicenseServlet to support on-demand loading of licenses (part of 16.license_plugin.patch)
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@911770 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/misc/LicenseServlet.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/misc/LicenseServlet.java
index ef819bd..8aa4d17 100644
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/misc/LicenseServlet.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/misc/LicenseServlet.java
@@ -19,263 +19,265 @@
package org.apache.felix.webconsole.internal.misc;
-import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.PrintWriter;
-import java.io.Reader;
import java.net.URL;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
+import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils;
-import org.apache.felix.webconsole.AbstractWebConsolePlugin;
-import org.apache.felix.webconsole.WebConsoleConstants;
+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.JSONException;
-import org.json.JSONWriter;
+import org.json.JSONObject;
import org.osgi.framework.Bundle;
-import org.osgi.service.component.ComponentContext;
/**
- * The <code>LicenseServlet</code> TODO
+ * LicenseServlet provides the licenses plugin that browses through the bundles,
+ * searching for common license files.
+ *
+ * TODO: add support for 'Bundle-License' manifest header
*/
-public class LicenseServlet extends AbstractWebConsolePlugin implements OsgiManagerPlugin
+public final class LicenseServlet extends SimpleWebConsolePlugin implements OsgiManagerPlugin
{
+ // common names (without extension) of the license files.
+ private static final String LICENSE_FILES[] = { "README", "DISCLAIMER", "LICENSE", "NOTICE" };
+
+ static final String LABEL = "licenses";
+ static final String TITLE = "Licenses";
+ static final String CSS[] = { "/res/ui/license.css" };
+
+ // templates
+ private final String TEMPLATE;
- private static final String[] CSS_REFS =
- { "res/ui/license.css" };
-
-
- public String getLabel()
+ /**
+ * Default constructor
+ */
+ public LicenseServlet()
{
- return "licenses";
+ super(LABEL, TITLE, CSS);
+
+ // load templates
+ TEMPLATE = readTemplateFile( "/templates/license.html" );
}
-
- public String getTitle()
+ /**
+ * @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
{
- return "Licenses";
+ final String bid = request.getParameter("bid");
+
+ if (bid != null)
+ {
+ Bundle bundle = getBundleContext().getBundle(Long.parseLong(bid));
+
+ // Check bundle
+ if (bundle == null)
+ {
+ response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
+ "No bundle with ID " + bid);
+ return;
+ }
+
+ // Check if URL is given and *validate* if it is a license file.
+ // Otherwise, using this servlet, an intruder can read ANY file in the bundle
+ final String url = request.getParameter("url"); // file location
+ if (url == null)
+ {
+ response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
+ "Missing parameter 'url'");
+ return;
+ }
+
+ String name = url.substring(url.lastIndexOf('/') + 1);
+ boolean isLicense = false;
+ for (int i = 0; !isLicense && i < LICENSE_FILES.length; i++)
+ {
+ isLicense = name.startsWith(LICENSE_FILES[i]);
+ }
+ if (!isLicense)
+ {
+ response.sendError(HttpServletResponse.SC_FORBIDDEN,
+ "Requested non-license file, go away!");
+ return;
+ }
+
+ final String jar = request.getParameter("jar"); // inner Jar file
+ response.setContentType("text/plain");
+
+ if (jar == null)
+ {
+ InputStream input = bundle.getResource(url).openStream();
+ try
+ {
+ IOUtils.copy(input, response.getWriter());
+ }
+ finally
+ {
+ IOUtils.closeQuietly(input);
+ }
+ }
+ else
+ { // license is in a nested JAR
+ ZipInputStream zin = null;
+ InputStream input = bundle.getResource(jar).openStream();
+ try
+ {
+ zin = new ZipInputStream(input);
+ for (ZipEntry zentry = zin.getNextEntry(); zentry != null; zentry = zin.getNextEntry())
+ {
+ if (url.equals(zentry.getName()))
+ {
+ IOUtils.copy(zin, response.getWriter());
+ return;
+ }
+ }
+ }
+ finally
+ {
+
+ IOUtils.closeQuietly(zin);
+ IOUtils.closeQuietly(input);
+ }
+
+ throw new ServletException("License file:" + url + " not found!");
+ }
+
+ }
+ else
+ {
+ super.doGet(request, response);
+ }
}
-
- protected String[] getCssReferences()
+ /**
+ * @see org.apache.felix.webconsole.AbstractWebConsolePlugin#renderContent(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
+ */
+ protected void renderContent( HttpServletRequest request, HttpServletResponse res ) throws IOException
{
- return CSS_REFS;
- }
-
-
- protected void renderContent( HttpServletRequest req, HttpServletResponse res ) throws IOException
- {
- PrintWriter pw = res.getWriter();
-
- final String appRoot = ( String ) req.getAttribute( WebConsoleConstants.ATTR_APP_ROOT );
- Util.script( pw, appRoot, "license.js" );
-
Bundle[] bundles = getBundleContext().getBundles();
Util.sort( bundles );
- Util.startScript( pw );
- pw.print( "bundleData = " );
- JSONWriter jw = new JSONWriter( pw );
+ // prepare variables
+ DefaultVariableResolver vars = ( ( DefaultVariableResolver ) WebConsoleUtil.getVariableResolver( request ) );
+ vars.put( "__data__", getBundleData(bundles).toString());
+
+ res.getWriter().print(TEMPLATE);
+ }
+
+ private static final JSONObject getBundleData(Bundle[] bundles) throws IOException
+ {
+ JSONObject ret = new JSONObject();
try
{
- jw.object();
- for ( int i = 0; i < bundles.length; i++ )
+ for (int i = 0; i < bundles.length; i++)
{
Bundle bundle = bundles[i];
- jw.key( String.valueOf( bundle.getBundleId() ) );
- jw.object();
-
- jw.key( "title" );
- jw.value( Util.getName( bundle ) );
-
- jw.key( "files" );
- jw.object();
- findResource( jw, bundle, new String[]
- { "README", "DISCLAIMER", "LICENSE", "NOTICE" } );
- jw.endObject();
-
- jw.endObject();
+ JSONObject files = findResource(bundle, LICENSE_FILES);
+ if (files.length() > 0)
+ { // has resources
+ JSONObject data = new JSONObject();
+ data.put("bid", bundle.getBundleId());
+ data.put("title", Util.getName(bundle));
+ data.put("files", files);
+ ret.put(String.valueOf(bundle.getBundleId()), data);
+ }
}
- jw.endObject();
- pw.println( ";" );
}
- catch ( JSONException je )
+ catch (JSONException je)
{
- throw new IOException( je.toString() );
+ throw new IOException(je.toString());
}
- Util.endScript( pw );
-
- pw.println( "<div id='licenseContent'>" );
-
- pw.println( "<div id='licenseLeft'>" );
- for ( int i = 0; i < bundles.length; i++ )
- {
- Bundle bundle = bundles[i];
- String link = "displayBundle( \"" + bundle.getBundleId() + "\" );";
- pw.println( "<a href='javascript:" + link + "'>" + Util.getName( bundle ) + "</a><br />" );
-
- }
- pw.println( "</div>" );
-
- pw.println( "<div id='licenseRight'>" );
- pw.println( "<div id='licenseButtons' class='licenseButtons'> </div>" );
- pw.println( "<br />" );
- pw.println( "<div id='licenseDetails' class='licenseDetails'> </div>" );
- pw.println( "</div>" );
-
- pw.println( "<div id='licenseClear'> </div>" );
-
- pw.println( "</div>" ); // licenseContent
-
- Util.startScript( pw );
- pw.println( "displayBundle( '0' );" );
- Util.endScript( pw );
+ return ret;
}
- private String getName( String path )
+ private static final String getName( String path )
{
return path.substring( path.lastIndexOf( '/' ) + 1 );
}
-
- private void findResource( JSONWriter jw, Bundle bundle, String[] patterns ) throws IOException, JSONException
+ private static final JSONObject findResource( Bundle bundle, String[] patterns )
+ throws IOException, JSONException
{
- jw.key( "Bundle Resources" ); // aka the bundle files
- jw.array();
- for ( int i = 0; i < patterns.length; i++ )
+
+ JSONObject ret = new JSONObject();
+
+ for ( int i = 0; i < patterns.length; i++)
{
- Enumeration entries = bundle.findEntries( "/", patterns[i] + "*", true );
+ Enumeration entries = bundle.findEntries( "/", patterns[i] + "*", true);
if ( entries != null )
{
while ( entries.hasMoreElements() )
{
- URL url = ( URL ) entries.nextElement();
- jw.object();
- jw.key( "url" );
- jw.value( getName( url.getPath() ) );
- jw.key( "data" );
- jw.value( readResource( url ) );
- jw.endObject();
+ URL url = (URL) entries.nextElement();
+ JSONObject entry = new JSONObject();
+ entry.put( "path", url.getPath() );
+ entry.put( "url", getName(url.getPath()) );
+ ret.append( "__res__", entry );
}
}
}
- jw.endArray();
- Enumeration entries = bundle.findEntries( "/", "*.jar", true );
+ Enumeration entries = bundle.findEntries("/", "*.jar", true);
if ( entries != null )
{
while ( entries.hasMoreElements() )
{
- URL url = ( URL ) entries.nextElement();
-
- jw.key( "Embedded " + getName( url.getPath() ) );
- jw.array();
+ URL url = (URL) entries.nextElement();
+ final String resName = getName( url.getPath() );
InputStream ins = null;
try
{
ins = url.openStream();
- ZipInputStream zin = new ZipInputStream( ins );
+ ZipInputStream zin = new ZipInputStream(ins);
for ( ZipEntry zentry = zin.getNextEntry(); zentry != null; zentry = zin.getNextEntry() )
{
String name = zentry.getName();
// ignore directory entries
- if ( name.endsWith( "/" ) )
+ if ( name.endsWith("/") )
{
continue;
}
// cut off path and use file name for checking against patterns
- name = name.substring( name.lastIndexOf( '/' ) + 1 );
+ name = name.substring(name.lastIndexOf('/') + 1);
for ( int i = 0; i < patterns.length; i++ )
{
- if ( name.startsWith( patterns[i] ) )
+ if ( name.startsWith(patterns[i]) )
{
- jw.object();
- jw.key( "url" );
- jw.value( getName( name ) );
- jw.key( "data" );
- jw.value( readResource( new FilterInputStream( zin )
- {
- public void close()
- {
- // nothing for now
- }
- } ) );
- jw.endObject();
- break;
+ JSONObject entry = new JSONObject();
+ entry.put( "jar", url.getPath() );
+ entry.put( "path", zentry.getName() );
+ entry.put( "url", getName(name) );
+ ret.append( resName, entry );
}
}
}
}
finally
{
- IOUtils.closeQuietly( ins );
+ IOUtils.closeQuietly(ins);
}
- jw.endArray();
- }
- }
- }
-
-
- private String getResource( Bundle bundle, String[] path ) throws IOException
- {
- for ( int i = 0; i < path.length; i++ )
- {
- URL resource = bundle.getResource( path[i] );
- if ( resource != null )
- {
- return readResource( resource );
}
}
- return null;
- }
-
-
- private String readResource( URL resource ) throws IOException
- {
- return readResource( resource.openStream() );
- }
-
-
- private String readResource( InputStream resource ) throws IOException
- {
- try
- {
- // return new String(IOUtils.toCharArray(resource, "ISO-8859-1"));
- // the method below is faster that the one above
- return new String(IOUtils.toByteArray(resource), "ISO-8859-1");
- }
- finally
- {
- IOUtils.closeQuietly(resource);
- }
- }
-
-
- protected void activate( ComponentContext context )
- {
- activate( context.getBundleContext() );
- }
-
-
- protected void deactivate( ComponentContext context )
- {
- deactivate();
+ return ret;
}
}
diff --git a/webconsole/src/main/resources/OSGI-INF/l10n/bundle.properties b/webconsole/src/main/resources/OSGI-INF/l10n/bundle.properties
index 20de138..cac6186 100644
--- a/webconsole/src/main/resources/OSGI-INF/l10n/bundle.properties
+++ b/webconsole/src/main/resources/OSGI-INF/l10n/bundle.properties
@@ -165,3 +165,10 @@
config.del.config=Configuration:
config.del.bundle=Bundle:
config.unbind.ask=Are you sure to unbind this configuration ?
+
+
+# License plugin
+license.status.ok=The following bundles contains license files.
+license.status.none=No bundles with license files available
+license.resources=Bundle Resources:
+license.resources.embedded=Embedded {0}:
\ No newline at end of file
diff --git a/webconsole/src/main/resources/res/ui/license.css b/webconsole/src/main/resources/res/ui/license.css
index f371c35..b2bcba4 100644
--- a/webconsole/src/main/resources/res/ui/license.css
+++ b/webconsole/src/main/resources/res/ui/license.css
@@ -13,37 +13,28 @@
* 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.
- */ /* CSS Document */
+ */
+
#licenseContent {
- position: relative;
- margin-top: 25px; padding : 5px;
- width: 100%;
- padding: 5px;
+ width: 100%;
}
-
#licenseLeft {
- width: 300px;
- border-right: 1px black solid;
+ float: left;
+ width: 25%;
+ padding: 0;
+ margin-right: 1em;
}
-
+#licenseLeft a {
+ display: block;
+ width: 100%;
+ margin: 0;
+ text-decoration: none;
+}
#licenseRight {
- position: absolute;
- top: 5px;
- left: 305px;
- padding: 5px;
- width: 650px;
- border-left: 1px black solid;
+ float: left;
+ width: 70%;
}
-
-#licenseClear {
- clear: both;
- height: 1px;
+#licenseDetails {
+ width: 100%;
+ height: 100%;
}
-
-.licenseButtons {
- padding: 5px;
-}
-
-.licenseDetails {
- padding: 5px;
-}
\ No newline at end of file
diff --git a/webconsole/src/main/resources/res/ui/license.js b/webconsole/src/main/resources/res/ui/license.js
index a8cbb1f..35018d4 100644
--- a/webconsole/src/main/resources/res/ui/license.js
+++ b/webconsole/src/main/resources/res/ui/license.js
@@ -15,6 +15,9 @@
* limitations under the License.
*/
+var licenseButtons = false;
+var licenseDetails = false;
+
function displayBundle(/* String */ bundleId)
{
var theBundleData = bundleData[bundleId];
@@ -25,7 +28,6 @@
var title = theBundleData.title;
- var licenseButtons = document.getElementById('licenseButtons');
if (licenseButtons) {
var innerHTML = "";
@@ -36,47 +38,42 @@
for (var idx in entry)
{
var descr = entry[idx];
- buttons += "<a href='javascript:displayFile(\"" + bundleId + "\", \"" + name + "\", " + idx + ");'"
- + " >" + descr.url + "</a> ";
+ var jar = descr.jar ? '&jar=' + descr.jar : ''; // inner jar attribute
+ buttons += '<a href="' + pluginRoot + '?bid=' + bundleId + '&url=' + descr.path + jar + '" target="licenseDetails">' + descr.url + '</a> ';
}
if (buttons)
{
+ // apply i18n
+ name = '__res__' == name ? i18n.resources : i18n.resources_emb.msgFormat( name );
innerHTML += name + ": " + buttons + "<br />";
}
}
-
- if (!innerHTML)
- {
- innerHTML = "<em>The Bundle contains neither LICENSE nor NOTICE files</em>";
- }
-
- licenseButtons.innerHTML = "<h1>" + title + "</h1>" + innerHTML;
+
+ licenseButtons.html("<h1>" + title + "</h1>" + innerHTML);
}
- var licenseDetails = document.getElementById('licenseDetails');
- if (licenseDetails)
- {
- licenseDetails.innerHTML = "";
- }
+ licenseDetails.html("");
+ $("#licenseLeft a").removeClass('ui-state-default ui-corner-all');
+ $("#licenseLeft #" +bundleId).addClass('ui-state-default ui-corner-all');
}
-function displayFile ( /* String */ bundleId, /* String */ name, /* int */ idx )
-{
- var theBundleData = bundleData[bundleId];
- if (!theBundleData)
- {
- return;
- }
-
- var file = theBundleData.files[name][idx];
- if (!file)
- {
- return;
- }
-
- var licenseDetails = document.getElementById('licenseDetails');
- if (licenseDetails)
- {
- licenseDetails.innerHTML = "<h3>" + name + ": " + file.url + "</h3><pre>" + file.data + "</pre>";
- }
-}
+
+$(document).ready(function() {
+ // init elements cache
+ licenseButtons = $("#licenseButtons");
+ licenseDetails = $("#licenseDetails")
+
+ // render list of bundles
+ var txt = "";
+ for(id in bundleData) {
+ txt += '<a id="' + id + '" href="javascript:displayBundle(\'' + id + '\')">' + bundleData[id]['title'] + '</a>';
+ }
+ if (txt) {
+ $("#licenseLeft").html(txt);
+ } else {
+ $(".statline").html(i18n.status_none);
+ }
+
+ // display first element
+ for(i in bundleData) {displayBundle(i);break;}
+});
\ No newline at end of file
diff --git a/webconsole/src/main/resources/templates/license.html b/webconsole/src/main/resources/templates/license.html
new file mode 100644
index 0000000..2d59c89
--- /dev/null
+++ b/webconsole/src/main/resources/templates/license.html
@@ -0,0 +1,28 @@
+<script type="text/javascript" src="res/ui/license.js"></script>
+<script type="text/javascript">
+// <![CDATA[
+var bundleData = ${__data__};
+// i18n
+var i18n = {
+ status_ok : '${license.status.ok}', // The following bundles contains license files.
+ status_none : '${license.status.none}', // No bundles with license files available
+ resources : '${license.resources}', // 'Bundle Resources'
+ resources_emb: '${license.resources.embedded}' // 'Embedded {0}'
+}
+// ]]>
+</script>
+
+<!-- status line -->
+<p class="statline">${license.status.ok}</p>
+
+<div id="licenseContent">
+ <div id="licenseLeft" class="ui-widget-content ui-corner-all">
+ <!-- here comes the bundles links -->
+ </div>
+ <div id="licenseRight">
+ <div id="licenseButtons"> </div>
+ <br />
+ <iframe id="licenseDetails" name="licenseDetails" frameborder="0" class="ui-widget-content"></iframe>
+ </div>
+ <div class="ui-helper-clearfix"> </div>
+</div>