org.apache.felix.webconsole.internal.misc.ConfigurationRender.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.felix.webconsole.internal.misc.ConfigurationRender.java

Source

/*
 * 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.text.*;
import java.util.*;
import java.util.zip.Deflater;
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.apache.felix.webconsole.internal.i18n.ResourceBundleManager;
import org.osgi.framework.Bundle;
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 = "%configStatus.pluginTitle";
    private static final String[] CSS_REFS = { "/res/ui/configurationrender.css" };

    // use English as the locale for all non-display titles
    private static final Locale DEFAULT = Locale.ENGLISH;

    /**
     * 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);

    /**
     * The resource bundle manager to allow for status printer title
     * localization
     */
    private final ResourceBundleManager resourceBundleManager;

    private ServiceTracker cfgPrinterTracker;

    private int cfgPrinterTrackerCount;

    private ArrayList configurationPrinters;

    /** Default constructor */
    public ConfigurationRender(final ResourceBundleManager resourceBundleManager) {
        super(LABEL, TITLE, CSS_REFS);
        this.resourceBundleManager = resourceBundleManager;
    }

    /**
     * @see org.apache.felix.webconsole.SimpleWebConsolePlugin#deactivate()
     */
    public void deactivate() {
        // make sure the service tracker is closed and removed on deactivate
        ServiceTracker oldTracker = cfgPrinterTracker;
        if (oldTracker != null) {
            oldTracker.close();
        }
        cfgPrinterTracker = null;
        configurationPrinters = null;

        super.deactivate();
    }

    /**
     * @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(Deflater.BEST_SPEED);
            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")) {
            WebConsoleUtil.setNoCache(response);
            response.setContentType("text/html; charset=utf-8");

            String name = request.getPathInfo();
            name = name.substring(name.lastIndexOf('/') + 1);
            name = name.substring(0, name.length() - 4);
            name = WebConsoleUtil.urlDecode(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>");

            Collection printers = getConfigurationPrinters();
            for (Iterator i = printers.iterator(); i.hasNext();) {
                final PrinterDesc desc = (PrinterDesc) i.next();
                if (desc.label.equals(name)) {
                    printConfigurationPrinter(pw, desc, 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' src='${appRoot}/res/ui/ui.tabs.paging.js'></script>");
        pw.println("<script type='text/javascript' src='${appRoot}/res/ui/configurationrender.js'></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) + "/";
        Collection printers = getConfigurationPrinters();
        for (Iterator i = printers.iterator(); i.hasNext();) {
            final PrinterDesc desc = (PrinterDesc) i.next();
            final String label = desc.label;
            final String title = desc.title;
            pw.print("<li><a href='" + pluginRoot + label + ".nfo'>" + title + "</a></li>");
        }
        pw.println("</ul> <!-- end tabs on top -->");
        pw.println();

        pw.println("</div> <!-- end tabs container -->");

        pw.println(
                "<div id=\"waitDlg\" title=\"${configStatus.wait}\" class=\"ui-helper-hidden\"><img src=\"${appRoot}/res/imgs/loading.gif\" alt=\"${configStatus.wait}\" />${configStatus.wait.msg}</div>");

        pw.flush();
    }

    private void printConfigurationStatus(ConfigurationWriter pw, final String mode) {
        for (Iterator cpi = getConfigurationPrinters().iterator(); cpi.hasNext();) {
            final PrinterDesc desc = (PrinterDesc) cpi.next();
            if (desc.match(mode)) {
                printConfigurationPrinter(pw, desc, mode);
            }
        }
    }

    private final ArrayList 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]);
                    addConfigurationPrinter(cp, cfgPrinter, refs[i].getBundle(),
                            refs[i].getProperty(WebConsoleConstants.PLUGIN_LABEL),
                            refs[i].getProperty(ConfigurationPrinter.PROPERTY_MODES));
                }
            }
            configurationPrinters = new ArrayList(cp.values());
            cfgPrinterTrackerCount = cfgPrinterTracker.getTrackingCount();
        }

        return configurationPrinters;
    }

    private final void addConfigurationPrinter(final SortedMap printers, final ConfigurationPrinter cfgPrinter,
            final Bundle provider, final Object labelProperty, final Object mode) {
        if (cfgPrinter != null) {
            final String title = getTitle(cfgPrinter.getTitle(), provider);
            String sortKey = title;
            if (printers.containsKey(sortKey)) {
                int idx = -1;
                String idxTitle;
                do {
                    idx++;
                    idxTitle = sortKey + idx;
                } while (printers.containsKey(idxTitle));
                sortKey = idxTitle;
            }
            String label = (labelProperty instanceof String) ? (String) labelProperty : sortKey;
            printers.put(sortKey, new PrinterDesc(cfgPrinter, title, label, mode));
        }
    }

    // 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 final void printConfigurationPrinter(final ConfigurationWriter pw, final PrinterDesc desc,
            final String mode) {
        pw.title(desc.title);
        final ConfigurationPrinter cp = desc.printer;
        try {
            if (cp instanceof ModeAwareConfigurationPrinter) {
                ((ModeAwareConfigurationPrinter) cp).printConfiguration(pw, mode);
            } else {
                cp.printConfiguration(pw);
            }
        } catch (Throwable t) {
            pw.println();
            pw.println("Configuration Printer failed: " + t.toString());
            pw.println();
            log("Configuration Printer " + desc.title + " (" + cp.getClass() + ") failed", t);
        }
        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(WebConsoleUtil.toString(value));

        pw.println();
    }

    private final String getTitle(final String title, final Bundle provider) {
        if (!title.startsWith("%")) {
            return title;
        }

        ResourceBundle res = resourceBundleManager.getResourceBundle(provider, DEFAULT);
        return res.getString(title.substring(1));
    }

    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) {
                oldch = '_';
                this.write('\n'); // write <br/>
            } else {
                super.println();
            }
            oldch = '\n';
        }

        private int oldch = '_';

        // write the character unmodified unless filtering is enabled and
        // the character is a "<" in which case &lt; is written
        public void write(final int character) {
            if (doFilter) {
                switch (character) {
                case '<':
                    super.write('&');
                    super.write('l');
                    super.write('t');
                    super.write(';');
                    break;
                case '>':
                    super.write('&');
                    super.write('g');
                    super.write('t');
                    super.write(';');
                    break;
                case '&':
                    super.write('&');
                    super.write('a');
                    super.write('m');
                    super.write('p');
                    super.write(';');
                    break;
                case ' ':
                    super.write('&');
                    super.write('n');
                    super.write('b');
                    super.write('s');
                    super.write('p');
                    super.write(';');
                    break;
                case '\r':
                case '\n':
                    if (oldch != '\r' && oldch != '\n') {// don't add twice <br>
                        super.write('<');
                        super.write('b');
                        super.write('r');
                        super.write('/');
                        super.write('>');
                        super.write('\n');
                    }
                    break;
                default:
                    super.write(character);
                }
            } else {
                super.write(character);
            }
            oldch = 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) {
                for (int i = off; i < len; i++) {
                    this.write(chars[i]);
                }
            } 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) {
            write(string.toCharArray(), off, len);
        }

    }

    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.title, attachments);
                    }
                }
            }
        }

    }

    private static final class PrinterDesc {
        public final ConfigurationPrinter printer;
        public final String title;
        public final String label;
        private final String[] modes;

        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 String title, final String label,
                final Object modes) {
            this.printer = printer;
            this.title = title;
            this.label = label;
            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();
            }

            // increase the filename counter
            counter++;
        }

        public void end() {
            flush();

            try {
                zip.closeEntry();
            } catch (IOException ioe) {
                // should handle
            }
        }
    }
}