org.apache.felix.webconsole.internal.core.BundlesServlet.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.felix.webconsole.internal.core.BundlesServlet.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.core;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.TreeMap;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.felix.bundlerepository.R4Attribute;
import org.apache.felix.bundlerepository.R4Export;
import org.apache.felix.bundlerepository.R4Import;
import org.apache.felix.bundlerepository.R4Package;
import org.apache.felix.webconsole.internal.BaseWebConsolePluginServlet;
import org.apache.felix.webconsole.internal.Util;
import org.apache.felix.webconsole.internal.servlet.OsgiManager;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONWriter;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.Version;
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.component.ComponentConstants;
import org.osgi.service.packageadmin.ExportedPackage;
import org.osgi.service.packageadmin.PackageAdmin;
import org.osgi.service.startlevel.StartLevel;

public class BundlesServlet extends BaseWebConsolePluginServlet {
    private final class ServletRequestInfo {
        public final String extension;
        public final Bundle bundle;
        public final boolean bundleRequested;

        protected ServletRequestInfo(final HttpServletRequest request) {
            String info = request.getPathInfo();
            // remove label and starting slash
            info = info.substring(getServletLabel().length() + 1);
            int extensionLength = 5;

            // get extension
            if (info.endsWith(".json")) {
                extension = "json";
                info = info.substring(0, info.length() - extensionLength);
            } else {
                extension = "html";
            }

            // we only accept direct requests to a bundle if they have a slash
            // after the label
            String bundleInfo = null;
            if (info.startsWith("/")) {
                bundleInfo = info.substring(1);
            }
            if (bundleInfo == null) {
                bundle = null;
                bundleRequested = false;
            } else {
                bundle = getBundle(bundleInfo);
                bundleRequested = true;
            }
            request.setAttribute(BundlesServlet.class.getName(), this);
        }

    }

    public static ServletRequestInfo getRequestInfo(final HttpServletRequest request) {
        return (ServletRequestInfo) request.getAttribute(BundlesServlet.class.getName());
    }

    private final Log logger = LogFactory.getLog(getClass());

    public static final String SERV_NAME = "bundles";

    public static final String SERV_LABEL = "Bundles";
    public static final String BUNDLE_ID = "bundleId";
    private static final int STRING_BACKWARD = 1;
    private static final int STRING_FORWARD = 1;

    private static final int NOT_FOUND_ERROR = 404;

    private static final int SLEEP_TIME = 800;

    // bootdelegation property entries. wildcards are converted to package
    // name prefixes. whether an entry is a wildcard or not is set as a flag
    // in the bootPkgWildcards array.
    // see #activate and #isBootDelegated
    private String[] bootPkgs;

    // a flag for each entry in bootPkgs indicating whether the respective
    // entry was declared as a wildcard or not
    // see #activate and #isBootDelegated
    private boolean[] bootPkgWildcards;

    public void activateBundle(BundleContext bundleContext) {
        super.activateBundle(bundleContext);

        // bootdelegation property parsing from Apache Felix R4SearchPolicyCore
        String bootDelegation = bundleContext.getProperty(Constants.FRAMEWORK_BOOTDELEGATION);
        bootDelegation = (bootDelegation == null) ? "java.*" : bootDelegation + ",java.*";
        StringTokenizer strToken = new StringTokenizer(bootDelegation, " ,");
        bootPkgs = new String[strToken.countTokens()];
        bootPkgWildcards = new boolean[bootPkgs.length];
        for (int i = 0; i < bootPkgs.length; i++) {
            bootDelegation = strToken.nextToken();
            if (bootDelegation.endsWith("*")) {
                bootPkgWildcards[i] = true;
                bootDelegation = bootDelegation.substring(0, bootDelegation.length() - STRING_BACKWARD);
            }
            bootPkgs[i] = bootDelegation;
        }
    }

    private void appendBundleMsg(final StringBuffer strBuf, String msg, int count) {
        strBuf.append(count);

        if (count != 1)
            strBuf.append(" bundles ");
        else
            strBuf.append(" bundle ");
        strBuf.append(msg);
    }

    private void appendProperty(JSONArray array, ServiceReference servRef, String name, String label) {
        StringBuffer finalString = new StringBuffer();
        Object value = servRef.getProperty(name);
        if (value instanceof Object[]) {
            Object[] values = (Object[]) value;
            finalString.append(label).append(": ");
            for (int j = 0; j < values.length; j++) {
                if (j > 0)
                    finalString.append(", ");
                finalString.append(values[j]);
            }
            array.put(finalString.toString());
        } else if (value != null) {
            finalString.append(label).append(": ").append(value);
            array.put(finalString.toString());
        }
    }

    private void bundleInfo(JSONWriter jsonWriter, Bundle bundle, boolean details) throws JSONException {
        jsonWriter.object();
        jsonWriter.key("id");
        jsonWriter.value(bundle.getBundleId());
        jsonWriter.key("name");
        jsonWriter.value(Util.getBundleName(bundle));
        jsonWriter.key("state");
        jsonWriter.value(getStateString(bundle));
        jsonWriter.key("version");
        jsonWriter.value(Util.getHeaderValue(bundle, Constants.BUNDLE_VERSION));
        jsonWriter.key("symbolicName");
        jsonWriter.value(Util.getHeaderValue(bundle, Constants.BUNDLE_SYMBOLICNAME));
        jsonWriter.key("actions");
        jsonWriter.array();

        if (bundle.getBundleId() != 0) {
            if (hasStarted(bundle)) {
                printAction(jsonWriter, hasStarted(bundle), "start", "Start", "start");
            } else {
                printAction(jsonWriter, isStopped(bundle), "stop", "Stop", "stop");
            }
            printAction(jsonWriter, true, "refresh", "Refresh Package Imports", "refresh");
            printAction(jsonWriter, isUninstalled(bundle), "uninstall", "Uninstall", "delete");
        }
        jsonWriter.endArray();

        if (details) {
            printBundleDetails(jsonWriter, bundle);
        }

        jsonWriter.endObject();
    }

    private void collectImport(JSONArray array, String name, Version version, boolean optional,
            ExportedPackage export) {
        StringBuffer importBuff = new StringBuffer();
        boolean bootDel = isBootDelegated(name);

        String marker = null;
        importBuff.append(name);
        importBuff.append(",version=").append(version);
        importBuff.append(" from ");

        if (export != null) {
            importBuff.append(getBundleDescriptor(export.getExportingBundle()));

            if (bootDel) {
                importBuff.append(" -- It will be overwritten by Boot Delegation");
                marker = "INFO";
            }
        } else {
            importBuff.append(" -- It is unable to be resolved");
            marker = "ERROR";

            if (optional) {
                importBuff.append(" that is not required");
            }

            if (bootDel) {
                importBuff.append(" and overwritten by Boot Delegation");
            }
        }

        if (marker != null) {
            importBuff.insert(0, ": ");
            importBuff.insert(0, marker);
        }

        array.put(importBuff);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        final ServletRequestInfo reqInfo = new ServletRequestInfo(request);
        if (reqInfo.bundle == null && reqInfo.bundleRequested) {
            response.sendError(NOT_FOUND_ERROR);
            return;
        }
        if (reqInfo.extension.equals("json")) {
            this.renderResponseJSON(response, reqInfo.bundle);

            // nothing more to do
            return;
        }

        super.doGet(request, response);
    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        final ServletRequestInfo reqInfo = new ServletRequestInfo(request);
        if (reqInfo.bundleRequested && reqInfo.bundle == null) {
            response.sendError(NOT_FOUND_ERROR);
            return;
        }

        boolean successful = false;

        final String reqAction = request.getParameter("action");

        Bundle bundle = getBundle(request.getPathInfo());

        if (bundle != null) {
            if (reqAction == null) {
                successful = true;
            } else if ("start".equals(reqAction)) {
                // start bundle
                successful = true;
                try {
                    bundle.start();
                } catch (BundleException bundleEx) {
                    logger.error("Bundle can not start. " + bundleEx);
                }
            } else if ("stop".equals(reqAction)) {
                // stop bundle
                successful = true;
                try {
                    bundle.stop();
                } catch (BundleException bundleEx) {
                    logger.error("It stopped! " + bundleEx);
                }
            } else if ("refresh".equals(reqAction)) {
                // refresh bundle wiring
                refreshPkg(bundle);
                successful = true;
            } else if ("uninstall".equals(reqAction)) {
                // uninstall bundle
                successful = true;
                try {
                    bundle.uninstall();
                    bundle = null; // bundle has gone !
                } catch (BundleException bundleEx) {
                    logger.error("Unable to uninstall bundle! " + bundleEx);
                }
            }
        }

        if ("refreshPackages".equals(reqAction)) {
            getPackageAdmin().refreshPackages(null);
            successful = true;
        }

        if (successful) {
            // let's wait a little bit to give the framework time
            // to process our request
            try {
                Thread.sleep(SLEEP_TIME);
            } catch (InterruptedException ex) {
                // we ignore this
            }
            this.renderResponseJSON(response, null);
        } else {
            super.doPost(request, response);
        }
    }

    private Bundle getBundle(String pathInfo) {
        // only use last part of the pathInfo
        pathInfo = pathInfo.substring(pathInfo.lastIndexOf('/') + STRING_FORWARD);

        // assume bundle Id
        try {
            final long bundleId = Long.parseLong(pathInfo);
            if (bundleId >= 0) {
                return getBundleContext().getBundle(bundleId);
            }
        } catch (NumberFormatException numberEx) {
            // check if this follows the pattern {symbolic-name}[:{version}]
            final int position = pathInfo.indexOf(':');
            final String symbolicName;
            final String bundleVersion;
            if (position == -1) {
                symbolicName = pathInfo;
                bundleVersion = null;
            } else {
                symbolicName = pathInfo.substring(0, position);
                bundleVersion = pathInfo.substring(position + STRING_FORWARD);
            }

            // search
            final Bundle[] bundles = getBundleContext().getBundles();
            for (int i = 0; i < bundles.length; i++) {
                final Bundle bundle = bundles[i];
                // check symbolic name first
                if (symbolicName.equals(bundle.getSymbolicName())) {
                    if (bundleVersion == null
                            || bundleVersion.equals(bundle.getHeaders().get(Constants.BUNDLE_VERSION))) {
                        return bundle;
                    }
                }
            }
        }

        return null;
    }

    private String getBundleDescriptor(Bundle bundle) {
        StringBuffer infoBuf = new StringBuffer();

        if (bundle.getLocation() != null) {
            // otherwise try the location
            infoBuf.append(bundle.getLocation());
            infoBuf.append(" (").append(bundle.getBundleId());
            infoBuf.append(")");
        } else if (bundle.getSymbolicName() != null) {
            // list the bundle name if not null
            infoBuf.append(bundle.getSymbolicName());
            infoBuf.append(" (").append(bundle.getBundleId());
            infoBuf.append(")");
        } else {
            infoBuf.append(bundle.getBundleId());
        }
        return infoBuf.toString();
    }

    protected Bundle[] getBundles() {
        return getBundleContext().getBundles();
    }

    private String getBundleStatusLine(final Bundle[] bundles) {
        int active = 0, installed = 0, resolved = 0, fragments = 0;
        for (int i = 0; i < bundles.length; i++) {
            switch (bundles[i].getState()) {
            case Bundle.ACTIVE:
                active++;
                break;
            case Bundle.INSTALLED:
                installed++;
                break;
            case Bundle.RESOLVED:
                if (bundles[i].getHeaders().get(Constants.FRAGMENT_HOST) != null) {
                    fragments++;
                } else {
                    resolved++;
                }
                break;
            }
        }
        final StringBuffer strBuffer = new StringBuffer();
        strBuffer.append("Bundle information: ");
        appendBundleMsg(strBuffer, "in total", bundles.length);
        if (active == bundles.length || active + fragments == bundles.length) {
            strBuffer.append(" - all ");
            appendBundleMsg(strBuffer, "active.", bundles.length);
        } else {
            if (active != 0) {
                strBuffer.append(", ");
                appendBundleMsg(strBuffer, "active", active);
            }
            if (fragments != 0) {
                strBuffer.append(", ");
                appendBundleMsg(strBuffer, "active fragments", fragments);
            }
            if (resolved != 0) {
                strBuffer.append(", ");
                appendBundleMsg(strBuffer, "resolved", resolved);
            }
            if (installed != 0) {
                strBuffer.append(", ");
                appendBundleMsg(strBuffer, "installed", installed);
            }
            strBuffer.append('.');
        }
        return strBuffer.toString();
    }

    private void getDelegationInfo(JSONArray array, String name, Version version) {
        StringBuffer bufVal = new StringBuffer();
        boolean bootDel = isBootDelegated(name);
        if (bootDel) {
            bufVal.append("!! ");
        }

        bufVal.append(name);
        bufVal.append(",version=");
        bufVal.append(version);

        if (bootDel) {
            bufVal.append(" -- Overwritten by Boot Delegation");
        }

        array.put(bufVal.toString());
    }

    public String getServletCapital() {
        return SERV_LABEL;
    }

    public String getServletLabel() {
        return SERV_NAME;
    }

    private Integer getStartLevel(Bundle bundle) {
        StartLevel bundleLv = getStartLevel();
        return (bundleLv != null) ? new Integer(bundleLv.getBundleStartLevel(bundle)) : null;
    }

    private String getStateString(final Bundle bundle) {
        switch (bundle.getState()) {
        case Bundle.STARTING:
            return "Starting";
        case Bundle.ACTIVE:
            return "Active";
        case Bundle.STOPPING:
            return "Stopping";
        case Bundle.UNINSTALLED:
            return "Uninstalled";
        case Bundle.INSTALLED:
            return "Installed";
        case Bundle.RESOLVED:
            if (isFragmentBundle(bundle)) {
                return "Fragment";
            }
            return "Resolved";
        default:
            return "Unknown: " + bundle.getState();
        }
    }

    private boolean hasStarted(Bundle bundle) {
        if (isFragmentBundle(bundle)) {
            return false;
        }
        return bundle.getState() == Bundle.INSTALLED || bundle.getState() == Bundle.RESOLVED;
    }

    // returns true if the package is listed in the bootdelegation property
    private boolean isBootDelegated(String pkgName) {

        // bootdelegation analysis from Apache Felix R4SearchPolicyCore

        // Only consider delegation if we have a package name, since
        // we don't want to promote the default package. The spec does
        // not take a stand on this issue.
        if (pkgName.length() > 0) {

            // Delegate any packages listed in the boot delegation
            // property to the parent class loader.
            for (int i = 0; i < bootPkgs.length; i++) {

                // A wildcarded boot delegation package will be in the form of
                // "foo.", so if the package is wildcarded do a startsWith() or
                // a regionMatches() to ignore the trailing "." to determine if
                // the request should be delegated to the parent class loader.
                // If the package is not wildcarded, then simply do an equals()
                // test to see if the request should be delegated to the parent
                // class loader.
                if ((bootPkgWildcards[i] && (pkgName.startsWith(bootPkgs[i])
                        || bootPkgs[i].regionMatches(0, pkgName, 0, pkgName.length())))
                        || (!bootPkgWildcards[i] && bootPkgs[i].equals(pkgName))) {
                    return true;
                }
            }
        }

        return false;
    }

    private boolean isFragmentBundle(Bundle bundle) {
        return bundle.getHeaders().get(Constants.FRAGMENT_HOST) != null;
    }

    private boolean isStopped(Bundle bundle) {
        if (isFragmentBundle(bundle)) {
            return false;
        }
        return bundle.getState() == Bundle.ACTIVE;
    }

    private boolean isUninstalled(Bundle bundle) {
        return bundle.getState() == Bundle.INSTALLED || bundle.getState() == Bundle.RESOLVED
                || bundle.getState() == Bundle.ACTIVE;

    }

    private void jsonKeyVal(JSONWriter jsonWriter, String key, Object value) throws JSONException {
        if (key != null && value != null) {
            jsonWriter.object();
            jsonWriter.key("key");
            jsonWriter.value(key);
            jsonWriter.key("value");
            jsonWriter.value(value);
            jsonWriter.endObject();
        }
    }

    private void listHeaders(JSONWriter jsonWriter, Bundle bundle) throws JSONException {
        JSONArray val = new JSONArray();

        Dictionary headers = bundle.getHeaders();
        Enumeration headerKey = headers.keys();
        while (headerKey.hasMoreElements()) {
            Object header = headerKey.nextElement();
            String value = String.valueOf(headers.get(header));
            // Package headers may be long, support line breaking by
            // ensuring blanks after comma and semicolon.
            value = value.replaceAll("([;,])", "$1 ");
            val.put(header + ": " + value);
        }

        jsonKeyVal(jsonWriter, "Manifest Headers", val);
    }

    @SuppressWarnings("deprecation")
    private void listImportExport(JSONWriter jsonWriter, Bundle bundle) throws JSONException {
        PackageAdmin packageAdmin = getPackageAdmin();
        if (packageAdmin == null) {
            return;
        }

        Map usingBundles = new TreeMap();

        ExportedPackage[] exports = packageAdmin.getExportedPackages(bundle);
        if (exports != null && exports.length > 0) {
            // do alphabetical sort
            Arrays.sort(exports, new Comparator() {
                public int compare(ExportedPackage p1, ExportedPackage p2) {
                    return p1.getName().compareTo(p2.getName());
                }

                public int compare(Object o1, Object o2) {
                    return compare((ExportedPackage) o1, (ExportedPackage) o2);
                }
            });

            JSONArray val = new JSONArray();
            for (int j = 0; j < exports.length; j++) {
                ExportedPackage export = exports[j];
                getDelegationInfo(val, export.getName(), export.getVersion());
                Bundle[] ubList = export.getImportingBundles();
                if (ubList != null) {
                    for (int i = 0; i < ubList.length; i++) {
                        Bundle ub = ubList[i];
                        usingBundles.put(ub.getSymbolicName(), ub);
                    }
                }
            }
            jsonKeyVal(jsonWriter, "Exported Packages", val);
        } else {
            jsonKeyVal(jsonWriter, "Exported Packages", "None");
        }

        exports = packageAdmin.getExportedPackages((Bundle) null);
        if (exports != null && exports.length > 0) {
            // collect import packages first
            final List imports = new ArrayList();
            for (int i = 0; i < exports.length; i++) {
                final ExportedPackage ep = exports[i];
                final Bundle[] importers = ep.getImportingBundles();
                for (int j = 0; importers != null && j < importers.length; j++) {
                    if (importers[j].getBundleId() == bundle.getBundleId()) {
                        imports.add(ep);

                        break;
                    }
                }
            }
            // now sort
            JSONArray val = new JSONArray();
            if (imports.size() > 0) {
                final ExportedPackage[] packages = (ExportedPackage[]) imports
                        .toArray(new ExportedPackage[imports.size()]);
                Arrays.sort(packages, new Comparator() {
                    public int compare(ExportedPackage p1, ExportedPackage p2) {
                        return p1.getName().compareTo(p2.getName());
                    }

                    public int compare(Object o1, Object o2) {
                        return compare((ExportedPackage) o1, (ExportedPackage) o2);
                    }
                });
                // and finally print out
                for (int i = 0; i < packages.length; i++) {
                    ExportedPackage exportPkg = packages[i];
                    collectImport(val, exportPkg.getName(), exportPkg.getVersion(), false, exportPkg);
                }
            } else {
                // add description if there are no imports
                val.put("None");
            }

            jsonKeyVal(jsonWriter, "Imported Packages", val);
        }

        if (!usingBundles.isEmpty()) {
            JSONArray val = new JSONArray();
            for (Iterator bundleIter = usingBundles.values().iterator(); bundleIter.hasNext();) {
                Bundle usingBundle = (Bundle) bundleIter.next();
                val.put(getBundleDescriptor(usingBundle));
            }
            jsonKeyVal(jsonWriter, "Importing Bundles", val);
        }
    }

    private void listImportExportsUnresolved(JSONWriter jsonWriter, Bundle bundle) throws JSONException {
        Dictionary dictHeader = bundle.getHeaders();

        String exportPkg = (String) dictHeader.get(Constants.EXPORT_PACKAGE);
        if (exportPkg != null) {
            R4Package[] pkgs = R4Package.parseImportOrExportHeader(exportPkg);
            if (pkgs != null && pkgs.length > 0) {
                // do alphabetical sort
                Arrays.sort(pkgs, new Comparator() {
                    public int compare(Object o1, Object o2) {
                        return compare((R4Package) o1, (R4Package) o2);
                    }

                    public int compare(R4Package p1, R4Package p2) {
                        return p1.getName().compareTo(p2.getName());
                    }
                });

                JSONArray val = new JSONArray();
                for (int i = 0; i < pkgs.length; i++) {
                    R4Export export = new R4Export(pkgs[i]);
                    getDelegationInfo(val, export.getName(), export.getVersion());
                }
                jsonKeyVal(jsonWriter, "Exported Packages", val);
            } else {
                jsonKeyVal(jsonWriter, "Exported Packages", "None");
            }
        }

        exportPkg = (String) dictHeader.get(Constants.IMPORT_PACKAGE);
        if (exportPkg != null) {
            R4Package[] pkgs = R4Package.parseImportOrExportHeader(exportPkg);
            if (pkgs != null && pkgs.length > 0) {
                Map imports = new TreeMap();
                for (int i = 0; i < pkgs.length; i++) {
                    R4Package pkg = pkgs[i];
                    imports.put(pkg.getName(), new R4Import(pkg));
                }

                // collect import packages first
                final Map candidates = new HashMap();
                PackageAdmin packageAdmin = getPackageAdmin();
                if (packageAdmin != null) {
                    ExportedPackage[] exports = packageAdmin.getExportedPackages((Bundle) null);
                    if (exports != null && exports.length > 0) {

                        for (int i = 0; i < exports.length; i++) {
                            final ExportedPackage ep = exports[i];

                            R4Import imp = (R4Import) imports.get(ep.getName());
                            if (imp != null && imp.isSatisfied(toR4Export(ep))) {
                                candidates.put(ep.getName(), ep);
                            }
                        }
                    }
                }

                // now sort
                JSONArray importArray = new JSONArray();
                if (imports.size() > 0) {
                    for (Iterator importsIter = imports.values().iterator(); importsIter.hasNext();) {
                        R4Import r4Import = (R4Import) importsIter.next();
                        ExportedPackage ep = (ExportedPackage) candidates.get(r4Import.getName());

                        // if there is no matching export, check whether this
                        // bundle has the package, ignore the entry in this case
                        if (ep == null) {
                            String path = r4Import.getName().replace('.', '/');
                            if (bundle.getResource(path) != null) {
                                continue;
                            }
                        }

                        collectImport(importArray, r4Import.getName(), r4Import.getVersion(), r4Import.isOptional(),
                                ep);
                    }
                } else {
                    // add description if there are no imports
                    importArray.put("None");
                }

                jsonKeyVal(jsonWriter, "Imported Packages", importArray);
            }
        }
    }

    private void listServices(JSONWriter jsonWriter, Bundle bundle) throws JSONException {
        ServiceReference[] refs = bundle.getRegisteredServices();
        if (refs == null || refs.length == 0) {
            return;
        }

        for (int i = 0; i < refs.length; i++) {
            String key = "Service ID " + refs[i].getProperty(Constants.SERVICE_ID);

            JSONArray propArray = new JSONArray();

            appendProperty(propArray, refs[i], Constants.OBJECTCLASS, "Types");
            appendProperty(propArray, refs[i], Constants.SERVICE_PID, "PID");
            appendProperty(propArray, refs[i], ConfigurationAdmin.SERVICE_FACTORYPID, "Factory PID");
            appendProperty(propArray, refs[i], ComponentConstants.COMPONENT_NAME, "Component Name");
            appendProperty(propArray, refs[i], ComponentConstants.COMPONENT_ID, "Component ID");
            appendProperty(propArray, refs[i], ComponentConstants.COMPONENT_FACTORY, "Component Factory");
            appendProperty(propArray, refs[i], Constants.SERVICE_DESCRIPTION, "Description");
            appendProperty(propArray, refs[i], Constants.SERVICE_VENDOR, "Vendor");

            jsonKeyVal(jsonWriter, key, propArray);
        }
    }

    private void printAction(JSONWriter jsonWriter, boolean enabled, String op, String opLabel, String image)
            throws JSONException {
        jsonWriter.object();
        jsonWriter.key("enabled").value(enabled);
        jsonWriter.key("name").value(opLabel);
        jsonWriter.key("link").value(op);
        jsonWriter.key("image").value(image);
        jsonWriter.endObject();
    }

    private void printBundleDetails(JSONWriter jsonWriter, Bundle bundle) throws JSONException {
        Dictionary headers = bundle.getHeaders();

        jsonWriter.key("props");
        jsonWriter.array();
        jsonKeyVal(jsonWriter, "Symbolic Name", bundle.getSymbolicName());
        jsonKeyVal(jsonWriter, "Version", headers.get(Constants.BUNDLE_VERSION));
        jsonKeyVal(jsonWriter, "Location", bundle.getLocation());
        jsonKeyVal(jsonWriter, "Last Modification", new Date(bundle.getLastModified()));

        String docUrl = (String) headers.get(Constants.BUNDLE_DOCURL);
        if (docUrl != null) {
            jsonKeyVal(jsonWriter, "Bundle Documentation", docUrl);
        }

        jsonKeyVal(jsonWriter, "Vendor", headers.get(Constants.BUNDLE_VENDOR));
        jsonKeyVal(jsonWriter, "Copyright", headers.get(Constants.BUNDLE_COPYRIGHT));
        jsonKeyVal(jsonWriter, "Description", headers.get(Constants.BUNDLE_DESCRIPTION));

        jsonKeyVal(jsonWriter, "Start Level", getStartLevel(bundle));

        jsonKeyVal(jsonWriter, "Bundle Classpath", headers.get(Constants.BUNDLE_CLASSPATH));

        if (bundle.getState() == Bundle.INSTALLED) {
            listImportExportsUnresolved(jsonWriter, bundle);
        } else {
            listImportExport(jsonWriter, bundle);
        }

        listServices(jsonWriter, bundle);

        listHeaders(jsonWriter, bundle);

        jsonWriter.endArray();
    }

    private void refreshPkg(final Bundle bundle) {
        getPackageAdmin().refreshPackages(new Bundle[] { bundle });
    }

    private void renderResponseJSON(final HttpServletResponse response, final Bundle bundle) throws IOException {
        response.setContentType("application/json");
        response.setCharacterEncoding("UTF-8");

        final PrintWriter printWriter = response.getWriter();
        writeJSON(printWriter, bundle);
    }

    protected void renderServletRequest(HttpServletRequest request, HttpServletResponse response)
            throws IOException {
        // get request info from request attribute
        final ServletRequestInfo reqInfo = getRequestInfo(request);
        final PrintWriter printWriter = response.getWriter();

        final String attrAppRoot = (String) request.getAttribute(OsgiManager.OSGI_APP_ROOT);
        Util.printScriptBody(printWriter, attrAppRoot, "bundles.js");

        Util.startScript(printWriter);
        printWriter.println("var imgRoot=\"" + attrAppRoot + "/res/imgs\" ;");
        printWriter.println("var startLevel = " + getStartLevel().getInitialBundleStartLevel() + ";");
        printWriter.println("var drawDetails = " + reqInfo.bundleRequested + ";");
        Util.endScript(printWriter);

        Util.printScriptBody(printWriter, attrAppRoot, "bundles.js");

        printWriter.println("<div id='plugin_content'/>");
        Util.startScript(printWriter);
        printWriter.print("renderBundles(");
        writeJSON(printWriter, reqInfo.bundle);
        printWriter.println(");");
        Util.endScript(printWriter);
    }

    private R4Export toR4Export(ExportedPackage export) {
        R4Attribute version = new R4Attribute(Constants.VERSION_ATTRIBUTE, export.getVersion().toString(), false);
        return new R4Export(export.getName(), null, new R4Attribute[] { version });
    }

    private void writeJSON(final PrintWriter printWriter, final Bundle bundle) throws IOException {
        final Bundle[] allBundles = this.getBundles();
        final String statusLine = this.getBundleStatusLine(allBundles);
        final Bundle[] bundles = (bundle != null) ? new Bundle[] { bundle } : allBundles;
        Util.sortBundleList(bundles);

        final JSONWriter jsonWriter = new JSONWriter(printWriter);

        try {
            jsonWriter.object();

            jsonWriter.key("status");
            jsonWriter.value(statusLine);
            jsonWriter.key("data");
            jsonWriter.array();

            for (int i = 0; i < bundles.length; i++) {
                bundleInfo(jsonWriter, bundles[i], bundle != null);
            }

            jsonWriter.endArray();
            jsonWriter.endObject();

        } catch (JSONException jsonEx) {
            throw new IOException(jsonEx.toString());
        }

    }
}