FELIX-3874 : Create new status printer module
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1439568 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/status-printer/pom.xml b/status-printer/pom.xml
new file mode 100644
index 0000000..a2e76b0
--- /dev/null
+++ b/status-printer/pom.xml
@@ -0,0 +1,101 @@
+<!--
+ 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>felix-parent</artifactId>
+ <version>2.1</version>
+ <relativePath>../pom/pom.xml</relativePath>
+ </parent>
+
+ <artifactId>org.apache.felix.status</artifactId>
+ <packaging>bundle</packaging>
+ <version>0.0.1-SNAPSHOT</version>
+
+ <name>Apache Felix Status</name>
+ <description>
+ Status Provider
+ </description>
+
+ <build>
+ <directory>${bundle.build.name}</directory>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <version>2.3.7</version>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Bundle-Category>osgi</Bundle-Category>
+ <Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
+ <Bundle-Vendor>The Apache Software Foundation</Bundle-Vendor>
+ <DynamicImport-Package>javax.servlet, javax.servlet.http</DynamicImport-Package>
+ <Bundle-Activator>org.apache.felix.status.impl.Activator</Bundle-Activator>
+ </instructions>
+ </configuration>
+ </plugin>
+ <!--
+ Configure default compilation for Java 5
+ -->
+ <plugin>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>1.5</source>
+ <target>1.5</target>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.core</artifactId>
+ <version>4.2.0</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.compendium</artifactId>
+ <version>4.2.0</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ <version>1.5.0</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>servlet-api</artifactId>
+ <version>2.4</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>biz.aQute</groupId>
+ <artifactId>bndlib</artifactId>
+ <version>1.43.0</version>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/status-printer/src/main/java/org/apache/felix/status/PrinterMode.java b/status-printer/src/main/java/org/apache/felix/status/PrinterMode.java
new file mode 100644
index 0000000..40250f2
--- /dev/null
+++ b/status-printer/src/main/java/org/apache/felix/status/PrinterMode.java
@@ -0,0 +1,30 @@
+/*
+ * 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.status;
+
+/**
+ * Enumeration for the different printer modes.
+ */
+public enum PrinterMode {
+
+ TEXT, // plain text
+ HTML_BODY, // HTML which can be placed inside a HTML body element (no external references)
+ JSON, // JSON output
+ ZIP_FILE // file content for a zip
+}
diff --git a/status-printer/src/main/java/org/apache/felix/status/StatusPrinter.java b/status-printer/src/main/java/org/apache/felix/status/StatusPrinter.java
new file mode 100644
index 0000000..d4b446e
--- /dev/null
+++ b/status-printer/src/main/java/org/apache/felix/status/StatusPrinter.java
@@ -0,0 +1,86 @@
+/*
+ * 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.status;
+
+import java.io.PrintWriter;
+
+/**
+ * The <code>StatusPrinter</code> is a service interface to be
+ * implemented by providers which want to hook into the display of the
+ * current configuration and state of the OSGi framework and application.
+ *
+ * A status printer must configure at least these three configuration properties
+ * <ul>
+ * <li>{@link #CONFIG_PRINTER_MODES} - the supported modes</li>
+ * <li>{@link #CONFIG_TITLE} - the printer title</li>
+ * <li>{@link #CONFIG_NAME} - the printer name</li>
+ * </ul>
+ */
+public interface StatusPrinter {
+
+ /**
+ * The service name under which services of this class must be registered
+ * to be picked for inclusion in the configuration report.
+ */
+ String SERVICE = StatusPrinter.class.getName(); //$NON-NLS-1$
+
+ /**
+ * The property defining the supported rendering modes.
+ * The value of this property is either a string or a string array containing
+ * valid names of {@link PrinterMode}.
+ *
+ * If this property is missing or contains invalid values,
+ * the printer is ignored.
+ */
+ String CONFIG_PRINTER_MODES = "felix.statusprinter.modes"; //$NON-NLS-1$
+
+ /**
+ * The unique name of the printer.
+ * If this property is missing the printer is ignored.
+ * If there are two or more services with the same name, the
+ * services with the highest ranking is used.
+ */
+ String CONFIG_NAME = "felix.statusprinter.name"; //$NON-NLS-1$
+
+ /**
+ * The title displayed by tools when this printer is used. It should be
+ * descriptive but short.
+ * If this property is missing the printer is ignored.
+ */
+ String CONFIG_TITLE = "felix.statusprinter.title"; //$NON-NLS-1$
+
+ /**
+ * The category under which this printer is categorized.
+ * This property is optional.
+ */
+ String CONFIG_CATEGORY = "felix.statusprinter.category"; //$NON-NLS-1$
+
+ /**
+ * Prints the configuration report to the given <code>printWriter</code>.
+ * Implementations are free to print whatever information they deem useful.
+ *
+ * If a printer is invoked with a mode it doesn't support ({@link #CONFIG_PRINTER_MODES})
+ * the printer should just do/print nothing and directly return.
+ *
+ * @param mode The render mode.
+ * @param printWriter where to write the configuration data. It might be flushed,
+ * but must not be closed.
+ */
+ void print( PrinterMode mode, PrintWriter printWriter );
+}
diff --git a/status-printer/src/main/java/org/apache/felix/status/StatusPrinterHandler.java b/status-printer/src/main/java/org/apache/felix/status/StatusPrinterHandler.java
new file mode 100644
index 0000000..18bde07
--- /dev/null
+++ b/status-printer/src/main/java/org/apache/felix/status/StatusPrinterHandler.java
@@ -0,0 +1,46 @@
+/*
+ * 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.status;
+
+
+/**
+ * The status printer handler can be used by clients to access
+ * a status printer. The handlers can be get from the {@link StatusPrinterManager}.
+ *
+ * For clients using status printers, a handler simplifies accessing and
+ * working with the status printer. A client should never lookup a
+ * status printer directly.
+ */
+public interface StatusPrinterHandler extends StatusPrinter, ZipAttachmentProvider {
+
+ /** The unique name of the printer. */
+ String getName();
+
+ /** The human readable title for the status printer. */
+ String getTitle();
+
+ /** The optional category for this printer. */
+ String getCategory();
+
+ /** All supported modes. */
+ PrinterMode[] getModes();
+
+ /** Whether the printer supports this mode. */
+ boolean supports( final PrinterMode mode );
+}
diff --git a/status-printer/src/main/java/org/apache/felix/status/StatusPrinterManager.java b/status-printer/src/main/java/org/apache/felix/status/StatusPrinterManager.java
new file mode 100644
index 0000000..617f5ce
--- /dev/null
+++ b/status-printer/src/main/java/org/apache/felix/status/StatusPrinterManager.java
@@ -0,0 +1,46 @@
+/*
+ * 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.status;
+
+/**
+ * The manager allows to access status printers.
+ * Instead of directly returning a status printer, a status
+ * printer handler is returned which provides access to the
+ * meta information of the printer and other utility methods.
+ */
+public interface StatusPrinterManager {
+
+ /**
+ * Get all status printer handlers.
+ * @return A list of handlers - might be empty.
+ */
+ StatusPrinterHandler[] getAllHandlers();
+
+ /**
+ * Get all handlers supporting the mode.
+ * @return A list of handlers - might be empty.
+ */
+ StatusPrinterHandler[] getHandlers( final PrinterMode mode);
+
+ /**
+ * Return a handler for the unique name.
+ * @return The corresponding handler or <code>null</code>.
+ */
+ StatusPrinterHandler getHandler( final String name );
+}
diff --git a/status-printer/src/main/java/org/apache/felix/status/ZipAttachmentProvider.java b/status-printer/src/main/java/org/apache/felix/status/ZipAttachmentProvider.java
new file mode 100644
index 0000000..37cb6b8
--- /dev/null
+++ b/status-printer/src/main/java/org/apache/felix/status/ZipAttachmentProvider.java
@@ -0,0 +1,53 @@
+/*
+ * 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.status;
+
+import java.io.IOException;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * This is an optional extension of the {@link StatusPrinter}.
+ * If a status printer implements this interface, the printer
+ * can add additional attachments to the output of the
+ * configuration zip.
+ *
+ * A service implementing this method must still register itself
+ * as a {@link StatusPrinter} but not as a
+ * {@link ZipAttachmentProvider} service.
+ */
+public interface ZipAttachmentProvider extends StatusPrinter {
+
+ /**
+ * Add attachments to the zip output stream.
+ * The attachment provider can add as many attachments in any format
+ * as it wants. However it should use the namePrefix to create unique
+ * names / paths inside the zip.
+ *
+ * The general pattern is: creating a zip entry by using the name prefix
+ * and a name, adding the entry to the zip output stream, writing
+ * the content of the file to the stream, and finally ending the
+ * zip entry.
+ *
+ * @param namePrefix Name prefix to use for zip entries. Ends with a slash.
+ * @param zos The zip output stream.
+ * @throws IOException
+ */
+ void addAttachments(final String namePrefix, final ZipOutputStream zos)
+ throws IOException;
+}
diff --git a/status-printer/src/main/java/org/apache/felix/status/impl/AbstractWebConsolePlugin.java b/status-printer/src/main/java/org/apache/felix/status/impl/AbstractWebConsolePlugin.java
new file mode 100644
index 0000000..3b62da8
--- /dev/null
+++ b/status-printer/src/main/java/org/apache/felix/status/impl/AbstractWebConsolePlugin.java
@@ -0,0 +1,447 @@
+/*
+ * 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.status.impl;
+
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.Writer;
+import java.text.MessageFormat;
+import java.util.Date;
+import java.util.zip.Deflater;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.felix.status.PrinterMode;
+import org.apache.felix.status.StatusPrinterHandler;
+import org.apache.felix.status.StatusPrinterManager;
+
+/**
+ * The web console plugin for a status printer.
+ */
+public abstract class AbstractWebConsolePlugin extends HttpServlet {
+
+ private static final long serialVersionUID = 1L;
+
+ /** The status printer manager. */
+ protected final StatusPrinterManager statusPrinterManager;
+
+ /**
+ * Constructor
+ * @param statusPrinterManager The manager
+ */
+ AbstractWebConsolePlugin(final StatusPrinterManager statusPrinterManager) {
+ this.statusPrinterManager = statusPrinterManager;
+ }
+
+ protected abstract StatusPrinterHandler getStatusPrinterHandler();
+
+ private void printConfigurationStatus( final ConfigurationWriter pw,
+ final PrinterMode mode,
+ final StatusPrinterHandler handler )
+ throws IOException {
+ if ( handler == null ) {
+ for(final StatusPrinterHandler sph : this.statusPrinterManager.getHandlers(mode)) {
+ pw.printStatus(mode, sph);
+ }
+ } else {
+ if ( handler.supports(mode) ) {
+ pw.printStatus(mode, handler);
+ }
+ }
+ }
+
+ /**
+ * Sets response headers to force the client to not cache the response
+ * sent back. This method must be called before the response is committed
+ * otherwise it will have no effect.
+ * <p>
+ * This method sets the <code>Cache-Control</code>, <code>Expires</code>,
+ * and <code>Pragma</code> headers.
+ *
+ * @param response The response for which to set the cache prevention
+ */
+ private final void setNoCache(final HttpServletResponse response) {
+ response.setHeader("Cache-Control", "no-cache"); //$NON-NLS-1$ //$NON-NLS-2$
+ response.addHeader("Cache-Control", "no-store"); //$NON-NLS-1$ //$NON-NLS-2$
+ response.addHeader("Cache-Control", "must-revalidate"); //$NON-NLS-1$ //$NON-NLS-2$
+ response.addHeader("Cache-Control", "max-age=0"); //$NON-NLS-1$ //$NON-NLS-2$
+ response.setHeader("Expires", "Thu, 01 Jan 1970 01:00:00 GMT"); //$NON-NLS-1$ //$NON-NLS-2$
+ response.setHeader("Pragma", "no-cache"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ @Override
+ protected void doGet(final HttpServletRequest request,
+ final HttpServletResponse response)
+ throws ServletException, IOException {
+ this.setNoCache( response );
+
+ // full request?
+ final StatusPrinterHandler handler;
+ if ( request.getPathInfo().lastIndexOf('/') > 0 ) {
+ handler = null; // all;
+ } else {
+ handler = this.getStatusPrinterHandler();
+ if ( handler == null ) {
+ response.sendError( HttpServletResponse.SC_NOT_FOUND );
+ return;
+ }
+ }
+
+ if ( request.getPathInfo().endsWith( ".txt" ) ) { //$NON-NLS-2$
+ response.setContentType( "text/plain; charset=utf-8" ); //$NON-NLS-2$
+ final ConfigurationWriter pw = new PlainTextConfigurationWriter( response.getWriter() );
+ printConfigurationStatus( pw, PrinterMode.TEXT, handler );
+ pw.flush();
+ } else if ( request.getPathInfo().endsWith( ".zip" ) ) { //$NON-NLS-2$
+ String type = getServletContext().getMimeType( request.getPathInfo() );
+ if ( type == null ) {
+ type = "application/x-zip"; //$NON-NLS-2$
+ }
+ response.setContentType( type );
+
+ final ZipOutputStream zip = new ZipOutputStream( response.getOutputStream() );
+ zip.setLevel( Deflater.BEST_SPEED );
+ zip.setMethod( ZipOutputStream.DEFLATED );
+
+ final Date now = new Date();
+ // create time stamp entry
+ final ZipEntry entry = new ZipEntry( "timestamp.txt" ); //$NON-NLS-2$
+ entry.setTime(now.getTime());
+ zip.putNextEntry( entry );
+ final StringBuilder sb = new StringBuilder();
+ sb.append("Date: ");
+ synchronized ( StatusPrinterAdapter.DISPLAY_DATE_FORMAT ) {
+ sb.append(StatusPrinterAdapter.DISPLAY_DATE_FORMAT.format(now));
+ }
+ sb.append(" (");
+ sb.append(String.valueOf(now.getTime()));
+ sb.append(")\n");
+
+ zip.write(sb.toString().getBytes("UTF-8"));
+ zip.closeEntry();
+
+ final ZipConfigurationWriter pw = new ZipConfigurationWriter( zip );
+ printConfigurationStatus( pw, PrinterMode.ZIP_FILE, handler );
+
+ zip.finish();
+ } else if ( request.getPathInfo().endsWith( ".nfo" ) ) {
+ if ( handler == null ) {
+ response.sendError( HttpServletResponse.SC_NOT_FOUND);
+ return;
+ }
+ response.setContentType( "text/html; charset=utf-8" );
+
+ final HtmlConfigurationWriter pw = new HtmlConfigurationWriter( response.getWriter() );
+ pw.println ( "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\"" );
+ pw.println ( " \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">" );
+ pw.println ( "<html xmlns=\"http://www.w3.org/1999/xhtml\">" );
+ pw.println ( "<head><title>dummy</title></head><body><div>" );
+
+ if ( handler.supports(PrinterMode.HTML_BODY) ) {
+ handler.print(PrinterMode.HTML_BODY, pw);
+ } else {
+ pw.enableFilter( true );
+ handler.print(PrinterMode.TEXT, pw);
+ pw.enableFilter( false );
+ }
+ pw.println( "</div></body></html>" );
+ return;
+ } else {
+ if ( handler == null ) {
+ response.sendError( HttpServletResponse.SC_NOT_FOUND);
+ return;
+ }
+ final HtmlConfigurationWriter pw = new HtmlConfigurationWriter(response.getWriter());
+ pw.println("<script type=\"text/javascript\">");
+ pw.println("// <![CDATA[");
+ pw.println("function pad(value) { if ( value < 10 ) { return '0' + value;} return '' + value;}");
+ pw.println("function downloadDump(ext, full) {");
+ pw.println(" if (full) {");
+ pw.println(" var now = new Date();");
+ pw.println(" var name = \"configuration-status-\" + now.getUTCFullYear() + pad(now.getUTCMonth() + 1) + pad(now.getUTCDate()) + \"-\" + pad(now.getUTCHours()) + pad(now.getUTCMinutes()) + pad(now.getUTCSeconds()) + \".\";");
+ pw.println(" location.href = location.href + \"/\" + name + ext;");
+ pw.println(" } else {");
+ pw.println(" location.href = location.href + '.' + ext;");
+ pw.println(" }");
+ pw.println("}");
+
+ pw.println("$(document).ready(function() {");
+ pw.println(" $('.downloadTxt').click(function() { downloadDump('txt', false)});");
+ pw.println(" $('.downloadZip').click(function() { downloadDump('zip', false)});");
+ pw.println(" $('.downloadFullZip').click(function() { downloadDump('zip', true)});");
+ pw.println(" $('.downloadFullTxt').click(function() { downloadDump('txt', true)});");
+ pw.println("});");
+ pw.println("// ]]>");
+ pw.println("</script>");
+ pw.println( "<br/><p class=\"statline\">");
+
+ final Date currentTime = new Date();
+ synchronized ( StatusPrinterAdapter.DISPLAY_DATE_FORMAT ) {
+ pw.print("Date: ");
+ pw.println(StatusPrinterAdapter.DISPLAY_DATE_FORMAT.format(currentTime));
+ }
+
+ pw.print("<button type=\"button\" class=\"downloadFullZip\" style=\"float: right; margin-right: 30px; margin-top: 5px;\">Download Full Zip</button>");
+ pw.print("<button type=\"button\" class=\"downloadFullTxt\" style=\"float: right; margin-right: 30px; margin-top: 5px;\">Download Full Text</button>");
+
+ if ( handler.supports(PrinterMode.ZIP_FILE) ) {
+ pw.print("<button type=\"button\" class=\"downloadZip\" style=\"float: right; margin-right: 30px; margin-top: 5px;\">Download As Zip</button>");
+ }
+ if ( handler.supports(PrinterMode.TEXT ) ) {
+ pw.print("<button type=\"button\" class=\"downloadTxt\" style=\"float: right; margin-right: 30px; margin-top: 5px;\">Download As Text</button>");
+ }
+
+ pw.println("<br/> </p>"); // status line
+ pw.print("<div>");
+ if ( handler.supports(PrinterMode.HTML_BODY) ) {
+ handler.print(PrinterMode.HTML_BODY, pw);
+ } else {
+ pw.enableFilter( true );
+ handler.print(PrinterMode.TEXT, pw);
+ pw.enableFilter( false );
+ }
+ pw.print("</div>");
+ }
+ }
+
+ /**
+ * Base class for all configuration writers.
+ */
+ private abstract static class ConfigurationWriter extends PrintWriter {
+
+ ConfigurationWriter( final Writer delegatee ) {
+ super( delegatee );
+ }
+
+ protected void title( final String title ) throws IOException {
+ // dummy implementation
+ }
+
+
+ protected void end() throws IOException {
+ // dummy implementation
+ }
+
+ public void printStatus(
+ final PrinterMode mode,
+ final StatusPrinterHandler handler)
+ throws IOException {
+ this.title(handler.getTitle());
+ handler.print(mode, this);
+ this.end();
+ }
+ }
+
+ /**
+ * The HTML configuration writer outputs the status as an HTML snippet.
+ */
+ private static class HtmlConfigurationWriter extends ConfigurationWriter {
+
+ // whether or not to filter "<" signs in the output
+ private boolean doFilter;
+
+
+ HtmlConfigurationWriter( final Writer delegatee ) {
+ super( delegatee );
+ }
+
+
+ void enableFilter( final boolean doFilter ) {
+ this.doFilter = doFilter;
+ }
+
+ // IE has an issue with white-space:pre in our case so, we write
+ // <br/> instead of [CR]LF to get the line break. This also works
+ // in other browsers.
+ @Override
+ public void println() {
+ if ( doFilter ) {
+ this.write('\n'); // write <br/>
+ } else {
+ super.println();
+ }
+ }
+
+ // some VM implementation directly write in underlying stream, instead of
+ // delegation to the write() method. So we need to override this, to make
+ // sure, that everything is escaped correctly
+ @Override
+ public void print(final String str) {
+ final char[] chars = str.toCharArray();
+ write(chars, 0, chars.length);
+ }
+
+
+ private final char[] oneChar = new char[1];
+
+ // always delegate to write(char[], int, int) otherwise in some VM
+ // it cause endless cycle and StackOverflowError
+ @Override
+ public void write(final int character) {
+ synchronized (oneChar) {
+ oneChar[0] = (char) character;
+ write(oneChar, 0, 1);
+ }
+ }
+
+ // write the characters unmodified unless filtering is enabled in
+ // which case the writeFiltered(String) method is called for filtering
+ @Override
+ public void write(char[] chars, int off, int len) {
+ if (doFilter) {
+ chars = this.escapeHtml(new String(chars, off, len)).toCharArray();
+ off = 0;
+ len = chars.length;
+ }
+ super.write(chars, off, len);
+ }
+
+ // write the string unmodified unless filtering is enabled in
+ // which case the writeFiltered(String) method is called for filtering
+ @Override
+ public void write( final String string, final int off, final int len ) {
+ write(string.toCharArray(), off, len);
+ }
+
+ /**
+ * Escapes HTML special chars like: <>&\r\n and space
+ *
+ *
+ * @param text the text to escape
+ * @return the escaped text
+ */
+ private String escapeHtml(final String text) {
+ final StringBuilder sb = new StringBuilder(text.length() * 4 / 3);
+ char ch, oldch = '_';
+ for (int i = 0; i < text.length(); i++) {
+ switch (ch = text.charAt(i)) {
+ case '<':
+ sb.append("<"); //$NON-NLS-1$
+ break;
+ case '>':
+ sb.append(">"); //$NON-NLS-1$
+ break;
+ case '&':
+ sb.append("&"); //$NON-NLS-1$
+ break;
+ case ' ':
+ sb.append(" "); //$NON-NLS-1$
+ break;
+ case '\r':
+ case '\n':
+ if (oldch != '\r' && oldch != '\n') // don't add twice <br>
+ sb.append("<br/>\n"); //$NON-NLS-1$
+ break;
+ default:
+ sb.append(ch);
+ }
+ oldch = ch;
+ }
+
+ return sb.toString();
+ }
+ }
+
+ /**
+ * The plain text configuration writer outputs the status as plain text.
+ */
+ private static class PlainTextConfigurationWriter extends ConfigurationWriter {
+
+ PlainTextConfigurationWriter( final Writer delegatee ) {
+ super( delegatee );
+ }
+
+ @Override
+ protected void title( final String title ) throws IOException {
+ print( "*** " );
+ print( title );
+ println( ":" );
+ }
+
+
+ @Override
+ protected void end() throws IOException {
+ println();
+ }
+ }
+
+ /**
+ * The ZIP configuration writer creates a zip with
+ * - txt output of a status printers (if supported)
+ * - json output of a status printers (if supported)
+ * - attachments from a status printer (if supported)
+ */
+ private static class ZipConfigurationWriter extends ConfigurationWriter {
+
+ private final ZipOutputStream zip;
+
+ private int counter;
+
+ ZipConfigurationWriter( final ZipOutputStream zip ) {
+ super( new OutputStreamWriter( zip ) );
+ this.zip = zip;
+ }
+
+ private String getFormattedTitle(final String title) {
+ return MessageFormat.format( "{0,number,000}-{1}", new Object[]
+ { new Integer( counter ), title } );
+ }
+
+ @Override
+ protected void title( final String title ) throws IOException {
+ counter++;
+
+ final String name = getFormattedTitle(title).concat(".txt");
+
+ final ZipEntry entry = new ZipEntry( name );
+ zip.putNextEntry( entry );
+ }
+
+ @Override
+ protected void end() throws IOException {
+ flush();
+
+ zip.closeEntry();
+ }
+
+ @Override
+ public void printStatus(
+ final PrinterMode mode,
+ final StatusPrinterHandler handler)
+ throws IOException {
+ super.printStatus(mode, handler);
+ final String title = getFormattedTitle(handler.getTitle());
+ handler.addAttachments(title.concat("/"), this.zip);
+ if ( handler.supports(PrinterMode.JSON) ) {
+ final String name = "json/".concat(title).concat(".json");
+
+ final ZipEntry entry = new ZipEntry( name );
+ zip.putNextEntry( entry );
+ handler.print(PrinterMode.JSON, this);
+ flush();
+
+ zip.closeEntry();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/status-printer/src/main/java/org/apache/felix/status/impl/Activator.java b/status-printer/src/main/java/org/apache/felix/status/impl/Activator.java
new file mode 100644
index 0000000..d43210d
--- /dev/null
+++ b/status-printer/src/main/java/org/apache/felix/status/impl/Activator.java
@@ -0,0 +1,73 @@
+/*
+ * 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.status.impl;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import org.apache.felix.status.StatusPrinterManager;
+import org.apache.felix.status.impl.webconsole.WebConsoleAdapter;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceRegistration;
+
+/**
+ * Activate bridges and register manager.
+ */
+public class Activator implements BundleActivator {
+
+ private StatusPrinterManagerImpl printerManager;
+
+ private ServiceRegistration managerRegistration;
+
+ private WebConsoleAdapter webAdapter;
+
+ /**
+ * @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext)
+ */
+ public void start(final BundleContext context) throws Exception {
+ this.webAdapter = new WebConsoleAdapter(context);
+ this.printerManager = new StatusPrinterManagerImpl(context);
+ final Dictionary<String, Object> props = new Hashtable<String, Object>();
+ props.put(Constants.SERVICE_DESCRIPTION, "Apache Felix Status Printer Manager");
+ props.put(Constants.SERVICE_VENDOR, "The Apache Software Foundation");
+ this.managerRegistration = context.registerService(
+ StatusPrinterManager.class.getName(),
+ this.printerManager, props);
+
+ }
+
+ /**
+ * @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
+ */
+ public void stop(final BundleContext context) throws Exception {
+ if( this.managerRegistration != null ) {
+ this.managerRegistration.unregister();
+ this.managerRegistration = null;
+ }
+ if ( this.printerManager != null ) {
+ this.printerManager.dispose();
+ this.printerManager = null;
+ }
+ if ( this.webAdapter != null ) {
+ this.webAdapter.dispose();
+ this.webAdapter = null;
+ }
+ }
+
+}
diff --git a/status-printer/src/main/java/org/apache/felix/status/impl/ClassUtils.java b/status-printer/src/main/java/org/apache/felix/status/impl/ClassUtils.java
new file mode 100644
index 0000000..6c02724
--- /dev/null
+++ b/status-printer/src/main/java/org/apache/felix/status/impl/ClassUtils.java
@@ -0,0 +1,58 @@
+/*
+ * 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.status.impl;
+
+import java.lang.reflect.Method;
+
+/**
+ * Utility methods for dynamic method invocations
+ */
+public class ClassUtils {
+
+ /**
+ * Search a method with the given name and signature.
+ * @return The method or <code>null</code> if not found.
+ */
+ public static Method searchMethod(final Class<?> clazz, final String mName, final Class<?>[] params) {
+ try {
+ final Method m = clazz.getMethod(mName, params);
+ m.setAccessible(true);
+ return m;
+ } catch (Throwable nsme) {
+ // ignore, we catch Throwable above to not only catch NoSuchMethodException
+ // but also other ones like ClassDefNotFoundError etc.
+ }
+ if ( clazz.getSuperclass() != null ) {
+ // try super class
+ return searchMethod(clazz.getSuperclass(), mName, params);
+ }
+ return null;
+ }
+
+ /**
+ * Invoke the method on the object with the arguments.
+ * @return The result of the method invocation or <code>null</code> if an exception occurs.
+ */
+ public static Object invoke(final Object obj, final Method m, final Object[] args) {
+ try {
+ return m.invoke(obj, args);
+ } catch (final Throwable e) {
+ // ignore
+ }
+ return null;
+ }
+}
diff --git a/status-printer/src/main/java/org/apache/felix/status/impl/DefaultWebConsolePlugin.java b/status-printer/src/main/java/org/apache/felix/status/impl/DefaultWebConsolePlugin.java
new file mode 100644
index 0000000..d13b2f5
--- /dev/null
+++ b/status-printer/src/main/java/org/apache/felix/status/impl/DefaultWebConsolePlugin.java
@@ -0,0 +1,132 @@
+/*
+ * 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.status.impl;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.zip.ZipOutputStream;
+
+import org.apache.felix.status.PrinterMode;
+import org.apache.felix.status.StatusPrinterHandler;
+import org.apache.felix.status.StatusPrinterManager;
+import org.apache.felix.status.impl.webconsole.ConsoleConstants;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceFactory;
+import org.osgi.framework.ServiceRegistration;
+
+/**
+ * The web console plugin for a status printer.
+ */
+public class DefaultWebConsolePlugin extends AbstractWebConsolePlugin implements StatusPrinterHandler {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Constructor
+ * @param statusPrinterAdapter The adapter
+ */
+ DefaultWebConsolePlugin(final StatusPrinterManager statusPrinterManager) {
+ super(statusPrinterManager);
+ }
+
+ @Override
+ protected StatusPrinterHandler getStatusPrinterHandler() {
+ return this;
+ }
+
+ /**
+ * @see org.apache.felix.status.StatusPrinterHandler#getTitle()
+ */
+ public String getTitle() {
+ return "Overview";
+ }
+
+ /**
+ * @see org.apache.felix.status.StatusPrinterHandler#getName()
+ */
+ public String getName() {
+ return "config";
+ }
+
+ /**
+ * @see org.apache.felix.status.StatusPrinterHandler#getCategory()
+ */
+ public String getCategory() {
+ return "Status";
+ }
+
+ /**
+ * @see org.apache.felix.status.StatusPrinterHandler#getModes()
+ */
+ public PrinterMode[] getModes() {
+ return new PrinterMode[] {PrinterMode.TEXT};
+ }
+
+ /**
+ * @see org.apache.felix.status.StatusPrinterHandler#supports(org.apache.felix.status.PrinterMode)
+ */
+ public boolean supports(final PrinterMode mode) {
+ return mode == PrinterMode.TEXT;
+ }
+
+ /**
+ * @see org.apache.felix.status.StatusPrinter#print(org.apache.felix.status.PrinterMode, java.io.PrintWriter)
+ */
+ public void print(final PrinterMode mode, final PrintWriter printWriter) {
+ final StatusPrinterHandler[] handlers = this.statusPrinterManager.getAllHandlers();
+ printWriter.print("Currently registered ");
+ printWriter.print(String.valueOf(handlers.length));
+ printWriter.println(" status printer.");
+ printWriter.println();
+ for(final StatusPrinterHandler handler : handlers) {
+ printWriter.println(handler.getTitle());
+ }
+ }
+
+ /**
+ * @see org.apache.felix.status.ZipAttachmentProvider#addAttachments(java.lang.String, java.util.zip.ZipOutputStream)
+ */
+ public void addAttachments(String namePrefix, ZipOutputStream zos)
+ throws IOException {
+ // no attachments support
+ }
+
+ public static ServiceRegistration register(final BundleContext context,
+ final StatusPrinterManager manager) {
+ final DefaultWebConsolePlugin dwcp = new DefaultWebConsolePlugin(manager);
+
+ final Dictionary<String, Object> props = new Hashtable<String, Object>();
+ props.put(ConsoleConstants.PLUGIN_LABEL, dwcp.getName());
+ props.put(ConsoleConstants.PLUGIN_TITLE, dwcp.getTitle());
+ props.put(ConsoleConstants.PLUGIN_CATEGORY, dwcp.getCategory());
+ return context.registerService(ConsoleConstants.INTERFACE_SERVLET, new ServiceFactory() {
+
+ public void ungetService(final Bundle bundle, final ServiceRegistration registration,
+ final Object service) {
+ // nothing to do
+ }
+
+ public Object getService(final Bundle bundle, final ServiceRegistration registration) {
+ return dwcp;
+ }
+
+ }, props);
+ }
+}
\ No newline at end of file
diff --git a/status-printer/src/main/java/org/apache/felix/status/impl/StatusPrinterAdapter.java b/status-printer/src/main/java/org/apache/felix/status/impl/StatusPrinterAdapter.java
new file mode 100644
index 0000000..6de7079
--- /dev/null
+++ b/status-printer/src/main/java/org/apache/felix/status/impl/StatusPrinterAdapter.java
@@ -0,0 +1,214 @@
+/*
+ * 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.status.impl;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.lang.reflect.Method;
+import java.text.DateFormat;
+import java.util.Comparator;
+import java.util.Locale;
+import java.util.zip.ZipOutputStream;
+
+import org.apache.felix.status.PrinterMode;
+import org.apache.felix.status.StatusPrinter;
+import org.apache.felix.status.StatusPrinterHandler;
+import org.apache.felix.status.StatusPrinterManager;
+import org.apache.felix.status.ZipAttachmentProvider;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+
+/**
+ * Helper class for a status printer.
+ */
+public class StatusPrinterAdapter implements StatusPrinterHandler, Comparable<StatusPrinterAdapter> {
+
+ /**
+ * Formatter pattern to render the current time of status generation.
+ */
+ static final DateFormat DISPLAY_DATE_FORMAT = DateFormat.getDateTimeInstance( DateFormat.LONG,
+ DateFormat.LONG, Locale.US );
+
+ /**
+ * Create a new adapter if the provided service is either a printer or provides
+ * the print method.
+ * @return An adapter or <code>null</code> if the method is missing.
+ */
+ public static StatusPrinterAdapter createAdapter(final StatusPrinterDescription description,
+ final Object service) {
+
+ Method printMethod = null;
+ if ( !(service instanceof StatusPrinter) ) {
+
+ // print(String, PrintWriter)
+ printMethod = ClassUtils.searchMethod(service.getClass(), "print",
+ new Class[] {String.class, PrintWriter.class});
+ if ( printMethod == null ) {
+ return null;
+ }
+ }
+ Method attachmentMethod = null;
+ if ( !(service instanceof ZipAttachmentProvider) ) {
+
+ // addAttachments()
+ attachmentMethod = ClassUtils.searchMethod(service.getClass(), "addAttachments",
+ new Class[] {String.class, ZipOutputStream.class});
+ }
+ return new StatusPrinterAdapter(
+ description,
+ service,
+ printMethod,
+ attachmentMethod);
+ }
+
+ /**
+ * Comparator for adapters based on the service ranking.
+ */
+ public static final Comparator<StatusPrinterAdapter> RANKING_COMPARATOR = new Comparator<StatusPrinterAdapter>() {
+
+ public int compare(final StatusPrinterAdapter o1, final StatusPrinterAdapter o2) {
+ return o1.description.compareTo(o2.description);
+ }
+ };
+
+ /** The status printer service. */
+ private final Object printer;
+
+ /** The printer description. */
+ private final StatusPrinterDescription description;
+
+ /** The method to use if printer does not implement the service interface. */
+ private final Method printMethod;
+
+ private final Method attachmentMethod;
+
+ /** Service registration for the web console. */
+ private ServiceRegistration registration;
+
+ /**
+ * Constructor.
+ */
+ public StatusPrinterAdapter( final StatusPrinterDescription description,
+ final Object printer,
+ final Method printMethod,
+ final Method attachmentMethod) {
+ this.description = description;
+ this.printer = printer;
+ this.printMethod = printMethod;
+ this.attachmentMethod = attachmentMethod;
+ }
+
+ public void registerConsole(final BundleContext context, final StatusPrinterManager manager) {
+ if ( this.registration == null &&
+ (supports(PrinterMode.HTML_BODY) || supports(PrinterMode.TEXT))) {
+ this.registration = WebConsolePlugin.register(context, manager, this.description);
+ }
+ }
+
+ public void unregisterConsole() {
+ if ( this.registration != null ) {
+ this.registration.unregister();
+ this.registration = null;
+ }
+ }
+
+ /**
+ * @see org.apache.felix.status.StatusPrinterHandler#getTitle()
+ */
+ public String getTitle() {
+ return this.description.getTitle();
+ }
+
+ /**
+ * @see org.apache.felix.status.StatusPrinterHandler#getName()
+ */
+ public String getName() {
+ return this.description.getName();
+ }
+
+ /**
+ * @see org.apache.felix.status.StatusPrinterHandler#getCategory()
+ */
+ public String getCategory() {
+ return this.description.getCategory();
+ }
+
+ /**
+ * @see org.apache.felix.status.StatusPrinterHandler#getModes()
+ */
+ public PrinterMode[] getModes() {
+ return this.description.getModes();
+ }
+
+ /**
+ * @see org.apache.felix.status.ZipAttachmentProvider#addAttachments(java.lang.String, java.util.zip.ZipOutputStream)
+ */
+ public void addAttachments(final String namePrefix, final ZipOutputStream zos)
+ throws IOException {
+ // check if printer implements ZipAttachmentProvider
+ if ( printer instanceof ZipAttachmentProvider ) {
+ ((ZipAttachmentProvider)printer).addAttachments(namePrefix, zos);
+ } else if ( this.attachmentMethod != null ) {
+ ClassUtils.invoke(this.printer, this.attachmentMethod, new Object[] {namePrefix, zos});
+ }
+ }
+
+ /**
+ * @see org.apache.felix.status.StatusPrinterHandler#supports(org.apache.felix.status.PrinterMode)
+ */
+ public boolean supports(final PrinterMode mode) {
+ for(int i=0; i<this.description.getModes().length; i++) {
+ if ( this.description.getModes()[i] == mode ) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @see org.apache.felix.status.StatusPrinter#print(org.apache.felix.status.PrinterMode, java.io.PrintWriter)
+ */
+ public void print(final PrinterMode mode,
+ final PrintWriter printWriter) {
+ if ( this.supports(mode) ) {
+ if ( this.printer instanceof StatusPrinter ) {
+ ((StatusPrinter)this.printer).print(mode, printWriter);
+ } else {
+ ClassUtils.invoke(this.printer, this.printMethod, new Object[] {mode.toString(), printWriter});
+ }
+ }
+ }
+
+ /**
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return printer.getClass() + "(" + super.toString() + ")";
+ }
+
+ /**
+ * @see java.lang.Comparable#compareTo(java.lang.Object)
+ */
+ public int compareTo(final StatusPrinterAdapter spa) {
+ return this.description.getSortKey().compareTo(spa.description.getSortKey());
+ }
+
+ public StatusPrinterDescription getDescription() {
+ return this.description;
+ }
+ }
diff --git a/status-printer/src/main/java/org/apache/felix/status/impl/StatusPrinterDescription.java b/status-printer/src/main/java/org/apache/felix/status/impl/StatusPrinterDescription.java
new file mode 100644
index 0000000..6224ef6
--- /dev/null
+++ b/status-printer/src/main/java/org/apache/felix/status/impl/StatusPrinterDescription.java
@@ -0,0 +1,134 @@
+/*
+ * 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.status.impl;
+
+import java.util.Arrays;
+
+import org.apache.felix.status.PrinterMode;
+import org.apache.felix.status.StatusPrinter;
+import org.osgi.framework.ServiceReference;
+
+/**
+ * Helper class for a configuration printer.
+ */
+public class StatusPrinterDescription implements Comparable<StatusPrinterDescription> {
+
+ private final ServiceReference reference;
+
+ private final PrinterMode[] modes;
+
+ private final String name;
+
+ private final String title;
+
+ private final String sortKey;
+
+ private final String category;
+
+ public StatusPrinterDescription(final ServiceReference ref) {
+ this.reference = ref;
+
+ // check modes
+ final Object modesCfg = ref.getProperty(StatusPrinter.CONFIG_PRINTER_MODES);
+ if ( modesCfg instanceof String ) {
+ this.modes = new PrinterMode[] { PrinterMode.valueOf((String)modesCfg)};
+ } else if ( modesCfg instanceof String[] ) {
+ final String[] modesCfgArray = (String[])modesCfg;
+ this.modes = new PrinterMode[modesCfgArray.length];
+ for(int i=0; i<modesCfgArray.length;i++) {
+ this.modes[i] = PrinterMode.valueOf(modesCfgArray[i]);
+ }
+ } else {
+ this.modes = null;
+ }
+
+ // check name
+ if ( ref.getProperty(StatusPrinter.CONFIG_NAME) != null ) {
+ this.name = ref.getProperty(StatusPrinter.CONFIG_NAME).toString();
+ } else {
+ this.name = null;
+ }
+
+ // check title
+ if ( ref.getProperty(StatusPrinter.CONFIG_TITLE) != null ) {
+ this.title = ref.getProperty(StatusPrinter.CONFIG_TITLE).toString();
+ if ( this.title.startsWith("%") ) {
+ this.sortKey = this.title.substring(1);
+ } else {
+ this.sortKey = this.title;
+ }
+ } else {
+ this.title = null;
+ this.sortKey = null;
+ }
+
+ // check category
+ if ( ref.getProperty(StatusPrinter.CONFIG_CATEGORY) != null ) {
+ this.category = ref.getProperty(StatusPrinter.CONFIG_CATEGORY).toString();
+ } else {
+ this.category = null;
+ }
+ }
+
+ public String getTitle() {
+ return this.title;
+ }
+
+ public String getSortKey() {
+ return this.sortKey;
+ }
+
+ public String getName() {
+ return this.name;
+ }
+
+ public String getCategory() {
+ return this.category;
+ }
+
+ public PrinterMode[] getModes() {
+ return this.modes;
+ }
+
+ public ServiceReference getServiceReference() {
+ return this.reference;
+ }
+
+ /**
+ * @see java.lang.Comparable#compareTo(java.lang.Object)
+ */
+ public int compareTo(final StatusPrinterDescription spa) {
+ return this.reference.compareTo(spa.reference);
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ return this.reference.equals(obj);
+ }
+
+ @Override
+ public int hashCode() {
+ return this.reference.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return "StatusPrinterDescription [title=" + title + ", name=" + name
+ + ", modes=" + Arrays.toString(modes) + ", sortKey=" + sortKey
+ + ", category=" + category + "]";
+ }
+}
diff --git a/status-printer/src/main/java/org/apache/felix/status/impl/StatusPrinterManagerImpl.java b/status-printer/src/main/java/org/apache/felix/status/impl/StatusPrinterManagerImpl.java
new file mode 100644
index 0000000..09e518e
--- /dev/null
+++ b/status-printer/src/main/java/org/apache/felix/status/impl/StatusPrinterManagerImpl.java
@@ -0,0 +1,263 @@
+/*
+ * 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.status.impl;
+
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentSkipListSet;
+
+import org.apache.felix.status.PrinterMode;
+import org.apache.felix.status.StatusPrinter;
+import org.apache.felix.status.StatusPrinterHandler;
+import org.apache.felix.status.StatusPrinterManager;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.util.tracker.ServiceTracker;
+import org.osgi.util.tracker.ServiceTrackerCustomizer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * The manager keeps track of all status printers and maintains them
+ * based on their name. If more than one printer with the same name
+ * is registered, the one with highest service ranking is used.
+ */
+public class StatusPrinterManagerImpl implements StatusPrinterManager,
+ ServiceTrackerCustomizer {
+
+ /** Logger. */
+ private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+ /** Bundle Context .*/
+ private final BundleContext bundleContext;
+
+ /** Service tracker for status printers. */
+ private final ServiceTracker cfgPrinterTracker;
+
+ /** All adapters mapped by their name. */
+ private final Map<String, List<StatusPrinterAdapter>> allAdapters = new HashMap<String, List<StatusPrinterAdapter>>();
+
+ /** Used adapters. */
+ private final Set<StatusPrinterAdapter> usedAdapters = new ConcurrentSkipListSet<StatusPrinterAdapter>();
+
+ /** Registration for the web console. */
+ private final ServiceRegistration pluginRegistration;
+
+ /**
+ * Create the status printer
+ * @param btx Bundle Context
+ * @throws InvalidSyntaxException Should only happen if we have an error in the code
+ */
+ public StatusPrinterManagerImpl(final BundleContext btx) throws InvalidSyntaxException {
+ this.bundleContext = btx;
+ this.cfgPrinterTracker = new ServiceTracker( this.bundleContext,
+ this.bundleContext.createFilter("(&(" + StatusPrinter.CONFIG_PRINTER_MODES + "=*)"
+ + "(" + StatusPrinter.CONFIG_NAME + "=*)"
+ + "(" + StatusPrinter.CONFIG_TITLE + "=*))"),
+ this );
+ this.cfgPrinterTracker.open();
+
+ this.pluginRegistration = DefaultWebConsolePlugin.register(btx, this);
+ }
+
+ /**
+ * Dispose this service
+ */
+ public void dispose() {
+ if ( this.pluginRegistration != null ) {
+ this.pluginRegistration.unregister();
+ }
+ this.cfgPrinterTracker.close();
+ synchronized ( this.allAdapters ) {
+ this.allAdapters.clear();
+ }
+ this.usedAdapters.clear();
+ }
+
+ /**
+ * @see org.osgi.util.tracker.ServiceTrackerCustomizer#addingService(org.osgi.framework.ServiceReference)
+ */
+ public Object addingService(final ServiceReference reference) {
+ final Object obj = this.bundleContext.getService(reference);
+ if ( obj != null ) {
+ this.addService(reference, obj);
+ }
+ return obj;
+ }
+
+ /**
+ * @see org.osgi.util.tracker.ServiceTrackerCustomizer#modifiedService(org.osgi.framework.ServiceReference, java.lang.Object)
+ */
+ public void modifiedService(final ServiceReference reference, final Object service) {
+ this.removeService(reference);
+ this.addService(reference, service);
+ }
+
+ private void addService(final ServiceReference reference, final Object obj) {
+ final StatusPrinterDescription desc = new StatusPrinterDescription(reference);
+
+ boolean valid = true;
+ if ( desc.getModes() == null ) {
+ logger.info("Ignoring status printer - printer modes configuration is missing: {}", reference);
+ valid = false;
+ }
+ if ( desc.getName() == null ) {
+ logger.info("Ignoring status printer - name configuration is missing: {}", reference);
+ valid = false;
+ }
+ if ( desc.getTitle() == null ) {
+ logger.info("Ignoring status printer - title configuration is missing: {}", reference);
+ valid = false;
+ }
+ if ( valid ) {
+ final StatusPrinterAdapter adapter = StatusPrinterAdapter.createAdapter(desc, obj);
+ if ( adapter == null ) {
+ logger.info("Ignoring status printer - printer method is missing: {}", reference);
+ } else {
+ this.addAdapter(adapter);
+ }
+ }
+ }
+
+ private void addAdapter(final StatusPrinterAdapter adapter) {
+ StatusPrinterAdapter removeAdapter = null;
+ StatusPrinterAdapter addAdapter = null;
+
+ final String key = adapter.getName();
+ synchronized ( this.allAdapters ) {
+ List<StatusPrinterAdapter> list = this.allAdapters.get(key);
+ final StatusPrinterAdapter first;
+ if ( list == null ) {
+ list = new LinkedList<StatusPrinterAdapter>();
+ this.allAdapters.put(key, list);
+ first = null;
+ } else {
+ first = list.get(0);
+ }
+ list.add(adapter);
+ Collections.sort(list, StatusPrinterAdapter.RANKING_COMPARATOR);
+ if ( first != null ) {
+ if ( first != list.get(0) ) {
+ // update
+ removeAdapter = first;
+ addAdapter = adapter;
+ }
+ } else {
+ // add
+ addAdapter = adapter;
+ }
+ }
+ if ( removeAdapter != null ) {
+ final Iterator<StatusPrinterAdapter> i = this.usedAdapters.iterator();
+ while ( i.hasNext() ) {
+ if ( i.next() == removeAdapter ) {
+ i.remove();
+ break;
+ }
+ }
+ removeAdapter.unregisterConsole();
+ }
+ if ( addAdapter != null ) {
+ this.usedAdapters.add(addAdapter);
+ addAdapter.registerConsole(this.bundleContext, this);
+ }
+ }
+
+ /**
+ * @see org.osgi.util.tracker.ServiceTrackerCustomizer#removedService(org.osgi.framework.ServiceReference, java.lang.Object)
+ */
+ public void removedService(final ServiceReference reference, final Object service) {
+ this.removeService(reference);
+ this.bundleContext.ungetService(reference);
+ }
+
+ private void removeService(final ServiceReference reference) {
+ synchronized ( this.allAdapters ) {
+ final Iterator<Map.Entry<String, List<StatusPrinterAdapter>>> i = this.allAdapters.entrySet().iterator();
+ while ( i.hasNext() ) {
+ final Map.Entry<String, List<StatusPrinterAdapter>> entry = i.next();
+ final Iterator<StatusPrinterAdapter> iter = entry.getValue().iterator();
+ boolean removed = false;
+ while ( iter.hasNext() ) {
+ final StatusPrinterAdapter adapter = iter.next();
+ if ( adapter.getDescription().getServiceReference().compareTo(reference) == 0 ) {
+ iter.remove();
+ removed = true;
+ break;
+ }
+ }
+ if ( removed ) {
+ if ( entry.getValue().size() == 0 ) {
+ i.remove();
+ }
+ break;
+ }
+ }
+ }
+ final Iterator<StatusPrinterAdapter> iter = this.usedAdapters.iterator();
+ while ( iter.hasNext() ) {
+ final StatusPrinterAdapter adapter = iter.next();
+ if ( adapter.getDescription().getServiceReference().compareTo(reference) == 0 ) {
+ iter.remove();
+ adapter.unregisterConsole();
+ break;
+ }
+ }
+ }
+
+ /**
+ * @see org.apache.felix.status.StatusPrinterManager#getAllHandlers()
+ */
+ public StatusPrinterHandler[] getAllHandlers() {
+ return this.usedAdapters.toArray(new StatusPrinterHandler[this.usedAdapters.size()]);
+ }
+
+ /**
+ * @see org.apache.felix.status.StatusPrinterManager#getHandlers(org.apache.felix.status.PrinterMode)
+ */
+ public StatusPrinterHandler[] getHandlers(final PrinterMode mode) {
+ final List<StatusPrinterHandler> result = new ArrayList<StatusPrinterHandler>();
+ for(final StatusPrinterAdapter printer : this.usedAdapters) {
+ if ( printer.supports(mode) ) {
+ result.add(printer);
+ }
+ }
+ return result.toArray(new StatusPrinterHandler[result.size()]);
+ }
+
+ /**
+ * @see org.apache.felix.status.StatusPrinterManager#getHandler(java.lang.String)
+ */
+ public StatusPrinterHandler getHandler(final String name) {
+ for(final StatusPrinterAdapter printer : this.usedAdapters) {
+ if ( name.equals(printer.getName()) ) {
+ return printer;
+ }
+ }
+ return null;
+ }
+}
diff --git a/status-printer/src/main/java/org/apache/felix/status/impl/WebConsolePlugin.java b/status-printer/src/main/java/org/apache/felix/status/impl/WebConsolePlugin.java
new file mode 100644
index 0000000..4af1d19
--- /dev/null
+++ b/status-printer/src/main/java/org/apache/felix/status/impl/WebConsolePlugin.java
@@ -0,0 +1,78 @@
+/*
+ * 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.status.impl;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import org.apache.felix.status.StatusPrinterHandler;
+import org.apache.felix.status.StatusPrinterManager;
+import org.apache.felix.status.impl.webconsole.ConsoleConstants;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceFactory;
+import org.osgi.framework.ServiceRegistration;
+
+/**
+ * The web console plugin for a status printer.
+ */
+public class WebConsolePlugin extends AbstractWebConsolePlugin {
+
+ private static final long serialVersionUID = 1L;
+
+ /** Printer name. */
+ private final String printerName;
+
+ /**
+ * Constructor
+ * @param statusPrinterManager The status printer manager.
+ * @param printerName The name of the printer this plugin is displaying.
+ */
+ WebConsolePlugin(final StatusPrinterManager statusPrinterManager,
+ final String printerName) {
+ super(statusPrinterManager);
+ this.printerName = printerName;
+ }
+
+ @Override
+ protected StatusPrinterHandler getStatusPrinterHandler() {
+ return this.statusPrinterManager.getHandler(this.printerName);
+ }
+
+ public static ServiceRegistration register(
+ final BundleContext context,
+ final StatusPrinterManager manager,
+ final StatusPrinterDescription desc) {
+ final Dictionary<String, Object> props = new Hashtable<String, Object>();
+ props.put(ConsoleConstants.PLUGIN_LABEL, "status-" + desc.getName());
+ props.put(ConsoleConstants.PLUGIN_TITLE, desc.getTitle());
+ props.put(ConsoleConstants.PLUGIN_CATEGORY, desc.getCategory() == null ? "Status" : desc.getCategory());
+ return context.registerService(ConsoleConstants.INTERFACE_SERVLET, new ServiceFactory() {
+
+ public void ungetService(final Bundle bundle, final ServiceRegistration registration,
+ final Object service) {
+ // nothing to do
+ }
+
+ public Object getService(final Bundle bundle, final ServiceRegistration registration) {
+ return new WebConsolePlugin(manager, desc.getName());
+ }
+
+ }, props);
+
+ }
+}
\ No newline at end of file
diff --git a/status-printer/src/main/java/org/apache/felix/status/impl/webconsole/ConfigurationPrinterAdapter.java b/status-printer/src/main/java/org/apache/felix/status/impl/webconsole/ConfigurationPrinterAdapter.java
new file mode 100644
index 0000000..54e3262
--- /dev/null
+++ b/status-printer/src/main/java/org/apache/felix/status/impl/webconsole/ConfigurationPrinterAdapter.java
@@ -0,0 +1,234 @@
+/*
+ * 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.status.impl.webconsole;
+
+import java.io.PrintWriter;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.felix.status.PrinterMode;
+import org.apache.felix.status.impl.ClassUtils;
+import org.osgi.framework.ServiceReference;
+
+/**
+ * Helper class for a configuration printer.
+ */
+public class ConfigurationPrinterAdapter {
+
+ private final Object printer;
+ public String title;
+ public String label;
+ private final String[] modes;
+ private final boolean escapeHtml;
+ private final Method printMethod;
+ private final Method attachmentMethod;
+
+ private static final List<String> CUSTOM_MODES = new ArrayList<String>();
+ static {
+ CUSTOM_MODES.add( ConsoleConstants.MODE_TXT);
+ CUSTOM_MODES.add( ConsoleConstants.MODE_WEB );
+ CUSTOM_MODES.add( ConsoleConstants.MODE_ZIP );
+ }
+
+ /**
+ * Check whether the class implements the configuration printer.
+ * This is done manually to avoid having the configuration printer class available.
+ */
+ private static boolean isConfigurationPrinter(final Class<?> clazz) {
+ for(final Class<?> i : clazz.getInterfaces() ) {
+ if ( i.getName().equals(ConsoleConstants.INTERFACE_CONFIGURATION_PRINTER) ) {
+ return true;
+ }
+ }
+ if ( clazz.getSuperclass() != null ) {
+ return isConfigurationPrinter(clazz.getSuperclass());
+ }
+ return false;
+ }
+
+ /**
+ * Try to create a new configuration printer adapter.
+ */
+ public static ConfigurationPrinterAdapter createAdapter(
+ final Object service,
+ final ServiceReference ref) {
+ String title;
+ Object modes = null;
+ if ( isConfigurationPrinter(service.getClass()) ) {
+ modes = ref.getProperty(ConsoleConstants.CONFIG_PRINTER_MODES);
+ if ( modes == null ) {
+ modes = ref.getProperty( ConsoleConstants.PROPERTY_MODES );
+ }
+ final Method titleMethod = ClassUtils.searchMethod(service.getClass(), "getTitle", null);
+ if ( titleMethod == null ) {
+ return null;
+ }
+ title = (String)ClassUtils.invoke(service, titleMethod, null);
+ } else {
+ modes = ref.getProperty( ConsoleConstants.CONFIG_PRINTER_MODES );
+ title = (String)ref.getProperty( ConsoleConstants.PLUGIN_TITLE );
+ }
+
+ Object cfgPrinter = null;
+ Method printMethod = null;
+
+ // first: printConfiguration(PrintWriter, String)
+ final Method method2Params = ClassUtils.searchMethod(service.getClass(), "printConfiguration",
+ new Class[] {PrintWriter.class, String.class});
+ if ( method2Params != null ) {
+ cfgPrinter = service;
+ printMethod = method2Params;
+ }
+
+ if ( cfgPrinter == null ) {
+ // second: printConfiguration(PrintWriter)
+ final Method method1Params = ClassUtils.searchMethod(service.getClass(), "printConfiguration",
+ new Class[] {PrintWriter.class});
+ if ( method1Params != null ) {
+ cfgPrinter = service;
+ printMethod = method1Params;
+ }
+ }
+
+ if ( cfgPrinter != null ) {
+ final Object label = ref.getProperty( ConsoleConstants.PLUGIN_LABEL );
+ // check escaping
+ boolean webUnescaped;
+ Object ehObj = ref.getProperty( ConsoleConstants.CONFIG_PRINTER_WEB_UNESCAPED );
+ if ( ehObj instanceof Boolean ) {
+ webUnescaped = ( ( Boolean ) ehObj ).booleanValue();
+ } else if ( ehObj instanceof String ) {
+ webUnescaped = Boolean.valueOf( ( String ) ehObj ).booleanValue();
+ } else {
+ webUnescaped = false;
+ }
+
+ final String[] modesArray;
+ // check modes
+ if ( modes == null || !( modes instanceof String || modes instanceof String[] ) ) {
+ modesArray = null;
+ } else {
+ if ( modes instanceof String ) {
+ if ( CUSTOM_MODES.contains(modes) ) {
+ modesArray = new String[] {modes.toString()};
+ } else {
+ modesArray = null;
+ }
+ } else {
+ final String[] values = (String[])modes;
+ boolean valid = values.length > 0;
+ for(int i=0; i<values.length; i++) {
+ if ( !CUSTOM_MODES.contains(values[i]) ) {
+ valid = false;
+ break;
+ }
+ }
+ if ( valid) {
+ modesArray = values;
+ } else {
+ modesArray = null;
+ }
+ }
+ }
+
+ return new ConfigurationPrinterAdapter(
+ cfgPrinter,
+ printMethod,
+ ClassUtils.searchMethod(cfgPrinter.getClass(), "getAttachments", new Class[] {String.class}),
+ title,
+ (label instanceof String ? (String)label : null),
+ modesArray,
+ !webUnescaped);
+ }
+ return null;
+ }
+
+ private ConfigurationPrinterAdapter( final Object printer,
+ final Method printMethod,
+ final Method attachmentMethod,
+ final String title,
+ final String label,
+ final String[] modesArray,
+ final boolean escapeHtml ) {
+ this.printer = printer;
+ this.title = title;
+ this.label = label;
+ this.escapeHtml = escapeHtml;
+ this.printMethod = printMethod;
+ this.attachmentMethod = attachmentMethod;
+ this.modes = modesArray;
+ }
+
+ /**
+ * Map the modes to status printer modes
+ */
+ public String[] getPrinterModes() {
+ final Set<String> list = new HashSet<String>();
+ if ( this.match(ConsoleConstants.MODE_TXT) || this.match(ConsoleConstants.MODE_ZIP) ) {
+ list.add(PrinterMode.ZIP_FILE.name());
+ }
+ if ( this.match(ConsoleConstants.MODE_WEB) ) {
+ if ( !escapeHtml ) {
+ list.add(PrinterMode.HTML_BODY.name());
+ } else {
+ list.add(PrinterMode.TEXT.name());
+ }
+ }
+ return list.toArray(new String[list.size()]);
+ }
+
+ private boolean match(final String mode) {
+ if ( this.modes == null) {
+ return true;
+ }
+ for(int i=0; i<this.modes.length; i++) {
+ if ( this.modes[i].equals(mode) ) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public final void printConfiguration( final PrintWriter pw, final String mode ) {
+ if ( printMethod.getParameterTypes().length > 1 ) {
+ ClassUtils.invoke(this.printer, this.printMethod, new Object[] {pw, mode});
+ } else {
+ ClassUtils.invoke(this.printer, this.printMethod, new Object[] {pw});
+ }
+ }
+
+ public URL[] getAttachments() {
+ // check if printer implements binary configuration printer
+ URL[] attachments = null;
+ if ( attachmentMethod != null ) {
+ attachments = (URL[])ClassUtils.invoke(printer, attachmentMethod, new Object[] {ConsoleConstants.MODE_ZIP});
+ }
+ return attachments;
+ }
+
+ /**
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return title + " (" + printer.getClass() + ")";
+ }
+}
diff --git a/status-printer/src/main/java/org/apache/felix/status/impl/webconsole/ConsoleConstants.java b/status-printer/src/main/java/org/apache/felix/status/impl/webconsole/ConsoleConstants.java
new file mode 100644
index 0000000..55eb6f6
--- /dev/null
+++ b/status-printer/src/main/java/org/apache/felix/status/impl/webconsole/ConsoleConstants.java
@@ -0,0 +1,45 @@
+/*
+ * 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.status.impl.webconsole;
+
+
+public class ConsoleConstants {
+
+ public static final String INTERFACE_SERVLET = "javax.servlet.Servlet"; //$NON-NLS-1$
+
+ public static final String INTERFACE_CONFIGURATION_PRINTER = "org.apache.felix.webconsole.ConfigurationPrinter"; //$NON-NLS-1$
+
+ public static final String PLUGIN_LABEL = "felix.webconsole.label"; //$NON-NLS-1$
+
+ public static final String PLUGIN_TITLE = "felix.webconsole.title"; //$NON-NLS-1$
+
+ public static final String PLUGIN_CATEGORY = "felix.webconsole.category"; //$NON-NLS-1$
+
+ public static final String CONFIG_PRINTER_MODES = "felix.webconsole.configprinter.modes"; //$NON-NLS-1$
+
+ public static final String CONFIG_PRINTER_WEB_UNESCAPED = "felix.webconsole.configprinter.web.unescaped"; //$NON-NLS-1$
+
+ public static final String MODE_ALWAYS = "always"; //$NON-NLS-1$
+
+ public static final String MODE_WEB = "web"; //$NON-NLS-1$
+
+ public static final String MODE_ZIP = "zip"; //$NON-NLS-1$
+
+ public static final String MODE_TXT = "txt"; //$NON-NLS-1$
+
+ public static final String PROPERTY_MODES = "modes"; //$NON-NLS-1$
+}
diff --git a/status-printer/src/main/java/org/apache/felix/status/impl/webconsole/ResourceBundleManager.java b/status-printer/src/main/java/org/apache/felix/status/impl/webconsole/ResourceBundleManager.java
new file mode 100644
index 0000000..b79323e
--- /dev/null
+++ b/status-printer/src/main/java/org/apache/felix/status/impl/webconsole/ResourceBundleManager.java
@@ -0,0 +1,179 @@
+/*
+ * 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.status.impl.webconsole;
+
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.PropertyResourceBundle;
+import java.util.ResourceBundle;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.BundleListener;
+import org.osgi.framework.Constants;
+
+
+/**
+ * The ResourceBundleManager manages resource bundle instance per OSGi Bundle.
+ * It contains a local cache, for bundles, but when a bundle is being unistalled,
+ * its resources stored in the cache are cleaned up.
+ */
+public class ResourceBundleManager implements BundleListener
+{
+
+ private final BundleContext bundleContext;
+
+ private final Map<Long, ResourceBundle> resourceBundleCaches;
+
+
+ /**
+ * Creates a new object and adds self as a bundle listener
+ *
+ * @param bundleContext the bundle context of the Web Console.
+ */
+ public ResourceBundleManager( final BundleContext bundleContext )
+ {
+ this.bundleContext = bundleContext;
+ this.resourceBundleCaches = new HashMap<Long, ResourceBundle>();
+
+ bundleContext.addBundleListener( this );
+ }
+
+
+ /**
+ * Removes the bundle lister.
+ */
+ public void dispose()
+ {
+ bundleContext.removeBundleListener( this );
+ }
+
+
+ /**
+ * This method is used to retrieve a /cached/ instance of the i18n resource associated
+ * with a given bundle.
+ *
+ * @param provider the bundle, provider of the resources
+ * @param locale the requested locale.
+ */
+ public ResourceBundle getResourceBundle( final Bundle provider ) {
+ ResourceBundle cache;
+ final Long key = new Long( provider.getBundleId() );
+ synchronized ( resourceBundleCaches ) {
+ cache = resourceBundleCaches.get( key );
+ if ( cache == null && !resourceBundleCaches.containsKey(key)) {
+ cache = this.loadResourceBundle(provider);
+ resourceBundleCaches.put( key, cache );
+ }
+ }
+
+ return cache;
+ }
+
+
+ // ---------- BundleListener
+
+ /**
+ * @see org.osgi.framework.BundleListener#bundleChanged(org.osgi.framework.BundleEvent)
+ */
+ public final void bundleChanged( BundleEvent event )
+ {
+ if ( event.getType() == BundleEvent.STOPPED )
+ {
+ final Long key = new Long( event.getBundle().getBundleId() );
+ synchronized ( resourceBundleCaches )
+ {
+ resourceBundleCaches.remove( key );
+ }
+ }
+ }
+
+ private static final Locale DEFAULT_LOCALE = Locale.ENGLISH;
+
+ private ResourceBundle loadResourceBundle(final Bundle bundle) {
+ final String path = "_" + DEFAULT_LOCALE.toString(); //$NON-NLS-1$
+ final URL source = ( URL ) getResourceBundleEntries(bundle).get( path );
+ if ( source != null ) {
+ try {
+ return new PropertyResourceBundle( source.openStream() );
+ } catch ( final IOException ignore ) {
+ // ignore
+ }
+ }
+ return null;
+ }
+
+ // TODO : Instead of getting all property files, we could just get the one for the default locale
+ private synchronized Map getResourceBundleEntries(final Bundle bundle)
+ {
+ String file = ( String ) bundle.getHeaders().get( Constants.BUNDLE_LOCALIZATION );
+ if ( file == null )
+ {
+ file = Constants.BUNDLE_LOCALIZATION_DEFAULT_BASENAME;
+ }
+
+ // remove leading slash
+ if ( file.startsWith( "/" ) ) //$NON-NLS-1$
+ {
+ file = file.substring( 1 );
+ }
+
+ // split path and base name
+ int slash = file.lastIndexOf( '/' );
+ String fileName = file.substring( slash + 1 );
+ String path = ( slash <= 0 ) ? "/" : file.substring( 0, slash ); //$NON-NLS-1$
+
+ HashMap resourceBundleEntries = new HashMap();
+
+ Enumeration locales = bundle.findEntries( path, fileName + "*.properties", false ); //$NON-NLS-1$
+ if ( locales != null )
+ {
+ while ( locales.hasMoreElements() )
+ {
+ URL entry = ( URL ) locales.nextElement();
+
+ // calculate the key
+ String entryPath = entry.getPath();
+ final int start = entryPath.lastIndexOf( '/' ) + 1 + fileName.length(); // path, slash and base name
+ final int end = entryPath.length() - 11; // .properties suffix
+ entryPath = entryPath.substring( start, end );
+
+ // the default language is "name.properties" thus the entry
+ // path is empty and must default to "_"+DEFAULT_LOCALE
+ if (entryPath.length() == 0) {
+ entryPath = "_" + DEFAULT_LOCALE; //$NON-NLS-1$
+ }
+
+ // only add this entry, if the "language" is not provided
+ // by the main bundle or an earlier bound fragment
+ if (!resourceBundleEntries.containsKey( entryPath )) {
+ resourceBundleEntries.put( entryPath, entry );
+ }
+ }
+ }
+
+ return resourceBundleEntries;
+ }
+}
diff --git a/status-printer/src/main/java/org/apache/felix/status/impl/webconsole/WebConsoleAdapter.java b/status-printer/src/main/java/org/apache/felix/status/impl/webconsole/WebConsoleAdapter.java
new file mode 100644
index 0000000..ddab251
--- /dev/null
+++ b/status-printer/src/main/java/org/apache/felix/status/impl/webconsole/WebConsoleAdapter.java
@@ -0,0 +1,205 @@
+/*
+ * 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.status.impl.webconsole;
+
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.net.URL;
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.ResourceBundle;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+import org.apache.felix.status.PrinterMode;
+import org.apache.felix.status.StatusPrinter;
+import org.apache.felix.status.ZipAttachmentProvider;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.util.tracker.ServiceTracker;
+import org.osgi.util.tracker.ServiceTrackerCustomizer;
+
+
+/**
+ */
+public class WebConsoleAdapter implements ServiceTrackerCustomizer {
+
+ private final BundleContext bundleContext;
+
+ private final ServiceTracker cfgPrinterTracker;
+
+ private final Map<ServiceReference, ServiceRegistration> registrations = new HashMap<ServiceReference, ServiceRegistration>();
+
+ private final ResourceBundleManager rbManager;
+
+ public WebConsoleAdapter(final BundleContext btx) throws InvalidSyntaxException {
+ this.bundleContext = btx;
+ this.rbManager = new ResourceBundleManager(btx);
+ this.cfgPrinterTracker = new ServiceTracker( this.bundleContext,
+ this.bundleContext.createFilter("(|(" + Constants.OBJECTCLASS + "=" + ConsoleConstants.INTERFACE_CONFIGURATION_PRINTER + ")" +
+ "(&(" + ConsoleConstants.PLUGIN_LABEL + "=*)(&("
+ + ConsoleConstants.PLUGIN_TITLE + "=*)("
+ + ConsoleConstants.CONFIG_PRINTER_MODES + "=*))))"),
+ this );
+ this.cfgPrinterTracker.open();
+ }
+
+ /**
+ * Dispose this service
+ */
+ public void dispose() {
+ this.cfgPrinterTracker.close();
+ synchronized ( this.registrations ) {
+ for(final ServiceRegistration reg : this.registrations.values()) {
+ reg.unregister();
+ }
+ this.registrations.clear();
+ }
+ this.rbManager.dispose();
+ }
+
+ public void add(final ServiceReference reference, final Object service) {
+ final ConfigurationPrinterAdapter cpa = ConfigurationPrinterAdapter.createAdapter(service, reference);
+ if ( cpa != null && cpa.title != null ) {
+ if ( cpa.title.startsWith("%") ) {
+ final String key = cpa.title.substring(1);
+ final ResourceBundle rb = this.rbManager.getResourceBundle(reference.getBundle());
+ if ( rb == null || !rb.containsKey(key) ) {
+ cpa.title = key;
+ } else {
+ cpa.title = rb.getString(key);
+ }
+ }
+ if ( cpa.label == null ) {
+ cpa.label = cpa.title;
+ }
+ final Dictionary<String, Object> props = new Hashtable<String, Object>();
+ props.put(StatusPrinter.CONFIG_NAME, cpa.label);
+ props.put(StatusPrinter.CONFIG_TITLE, cpa.title);
+ props.put(StatusPrinter.CONFIG_PRINTER_MODES, cpa.getPrinterModes());
+
+ if ( reference.getProperty(ConsoleConstants.PLUGIN_CATEGORY) != null ) {
+ props.put(StatusPrinter.CONFIG_CATEGORY, reference.getProperty(ConsoleConstants.PLUGIN_CATEGORY));
+ }
+ final ServiceRegistration reg = this.bundleContext.registerService(StatusPrinter.class.getName(), new ZipAttachmentProvider() {
+
+ /**
+ * @see org.apache.felix.status.StatusPrinter#print(org.apache.felix.status.PrinterMode, java.io.PrintWriter)
+ */
+ public void print(final PrinterMode mode, final PrintWriter printWriter) {
+ final String m;
+ if ( mode == PrinterMode.HTML_BODY ) {
+ m = ConsoleConstants.MODE_WEB;
+ } else if ( mode == PrinterMode.TEXT ) {
+ m = ConsoleConstants.MODE_TXT;
+ } else if ( mode == PrinterMode.ZIP_FILE ) {
+ m = ConsoleConstants.MODE_ZIP;
+ } else {
+ m = null;
+ }
+ if ( m != null ) {
+ cpa.printConfiguration(printWriter, m);
+ }
+ }
+
+ /**
+ * @see org.apache.felix.status.ZipAttachmentProvider#addAttachments(java.lang.String, java.util.zip.ZipOutputStream)
+ */
+ public void addAttachments(final String namePrefix, final ZipOutputStream zos)
+ throws IOException {
+ final URL[] attachments = cpa.getAttachments();
+ if ( attachments != null ) {
+ for(final URL current : attachments) {
+ final String path = current.getPath();
+ final String name;
+ if ( path == null || path.length() == 0 ) {
+ // sanity code, we should have a path, but if not let's
+ // just create some random name
+ name = "file" + Double.doubleToLongBits( Math.random() );
+ } else {
+ final int pos = path.lastIndexOf('/');
+ name = (pos == -1 ? path : path.substring(pos + 1));
+ }
+ final ZipEntry entry = new ZipEntry(namePrefix + name);
+ zos.putNextEntry(entry);
+ final InputStream is = current.openStream();
+ try {
+ byte[] buffer = new byte[4096];
+ int n = 0;
+ while (-1 != (n = is.read(buffer))) {
+ zos.write(buffer, 0, n);
+ }
+ } finally {
+ if ( is != null ) {
+ try { is.close(); } catch (final IOException ignore) {}
+ }
+ }
+ zos.closeEntry();
+ }
+ }
+ }
+
+ }, props);
+ synchronized ( this.registrations ) {
+ this.registrations.put(reference, reg);
+ }
+ }
+ }
+
+ private final void remove(final ServiceReference reference) {
+ final ServiceRegistration reg;
+ synchronized ( this.registrations ) {
+ reg = this.registrations.remove(reference);
+ }
+ if ( reg != null ) {
+ reg.unregister();
+ }
+ }
+
+ /**
+ * @see org.osgi.util.tracker.ServiceTrackerCustomizer#addingService(org.osgi.framework.ServiceReference)
+ */
+ public Object addingService(final ServiceReference reference) {
+ final Object service = this.bundleContext.getService(reference);
+ if ( service != null ) {
+ this.add(reference, service);
+ }
+ return service;
+ }
+ /**
+ * @see org.osgi.util.tracker.ServiceTrackerCustomizer#modifiedService(org.osgi.framework.ServiceReference, java.lang.Object)
+ */
+ public void modifiedService(final ServiceReference reference, final Object service) {
+ this.remove(reference);
+ this.add(reference, service);
+ }
+
+ /**
+ * @see org.osgi.util.tracker.ServiceTrackerCustomizer#removedService(org.osgi.framework.ServiceReference, java.lang.Object)
+ */
+ public void removedService(final ServiceReference reference, final Object service) {
+ this.remove(reference);
+ this.bundleContext.ungetService(reference);
+ }
+}
diff --git a/status-printer/src/main/java/org/apache/felix/status/package-info.java b/status-printer/src/main/java/org/apache/felix/status/package-info.java
new file mode 100644
index 0000000..8d647e8
--- /dev/null
+++ b/status-printer/src/main/java/org/apache/felix/status/package-info.java
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ */
+
+/**
+ * Interfaces for getting status information about the current instance
+ *
+ * @version 1.0.0
+ */
+@Version("1.0.0")
+@Export(optional = "provide:=true")
+package org.apache.felix.status;
+
+import aQute.bnd.annotation.Export;
+import aQute.bnd.annotation.Version;
+
+