| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.felix.webconsole.internal.misc; |
| |
| |
| import java.io.*; |
| import java.net.URL; |
| import java.net.URLDecoder; |
| import java.text.*; |
| import java.util.*; |
| import java.util.zip.ZipEntry; |
| import java.util.zip.ZipOutputStream; |
| |
| 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.*; |
| import org.apache.felix.webconsole.internal.OsgiManagerPlugin; |
| import org.osgi.framework.ServiceReference; |
| import org.osgi.util.tracker.ServiceTracker; |
| |
| |
| /** |
| * ConfigurationRender plugin renders the configuration status - a textual |
| * representation of the current framework status. The content itself is |
| * internally generated by the {@link ConfigurationPrinter} plugins. |
| */ |
| public class ConfigurationRender extends SimpleWebConsolePlugin implements OsgiManagerPlugin |
| { |
| |
| private static final String LABEL = "config"; |
| private static final String TITLE = "Configuration Status"; |
| private static final String[] CSS_REFS = null; |
| |
| private static final String TAB_PROPS = "System properties"; |
| private static final String TAB_THREADS = "Threads"; |
| |
| /** |
| * Formatter pattern to generate a relative path for the generation |
| * of the plain text or zip file representation of the status. The file |
| * name consists of a base name and the current time of status generation. |
| */ |
| private static final SimpleDateFormat FILE_NAME_FORMAT = new SimpleDateFormat( "'" + LABEL |
| + "/configuration-status-'yyyyMMdd'-'HHmmZ" ); |
| |
| /** |
| * Formatter pattern to render the current time of status generation. |
| */ |
| private static final DateFormat DISPLAY_DATE_FORMAT = DateFormat.getDateTimeInstance( DateFormat.LONG, |
| DateFormat.LONG, Locale.US ); |
| |
| private ServiceTracker cfgPrinterTracker; |
| |
| private int cfgPrinterTrackerCount; |
| |
| private SortedMap configurationPrinters = new TreeMap(); |
| |
| /** Default constructor */ |
| public ConfigurationRender() |
| { |
| super(LABEL, TITLE, CSS_REFS); |
| } |
| |
| |
| /** |
| * @see org.apache.felix.webconsole.AbstractWebConsolePlugin#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) |
| */ |
| protected final void doGet( HttpServletRequest request, HttpServletResponse response ) throws ServletException, |
| IOException |
| { |
| if ( request.getPathInfo().endsWith( ".txt" ) ) |
| { |
| response.setContentType( "text/plain; charset=utf-8" ); |
| ConfigurationWriter pw = new PlainTextConfigurationWriter( response.getWriter() ); |
| printConfigurationStatus( pw, ConfigurationPrinter.MODE_TXT ); |
| pw.flush(); |
| } |
| else if ( request.getPathInfo().endsWith( ".zip" ) ) |
| { |
| String type = getServletContext().getMimeType( request.getPathInfo() ); |
| if ( type == null ) |
| { |
| type = "application/x-zip"; |
| } |
| response.setContentType( type ); |
| |
| ZipOutputStream zip = new ZipOutputStream( response.getOutputStream() ); |
| zip.setLevel( 9 ); |
| zip.setMethod( ZipOutputStream.DEFLATED ); |
| |
| final ConfigurationWriter pw = new ZipConfigurationWriter( zip ); |
| printConfigurationStatus( pw, ConfigurationPrinter.MODE_ZIP ); |
| pw.flush(); |
| |
| addAttachments( pw, ConfigurationPrinter.MODE_ZIP ); |
| zip.finish(); |
| } |
| else if ( request.getPathInfo().endsWith( ".nfo" ) ) |
| { |
| response.setContentType( "text/html; charset=utf-8" ); |
| // disable cache |
| response.addHeader("Cache-Control", "no-cache, no-store, must-revalidate, max-age=0"); |
| response.addHeader("Expires", "Mon, 2 Sun 2001 05:00:00 GMT"); |
| response.addHeader("Pragma", "no-cache"); |
| |
| String name = request.getPathInfo(); |
| name = name.substring( name.lastIndexOf('/') + 1); |
| name = name.substring(0, name.length() - 4); |
| name = URLDecoder.decode( name ); |
| |
| ConfigurationWriter 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 ( TAB_PROPS.equals( name ) ) |
| { |
| printSystemProperties( pw ); |
| pw.println( "</div></body></html>" ); |
| return; |
| } |
| else if ( TAB_THREADS.equals( name)) |
| { |
| printThreads( pw ); |
| pw.println( "</div></body></html>" ); |
| return; |
| } |
| else |
| { |
| Collection printers = getConfigurationPrinters(); |
| for (Iterator i = printers.iterator(); i.hasNext();) |
| { |
| final PrinterDesc desc = (PrinterDesc) i.next(); |
| if (desc.printer.getTitle().equals( name ) ) |
| { |
| printConfigurationPrinter( pw, desc.printer, ConfigurationPrinter.MODE_WEB ); |
| pw.println( "</div></body></html>" ); |
| return; |
| } |
| } |
| } |
| |
| response.sendError( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Invalid configuration printer: " + name); |
| } |
| else |
| { |
| super.doGet( request, response ); |
| } |
| } |
| |
| |
| /** |
| * @see org.apache.felix.webconsole.AbstractWebConsolePlugin#renderContent(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) |
| */ |
| protected final void renderContent( HttpServletRequest request, HttpServletResponse response ) throws IOException |
| { |
| |
| //ConfigurationWriter pw = new HtmlConfigurationWriter( response.getWriter() ); |
| PrintWriter pw = response.getWriter(); |
| |
| pw.println( "<script type='text/javascript'>" ); |
| pw.println( "// <![CDATA[" ); |
| pw.println("$(document).ready(function() {$('#tabs').tabs()} );"); |
| pw.println( "// ]]>" ); |
| pw.println( "</script>" ); |
| |
| pw.println("<br/><p class=\"statline\">"); |
| |
| final Date currentTime = new Date(); |
| synchronized ( DISPLAY_DATE_FORMAT ) |
| { |
| pw.print("Date: "); |
| pw.println(DISPLAY_DATE_FORMAT.format(currentTime)); |
| } |
| |
| synchronized ( FILE_NAME_FORMAT ) |
| { |
| String fileName = FILE_NAME_FORMAT.format( currentTime ); |
| pw.print("<br/>Download as <a href='"); |
| pw.print(fileName); |
| pw.print(".txt'>[Single File]</a> or as <a href='"); |
| pw.print(fileName); |
| pw.println(".zip'>[ZIP]</a>"); |
| } |
| |
| pw.println("</p>"); // status line |
| |
| // display some information while the data is loading |
| // load the data (hidden to begin with) |
| pw.println("<div id='tabs'> <!-- tabs container -->"); |
| pw.println("<ul> <!-- tabs on top -->"); |
| |
| // print headers only |
| final String pluginRoot = request.getAttribute( WebConsoleConstants.ATTR_PLUGIN_ROOT ) + "/"; |
| pw.println("<li><a href='" + pluginRoot + TAB_PROPS + ".nfo'>" + TAB_PROPS + "</a></li>"); |
| pw.println("<li><a href='" + pluginRoot + TAB_THREADS + ".nfo'>" + TAB_THREADS + "</a></li>"); |
| |
| // print header for printers |
| Collection printers = getConfigurationPrinters(); |
| for (Iterator i = printers.iterator(); i.hasNext();) |
| { |
| final PrinterDesc desc = (PrinterDesc) i.next(); |
| final String title = desc.printer.getTitle(); |
| pw.print("<li><a href='" + pluginRoot + title + ".nfo'>" + title + "</a></li>" ); |
| } |
| pw.println("</ul> <!-- end tabs on top -->"); |
| pw.println(); |
| |
| pw.println("</div> <!-- end tabs container -->"); |
| |
| pw.flush(); |
| } |
| |
| |
| private void printConfigurationStatus( ConfigurationWriter pw, final String mode ) |
| { |
| printSystemProperties( pw ); |
| printThreads( pw ); |
| |
| for ( Iterator cpi = getConfigurationPrinters().iterator(); cpi.hasNext(); ) |
| { |
| final PrinterDesc desc = (PrinterDesc) cpi.next(); |
| if ( desc.match(mode) ) |
| { |
| printConfigurationPrinter( pw, desc.printer, mode ); |
| } |
| } |
| } |
| |
| |
| private final Collection getConfigurationPrinters() |
| { |
| if ( cfgPrinterTracker == null ) |
| { |
| cfgPrinterTracker = new ServiceTracker( getBundleContext(), ConfigurationPrinter.SERVICE, null ); |
| cfgPrinterTracker.open(); |
| cfgPrinterTrackerCount = -1; |
| } |
| |
| if ( cfgPrinterTrackerCount != cfgPrinterTracker.getTrackingCount() ) |
| { |
| SortedMap cp = new TreeMap(); |
| ServiceReference[] refs = cfgPrinterTracker.getServiceReferences(); |
| if ( refs != null ) |
| { |
| for ( int i = 0; i < refs.length; i++ ) |
| { |
| ConfigurationPrinter cfgPrinter = ( ConfigurationPrinter ) cfgPrinterTracker.getService(refs[i]); |
| if ( cfgPrinter != null ) |
| { |
| cp.put( cfgPrinter.getTitle(), new PrinterDesc(cfgPrinter, refs[i].getProperty(ConfigurationPrinter.PROPERTY_MODES)) ); |
| } |
| } |
| } |
| configurationPrinters = cp; |
| cfgPrinterTrackerCount = cfgPrinterTracker.getTrackingCount(); |
| } |
| |
| return configurationPrinters.values(); |
| } |
| |
| |
| private static final void printSystemProperties( ConfigurationWriter pw ) |
| { |
| pw.title( "System properties" ); |
| |
| Properties props = System.getProperties(); |
| SortedSet keys = new TreeSet( props.keySet() ); |
| for ( Iterator ki = keys.iterator(); ki.hasNext(); ) |
| { |
| Object key = ki.next(); |
| infoLine( pw, null, ( String ) key, props.get( key ) ); |
| } |
| |
| pw.end(); |
| } |
| |
| |
| // This is Sling stuff, we comment it out for now |
| // private void printRawFrameworkProperties(PrintWriter pw) { |
| // pw.println("*** Raw Framework properties:"); |
| // |
| // File file = new File(getBundleContext().getProperty("sling.home"), |
| // "sling.properties"); |
| // if (file.exists()) { |
| // Properties props = new Properties(); |
| // InputStream ins = null; |
| // try { |
| // ins = new FileInputStream(file); |
| // props.load(ins); |
| // } catch (IOException ioe) { |
| // // handle or ignore |
| // } finally { |
| // IOUtils.closeQuietly(ins); |
| // } |
| // |
| // SortedSet keys = new TreeSet(props.keySet()); |
| // for (Iterator ki = keys.iterator(); ki.hasNext();) { |
| // Object key = ki.next(); |
| // infoLine(pw, null, (String) key, props.get(key)); |
| // } |
| // |
| // } else { |
| // pw.println(" No Framework properties in " + file); |
| // } |
| // |
| // pw.println(); |
| // } |
| |
| |
| private static final void printConfigurationPrinter( final ConfigurationWriter pw, |
| final ConfigurationPrinter cp, |
| final String mode ) |
| { |
| pw.title( cp.getTitle() ); |
| if ( cp instanceof ModeAwareConfigurationPrinter ) |
| { |
| ((ModeAwareConfigurationPrinter)cp).printConfiguration( pw , mode); |
| } |
| else |
| { |
| cp.printConfiguration( pw ); |
| } |
| pw.end(); |
| } |
| |
| |
| /** |
| * Renders an info line - element in the framework configuration. The info line will |
| * look like: |
| * <pre> |
| * label = value |
| * </pre> |
| * |
| * Optionally it can be indented by a specific string. |
| * |
| * @param pw the writer to print to |
| * @param indent indentation string |
| * @param label the label data |
| * @param value the data itself. |
| */ |
| public static final void infoLine( PrintWriter pw, String indent, String label, Object value ) |
| { |
| if ( indent != null ) |
| { |
| pw.print( indent ); |
| } |
| |
| if ( label != null ) |
| { |
| pw.print( label ); |
| pw.print( " = " ); |
| } |
| |
| pw.print( asString( value ) ); |
| |
| pw.println(); |
| } |
| |
| |
| private static final String asString( final Object value ) |
| { |
| if ( value == null ) |
| { |
| return "n/a"; |
| } |
| else if ( value.getClass().isArray() ) |
| { |
| StringBuffer dest = new StringBuffer(); |
| Object[] values = ( Object[] ) value; |
| for ( int j = 0; j < values.length; j++ ) |
| { |
| if ( j > 0 ) |
| dest.append( ", " ); |
| dest.append( values[j] ); |
| } |
| return dest.toString(); |
| } |
| else |
| { |
| return value.toString(); |
| } |
| } |
| |
| |
| private static final void printThreads( ConfigurationWriter pw ) |
| { |
| // first get the root thread group |
| ThreadGroup rootGroup = Thread.currentThread().getThreadGroup(); |
| while ( rootGroup.getParent() != null ) |
| { |
| rootGroup = rootGroup.getParent(); |
| } |
| |
| pw.title( "Threads" ); |
| |
| printThreadGroup( pw, rootGroup ); |
| |
| int numGroups = rootGroup.activeGroupCount(); |
| ThreadGroup[] groups = new ThreadGroup[2 * numGroups]; |
| rootGroup.enumerate( groups ); |
| for ( int i = 0; i < groups.length; i++ ) |
| { |
| printThreadGroup( pw, groups[i] ); |
| } |
| |
| pw.end(); |
| } |
| |
| |
| private static final void printThreadGroup( PrintWriter pw, ThreadGroup group ) |
| { |
| if ( group != null ) |
| { |
| StringBuffer info = new StringBuffer(); |
| info.append("ThreadGroup ").append(group.getName()); |
| info.append( " [" ); |
| info.append( "maxprio=" ).append( group.getMaxPriority() ); |
| |
| info.append( ", parent=" ); |
| if ( group.getParent() != null ) |
| { |
| info.append( group.getParent().getName() ); |
| } |
| else |
| { |
| info.append( '-' ); |
| } |
| |
| info.append( ", isDaemon=" ).append( group.isDaemon() ); |
| info.append( ", isDestroyed=" ).append( group.isDestroyed() ); |
| info.append( ']' ); |
| |
| infoLine( pw, null, null, info.toString() ); |
| |
| int numThreads = group.activeCount(); |
| Thread[] threads = new Thread[numThreads * 2]; |
| group.enumerate( threads, false ); |
| for ( int i = 0; i < threads.length; i++ ) |
| { |
| printThread( pw, threads[i] ); |
| } |
| |
| pw.println(); |
| } |
| } |
| |
| |
| private static final void printThread( PrintWriter pw, Thread thread ) |
| { |
| if ( thread != null ) |
| { |
| StringBuffer info = new StringBuffer(); |
| info.append("Thread ").append( thread.getName() ); |
| info.append( " [" ); |
| info.append( "priority=" ).append( thread.getPriority() ); |
| info.append( ", alive=" ).append( thread.isAlive() ); |
| info.append( ", daemon=" ).append( thread.isDaemon() ); |
| info.append( ", interrupted=" ).append( thread.isInterrupted() ); |
| info.append( ", loader=" ).append( thread.getContextClassLoader() ); |
| info.append( ']' ); |
| |
| infoLine( pw, " ", null, info.toString() ); |
| } |
| } |
| |
| private abstract static class ConfigurationWriter extends PrintWriter |
| { |
| |
| ConfigurationWriter( Writer delegatee ) |
| { |
| super( delegatee ); |
| } |
| |
| |
| abstract void title( String title ); |
| |
| |
| abstract void end(); |
| |
| public void handleAttachments(final String title, final URL[] urls) |
| throws IOException |
| { |
| throw new UnsupportedOperationException("handleAttachments not supported by this configuration writer: " + this); |
| } |
| |
| } |
| |
| private static class HtmlConfigurationWriter extends ConfigurationWriter |
| { |
| |
| // whether or not to filter "<" signs in the output |
| private boolean doFilter; |
| |
| |
| HtmlConfigurationWriter( Writer delegatee ) |
| { |
| super( delegatee ); |
| } |
| |
| |
| public void title( String title ) |
| { |
| doFilter = true; |
| } |
| |
| |
| public void end() |
| { |
| doFilter = false; |
| } |
| |
| |
| // 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. |
| public void println() |
| { |
| if ( doFilter ) |
| { |
| super.write( "<br/>", 0, 5 ); |
| } |
| else |
| { |
| super.println(); |
| } |
| } |
| |
| |
| // write the character unmodified unless filtering is enabled and |
| // the character is a "<" in which case < is written |
| public void write( final int character ) |
| { |
| if ( doFilter && character == '<' ) |
| { |
| super.write( "<" ); |
| } |
| else |
| { |
| super.write( character ); |
| } |
| } |
| |
| |
| // write the characters unmodified unless filtering is enabled in |
| // which case the writeFiltered(String) method is called for filtering |
| public void write( final char[] chars, final int off, final int len ) |
| { |
| if ( doFilter ) |
| { |
| writeFiltered( new String( chars, off, len ) ); |
| } |
| else |
| { |
| super.write( chars, off, len ); |
| } |
| } |
| |
| |
| // write the string unmodified unless filtering is enabled in |
| // which case the writeFiltered(String) method is called for filtering |
| public void write( final String string, final int off, final int len ) |
| { |
| if ( doFilter ) |
| { |
| writeFiltered( string.substring( off, len ) ); |
| } |
| else |
| { |
| super.write( string, off, len ); |
| } |
| } |
| |
| |
| // helper method filter the string for "<" before writing |
| private void writeFiltered( String string ) |
| { |
| string = WebConsoleUtil.escapeHtml(string); // filtering |
| super.write( string, 0, string.length() ); |
| } |
| } |
| |
| private void addAttachments( final ConfigurationWriter cf, final String mode ) |
| throws IOException |
| { |
| for ( Iterator cpi = getConfigurationPrinters().iterator(); cpi.hasNext(); ) |
| { |
| // check if printer supports zip mode |
| final PrinterDesc desc = (PrinterDesc) cpi.next(); |
| if ( desc.match(mode) ) |
| { |
| // check if printer implements binary configuration printer |
| if ( desc.printer instanceof AttachmentProvider ) |
| { |
| final URL[] attachments = ((AttachmentProvider)desc.printer).getAttachments(mode); |
| if ( attachments != null ) |
| { |
| cf.handleAttachments(desc.printer.getTitle(), attachments); |
| } |
| } |
| } |
| } |
| |
| } |
| |
| private static final class PrinterDesc |
| { |
| private final String[] modes; |
| public final ConfigurationPrinter printer; |
| |
| private static final List CUSTOM_MODES = new ArrayList(); |
| static |
| { |
| CUSTOM_MODES.add(ConfigurationPrinter.MODE_TXT); |
| CUSTOM_MODES.add(ConfigurationPrinter.MODE_WEB); |
| CUSTOM_MODES.add(ConfigurationPrinter.MODE_ZIP); |
| } |
| |
| public PrinterDesc(final ConfigurationPrinter printer, final Object modes) |
| { |
| this.printer = printer; |
| if ( modes == null || !(modes instanceof String || modes instanceof String[]) ) |
| { |
| this.modes = null; |
| } |
| else |
| { |
| if ( modes instanceof String ) |
| { |
| if ( CUSTOM_MODES.contains(modes) ) |
| { |
| this.modes = new String[] {modes.toString()}; |
| } |
| else |
| { |
| this.modes = 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) |
| { |
| this.modes = values; |
| } |
| else |
| { |
| this.modes = null; |
| } |
| } |
| } |
| } |
| |
| public 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; |
| } |
| } |
| |
| private static class PlainTextConfigurationWriter extends ConfigurationWriter |
| { |
| |
| PlainTextConfigurationWriter( Writer delegatee ) |
| { |
| super( delegatee ); |
| } |
| |
| |
| public void title( String title ) |
| { |
| print( "*** " ); |
| print( title ); |
| println( ":" ); |
| } |
| |
| |
| public void end() |
| { |
| println(); |
| } |
| } |
| |
| private static class ZipConfigurationWriter extends ConfigurationWriter |
| { |
| private final ZipOutputStream zip; |
| |
| private int counter; |
| |
| |
| ZipConfigurationWriter( ZipOutputStream zip ) |
| { |
| super( new OutputStreamWriter( zip ) ); |
| this.zip = zip; |
| } |
| |
| |
| public void title( String title ) |
| { |
| String name = MessageFormat.format( "{0,number,000}-{1}.txt", new Object[] |
| { new Integer( counter ), title } ); |
| |
| counter++; |
| |
| ZipEntry entry = new ZipEntry( name ); |
| try |
| { |
| zip.putNextEntry( entry ); |
| } |
| catch ( IOException ioe ) |
| { |
| // should handle |
| } |
| } |
| |
| private OutputStream startFile( String title, String name) |
| { |
| final String path = MessageFormat.format( "{0,number,000}-{1}/{2}", new Object[] |
| { new Integer( counter ), title, name } ); |
| ZipEntry entry = new ZipEntry( path ); |
| try |
| { |
| zip.putNextEntry( entry ); |
| } |
| catch ( IOException ioe ) |
| { |
| // should handle |
| } |
| return zip; |
| } |
| |
| public void handleAttachments( final String title, final URL[] attachments) |
| throws IOException |
| { |
| for(int i = 0; i < attachments.length; i++) |
| { |
| final URL current = attachments[i]; |
| 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 OutputStream os = this.startFile(title, name); |
| final InputStream is = current.openStream(); |
| try |
| { |
| IOUtils.copy(is, os); |
| } |
| finally |
| { |
| IOUtils.closeQuietly(is); |
| } |
| this.end(); |
| } |
| } |
| |
| |
| public void end() |
| { |
| flush(); |
| |
| try |
| { |
| zip.closeEntry(); |
| } |
| catch ( IOException ioe ) |
| { |
| // should handle |
| } |
| } |
| } |
| } |