SLING-461 Move Sling Console to Apache Felix/FELIX-562 Move OSGi Console to Apache Felix

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@657024 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/webconsole/DISCLAIMER b/webconsole/DISCLAIMER
new file mode 100644
index 0000000..90850c2
--- /dev/null
+++ b/webconsole/DISCLAIMER
@@ -0,0 +1,7 @@
+Apache Sling is an effort undergoing incubation at The Apache Software Foundation (ASF),
+sponsored by the Apache Jackrabbit PMC. Incubation is required of all newly accepted
+projects until a further review indicates that the infrastructure, communications,
+and decision making process have stabilized in a manner consistent with other
+successful ASF projects. While incubation status is not necessarily a reflection of
+the completeness or stability of the code, it does indicate that the project has yet
+to be fully endorsed by the ASF.
\ No newline at end of file
diff --git a/webconsole/LICENSE b/webconsole/LICENSE
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/webconsole/LICENSE
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed 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.
diff --git a/webconsole/LICENSE.json b/webconsole/LICENSE.json
new file mode 100644
index 0000000..87d1411
--- /dev/null
+++ b/webconsole/LICENSE.json
@@ -0,0 +1,21 @@
+Copyright (c) 2002 JSON.org
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+The Software shall be used for Good, not Evil.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/webconsole/NOTICE b/webconsole/NOTICE
new file mode 100644
index 0000000..3b33958
--- /dev/null
+++ b/webconsole/NOTICE
@@ -0,0 +1,12 @@
+Apache Sling OSGi Web Console
+Copyright 2007-2008 The Apache Software Foundation
+
+Based on source code originally developed by
+Day Software (http://www.day.com/).
+
+This product includes software developed at
+The Apache Software Foundation (http://www.apache.org/).
+Licensed under the Apache License 2.0.
+
+This product includes software from http://www.json.org.
+Copyright (c) 2002 JSON.org
\ No newline at end of file
diff --git a/webconsole/pom.xml b/webconsole/pom.xml
new file mode 100644
index 0000000..96341b1
--- /dev/null
+++ b/webconsole/pom.xml
@@ -0,0 +1,139 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+    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.sling</groupId>
+        <artifactId>sling</artifactId>
+        <version>1-incubator-SNAPSHOT</version>
+        <relativePath>../../parent/pom.xml</relativePath>
+    </parent>
+
+    <artifactId>org.apache.sling.osgi.console.web</artifactId>
+    <packaging>bundle</packaging>
+    <version>2.0.0-incubator-SNAPSHOT</version>
+
+    <name>Sling - Sling Management Console</name>
+    <description>Sling Management Console Servlet</description>
+
+    <scm>
+        <connection>
+            scm:svn:http://svn.apache.org/repos/asf/incubator/sling/trunk/osgi/console-web
+        </connection>
+        <developerConnection>
+            scm:svn:https://svn.apache.org/repos/asf/incubator/sling/trunk/osgi/console-web
+        </developerConnection>
+        <url>
+            http://svn.apache.org/viewvc/incubator/sling/trunk/osgi/console-web
+        </url>
+    </scm>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-scr-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <Bundle-Activator>
+                            org.apache.sling.osgi.console.web.internal.SlingManagerActivator
+                        </Bundle-Activator>
+                        <Export-Package>
+                            org.apache.sling.osgi.console.web,
+                        </Export-Package>
+                        <Private-Package>
+                            !org.apache.sling.osgi.console.web,
+                            org.apache.sling.osgi.console.web.*,
+
+                            <!-- File Upload functionality -->
+                            org.apache.commons.fileupload,
+                            org.apache.commons.fileupload.disk,
+                            org.apache.commons.fileupload.servlet,
+
+                            <!-- Required by FileUpload and Util -->
+                            org.apache.commons.io,
+                            org.apache.commons.io.filefilter,
+                            org.apache.commons.io.output,
+
+                            <!-- Required for JSON data transfer -->
+                            org.apache.sling.commons.json,
+
+                            <!-- Import/Export-Package parsing -->
+                            org.apache.felix.bundlerepository
+                        </Private-Package>
+                        <Import-Package>
+                            org.apache.sling.*; org.apache.felix.*;
+                            org.osgi.service.obr;resolution:=optional,*
+                        </Import-Package>
+                    </instructions>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>servlet-api</artifactId>
+        </dependency>
+
+        <!-- This adds commons-io transitively -->
+        <dependency>
+            <groupId>commons-fileupload</groupId>
+            <artifactId>commons-fileupload</artifactId>
+            <scope>compile</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.osgi.core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.osgi.compendium</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.commons.json</artifactId>
+            <version>2.0.0-incubator-SNAPSHOT</version>
+            <scope>compile</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.scr</artifactId>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.bundlerepository</artifactId>
+            <scope>compile</scope>
+        </dependency>
+
+    </dependencies>
+</project>
diff --git a/webconsole/src/main/java/org/apache/sling/osgi/console/web/Action.java b/webconsole/src/main/java/org/apache/sling/osgi/console/web/Action.java
new file mode 100644
index 0000000..80ef110
--- /dev/null
+++ b/webconsole/src/main/java/org/apache/sling/osgi/console/web/Action.java
@@ -0,0 +1,59 @@
+/*
+ * 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.sling.osgi.console.web;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+public interface Action {
+
+    static final String SERVICE = Action.class.getName();
+    
+    /**
+     * The name of a request attribute, which may be set by performAction if
+     * redirecting.
+     */
+    static final String ATTR_REDIRECT_PARAMETERS= "redirectParameters";
+
+    String getName();
+
+    String getLabel();
+
+    /**
+     * Performs the action the request data optionally sending a response to
+     * the HTTP Servlet Response.
+     *
+     * @param request
+     * @param response
+     *
+     * @return <code>true</code> the client should be redirected after the
+     *      action has been taken. <code>false</code> if this method also
+     *      provided response to the client and nore more processing is
+     *      required.
+     *
+     * @throws IOException May be thrown if an I/O error occurrs
+     * @throws ServletException May be thrown if another error occurrs while
+     *      processing the action. The <code>rootCause</code> of the exception
+     *      should contain the cause of the error.
+     */
+    boolean performAction(HttpServletRequest request,
+        HttpServletResponse response) throws IOException, ServletException;
+
+}
diff --git a/webconsole/src/main/java/org/apache/sling/osgi/console/web/ConfigurationPrinter.java b/webconsole/src/main/java/org/apache/sling/osgi/console/web/ConfigurationPrinter.java
new file mode 100644
index 0000000..b28af2e
--- /dev/null
+++ b/webconsole/src/main/java/org/apache/sling/osgi/console/web/ConfigurationPrinter.java
@@ -0,0 +1,31 @@
+/*
+ * 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.sling.osgi.console.web;
+
+import java.io.PrintWriter;
+
+public interface ConfigurationPrinter {
+
+    static final String SERVICE = ConfigurationPrinter.class.getName();
+
+    String getTitle();
+
+    void printConfiguration(PrintWriter printWriter);
+
+}
diff --git a/webconsole/src/main/java/org/apache/sling/osgi/console/web/Render.java b/webconsole/src/main/java/org/apache/sling/osgi/console/web/Render.java
new file mode 100644
index 0000000..6fd4c65
--- /dev/null
+++ b/webconsole/src/main/java/org/apache/sling/osgi/console/web/Render.java
@@ -0,0 +1,38 @@
+/*
+ * 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.sling.osgi.console.web;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * The <code>Render</code> TODO
+ */
+public interface Render {
+
+    static final String SERVICE = Render.class.getName();
+
+    String getName();
+
+    String getLabel();
+
+    void render(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException;
+
+}
diff --git a/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/BaseManagementPlugin.java b/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/BaseManagementPlugin.java
new file mode 100644
index 0000000..e430145
--- /dev/null
+++ b/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/BaseManagementPlugin.java
@@ -0,0 +1,74 @@
+/*
+ * 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.sling.osgi.console.web.internal;
+
+import org.apache.sling.osgi.console.web.internal.servlet.Logger;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.packageadmin.PackageAdmin;
+import org.osgi.service.startlevel.StartLevel;
+import org.osgi.util.tracker.ServiceTracker;
+
+public class BaseManagementPlugin {
+
+    private BundleContext bundleContext;
+    private Logger log;
+
+    private ServiceTracker startLevelService;
+
+    private ServiceTracker packageAdmin;
+
+    protected BaseManagementPlugin() {
+    }
+
+    public void setBundleContext(BundleContext bundleContext) {
+        this.bundleContext = bundleContext;
+    }
+    
+    public void setLogger(Logger log) {
+        this.log = log;
+    }
+
+    protected BundleContext getBundleContext() {
+        return bundleContext;
+    }
+    
+    protected Logger getLog() {
+        return log;
+    }
+    
+    protected StartLevel getStartLevel() {
+        if (startLevelService == null) {
+            startLevelService = new ServiceTracker(getBundleContext(),
+                StartLevel.class.getName(), null);
+            startLevelService.open();
+        }
+        return (StartLevel) startLevelService.getService();
+    }
+
+    protected PackageAdmin getPackageAdmin() {
+        if (packageAdmin == null) {
+            packageAdmin = new ServiceTracker(getBundleContext(),
+                PackageAdmin.class.getName(), null);
+            packageAdmin.open();
+        }
+        return (PackageAdmin) packageAdmin.getService();
+    }
+
+
+}
diff --git a/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/SlingManagerActivator.java b/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/SlingManagerActivator.java
new file mode 100644
index 0000000..d00dad4
--- /dev/null
+++ b/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/SlingManagerActivator.java
@@ -0,0 +1,39 @@
+/*
+ * 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.sling.osgi.console.web.internal;
+
+import org.apache.sling.osgi.console.web.internal.servlet.SlingManager;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+
+public class SlingManagerActivator implements BundleActivator {
+
+    private SlingManager slingManager;
+    
+    public void start(BundleContext bundleContext) {
+        slingManager = new SlingManager(bundleContext);
+    }
+
+    public void stop(BundleContext arg0) {
+        if (slingManager != null) {
+            slingManager.dispose();
+        }
+    }
+
+}
diff --git a/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/Util.java b/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/Util.java
new file mode 100644
index 0000000..0ab4a64
--- /dev/null
+++ b/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/Util.java
@@ -0,0 +1,170 @@
+/*
+ * 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.sling.osgi.console.web.internal;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.text.MessageFormat;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.sling.osgi.console.web.Render;
+
+/**
+ * The <code>Util</code> TODO
+ */
+public class Util {
+
+    /** web apps subpage */
+    public static final String PAGE_WEBAPPS = "/webapps";
+
+    /** vm statistics subpage */
+    public static final String PAGE_VM_STAT = "/vmstat";
+
+    /** Logs subpage */
+    public static final String PAGE_LOGS = "/logs";
+
+    /** Parameter name */
+    public static final String PARAM_ACTION = "action";
+
+    /** Parameter name */
+    public static final String PARAM_CONTENT = "content";
+
+    /** Parameter name */
+    public static final String PARAM_SHUTDOWN = "shutdown";
+
+    /** Parameter value */
+    public static final String VALUE_SHUTDOWN = "shutdown";
+
+    private static final String HEADER = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">"
+        + "<html>"
+        + "<head>"
+        + "<meta http-equiv=\"Content-Type\" content=\"text/html; utf-8\">"
+        + "<link rel=\"icon\" href=\"res/imgs/favicon.ico\">"
+        + "<title>{0} - {12}</title>"
+        + "<script src=\"res/ui/admin.js\" language=\"JavaScript\"></script>"
+        + "<script language=\"JavaScript\">"
+        + "ABOUT_VERSION=''{1}'';"
+        + "ABOUT_JVERSION=''{2}'';"
+        + "ABOUT_JRT=''{3} (build {2})'';"
+        + "ABOUT_JVM=''{4} (build {5}, {6})'';"
+        + "ABOUT_MEM=\"{7} KB\";"
+        + "ABOUT_USED=\"{8} KB\";"
+        + "ABOUT_FREE=\"{9} KB\";"
+        + "</script>"
+        + "<link href=\"res/ui/admin.css\" rel=\"stylesheet\" type=\"text/css\">"
+        + "</head>"
+        + "<body>"
+        + "<div id=\"main\">"
+        + "<div id=\"lead\">"
+        + "<h1>"
+        + "{0}<br>{12}"
+        + "</h1>"
+        + "<p>"
+        + "<a target=\"_blank\" href=\"{13}\" title=\"{11}\"><img src=\"res/imgs/Sling.png\" width=\"175\" height=\"100\" border=\"0\"></a>"
+        + "</p>" + "</div>";
+
+    /** The name of the request attribute containig the map of FileItems from the POST request */
+    public static final String ATTR_FILEUPLOAD = "org.apache.sling.webmanager.fileupload";
+
+    public static PrintWriter startHtml(HttpServletResponse resp, String pageTitle) throws IOException {
+        resp.setContentType("text/html; utf-8");
+
+        PrintWriter pw = resp.getWriter();
+
+        String adminTitle = "Sling Management Console"; // ServletEngine.VERSION.getFullProductName();
+        String productName = "Sling"; // ServletEngine.VERSION.getShortProductName();
+        String productWeb = "http://incubator.apache.org/sling";
+        String vendorName = "http://www.apache.org"; // ServletEngine.VERSION.getVendorWeb();
+        String vendorWeb = "http://www.apache.org"; // ServletEngine.VERSION.getVendorWeb();
+
+        long freeMem = Runtime.getRuntime().freeMemory() / 1024;
+        long totalMem = Runtime.getRuntime().totalMemory() / 1024;
+        long usedMem = totalMem - freeMem;
+
+        String header = MessageFormat.format(HEADER, new Object[] {
+            adminTitle,
+            "1.0.0-SNAPSHOT", // ServletEngine.VERSION.getFullVersion(),
+            System.getProperty("java.runtime.version"),
+            System.getProperty("java.runtime.name"),
+            System.getProperty("java.vm.name"),
+            System.getProperty("java.vm.version"),
+            System.getProperty("java.vm.info"), new Long(totalMem),
+            new Long(usedMem), new Long(freeMem), vendorWeb, productName,
+            pageTitle, productWeb, vendorName});
+        pw.println(header);
+        return pw;
+    }
+
+    public static void navigation(PrintWriter pw, Collection<Render> renders, String current, boolean disabled) {
+        pw.println("<p id='technav'>");
+
+        SortedMap<String, String> map = new TreeMap<String, String>();
+        for (Iterator<Render> ri=renders.iterator(); ri.hasNext(); ) {
+            Render render = ri.next();
+            if (render.getLabel() == null) {
+                // ignore renders without a label
+            } else if (disabled || current.equals(render.getName())) {
+                map.put(render.getLabel(), "<span class='technavat'>" + render.getLabel() + "</span>");
+            } else {
+                map.put(render.getLabel(), "<a href='" + render.getName() + "'>" + render.getLabel() + "</a></li>");
+            }
+        }
+
+        for (Iterator<String> li=map.values().iterator(); li.hasNext(); ) {
+            pw.println(li.next());
+        }
+
+        pw.println("</p>");
+    }
+
+    public static void endHhtml(PrintWriter pw) {
+        pw.println("</body>");
+        pw.println("</html>");
+    }
+
+    public static void startScript(PrintWriter pw) {
+        pw.println("<script type='text/javascript'>");
+        pw.println("// <![CDATA[");
+    }
+
+    public static void endScript(PrintWriter pw) {
+        pw.println("// ]]>");
+        pw.println("</script>");
+    }
+
+    public static void spool(String res, HttpServletResponse resp) throws IOException {
+        InputStream ins = getResource(res);
+        if (ins != null) {
+            try {
+                IOUtils.copy(ins, resp.getOutputStream());
+            } finally {
+                IOUtils.closeQuietly(ins);
+            }
+        }
+    }
+
+    private static InputStream getResource(String resource) {
+        return Util.class.getResourceAsStream(resource);
+    }
+}
diff --git a/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/compendium/AbstractScrPlugin.java b/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/compendium/AbstractScrPlugin.java
new file mode 100644
index 0000000..97167bb
--- /dev/null
+++ b/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/compendium/AbstractScrPlugin.java
@@ -0,0 +1,44 @@
+/*
+ * 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.sling.osgi.console.web.internal.compendium;
+
+import org.apache.felix.scr.ScrService;
+import org.apache.sling.osgi.console.web.internal.BaseManagementPlugin;
+import org.osgi.util.tracker.ServiceTracker;
+
+public class AbstractScrPlugin extends BaseManagementPlugin {
+
+    private ServiceTracker scrServiceTracker;
+
+    protected ScrService getScrService() {
+        if (scrServiceTracker == null) {
+            try {
+                scrServiceTracker = new ServiceTracker(getBundleContext(),
+                    ScrService.class.getName(), null);
+                scrServiceTracker.open();
+            } catch (Throwable t) {
+                // missing ScrService class ??
+                return null;
+            }
+        }
+
+        return (ScrService) scrServiceTracker.getService();
+    }
+
+}
diff --git a/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/compendium/AjaxConfigManagerAction.java b/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/compendium/AjaxConfigManagerAction.java
new file mode 100644
index 0000000..d9018a0
--- /dev/null
+++ b/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/compendium/AjaxConfigManagerAction.java
@@ -0,0 +1,466 @@
+/*
+ * 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.sling.osgi.console.web.internal.compendium;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.lang.reflect.Array;
+import java.util.Arrays;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Properties;
+import java.util.StringTokenizer;
+import java.util.Vector;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.sling.commons.json.JSONArray;
+import org.apache.sling.commons.json.JSONException;
+import org.apache.sling.commons.json.JSONObject;
+import org.apache.sling.osgi.console.web.Action;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.Constants;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.Version;
+import org.osgi.service.cm.Configuration;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.service.metatype.AttributeDefinition;
+import org.osgi.service.metatype.ObjectClassDefinition;
+
+/**
+ * The <code>AjaxConfigManagerAction</code> TODO
+ * 
+ */
+public class AjaxConfigManagerAction extends ConfigManagerBase implements
+        Action {
+
+    public static final String NAME = "ajaxConfigManager";
+
+    public String getName() {
+        return NAME;
+    }
+
+    public String getLabel() {
+        return NAME;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.apache.sling.manager.web.internal.Action#performAction(javax.servlet.http.HttpServletRequest,
+     *      javax.servlet.http.HttpServletResponse)
+     */
+    public boolean performAction(HttpServletRequest request,
+            HttpServletResponse response) throws IOException {
+
+        // should actually apply the configuration before redirecting
+        if (request.getParameter("apply") != null) {
+            return applyConfiguration(request);
+        }
+
+        JSONObject result = new JSONObject();
+
+        String pid = request.getParameter(ConfigManager.PID);
+        boolean isFactory = pid == null;
+        if (isFactory) {
+            pid = request.getParameter("factoryPid");
+        }
+
+        if (pid != null) {
+            try {
+                this.configForm(result, pid, isFactory, getLocale(request));
+            } catch (Exception e) {
+                // add message
+            }
+        }
+
+        // send the result
+        response.setContentType("text/javascript");
+        response.getWriter().print(result.toString());
+
+        return false;
+    }
+
+    private void configForm(JSONObject json, String pid, boolean isFactory,
+            Locale loc) throws IOException, JSONException {
+        String locale = (loc == null) ? null : loc.toString();
+
+        ConfigurationAdmin ca = this.getConfigurationAdmin();
+        if (ca == null) {
+            // should print message
+            return;
+        }
+
+        Configuration config = null;
+        try {
+            Configuration[] configs = ca.listConfigurations("("
+                + Constants.SERVICE_PID + "=" + pid + ")");
+            if (configs != null && configs.length > 0) {
+                config = configs[0];
+            }
+        } catch (InvalidSyntaxException ise) {
+            // should print message
+            return;
+        }
+
+        json.put(ConfigManager.PID, pid);
+        json.put("isFactory", isFactory);
+
+        Dictionary<?, ?> props = null;
+        ObjectClassDefinition ocd;
+        if (config != null) {
+            props = config.getProperties();
+            ocd = this.getObjectClassDefinition(config, locale);
+        } else {
+            ocd = this.getObjectClassDefinition(pid, locale);
+        }
+
+        props = this.mergeWithMetaType(props, ocd, json);
+
+        if (props != null) {
+            JSONObject properties = new JSONObject();
+            for (Enumeration<?> pe = props.keys(); pe.hasMoreElements();) {
+                Object key = pe.nextElement();
+
+                // ignore well known special properties
+                if (!key.equals(Constants.SERVICE_PID)
+                    && !key.equals(Constants.SERVICE_DESCRIPTION)
+                    && !key.equals(Constants.SERVICE_ID)
+                    && !key.equals(Constants.SERVICE_RANKING)
+                    && !key.equals(Constants.SERVICE_VENDOR)
+                    && !key.equals(ConfigurationAdmin.SERVICE_BUNDLELOCATION)
+                    && !key.equals(ConfigurationAdmin.SERVICE_FACTORYPID)) {
+                    properties.put(String.valueOf(key), props.get(key));
+                }
+
+            }
+
+            json.put("title", pid);
+            json.put(
+                "description",
+                "Please enter configuration properties for this configuration in the field below. This configuration has no associated description");
+
+            json.put("propertylist", "properties");
+            json.put("properties", properties);
+
+        }
+
+        if (config != null) {
+            this.addConfigurationInfo(config, json, locale);
+        }
+    }
+
+    private Dictionary<?, ?> mergeWithMetaType(Dictionary<?, ?> props,
+            ObjectClassDefinition ocd, JSONObject json) throws JSONException {
+
+        if (props == null) {
+            props = new Hashtable<String, Object>();
+        }
+
+        if (ocd != null) {
+
+            String name = ocd.getName();
+            if (props.get("sling.context") != null) {
+                name += " (" + props.get("sling.context") + ")";
+            }
+            json.put("title", name);
+
+            if (ocd.getDescription() != null) {
+                json.put("description", ocd.getDescription());
+            }
+
+            AttributeDefinition[] ad = ocd.getAttributeDefinitions(ObjectClassDefinition.ALL);
+            if (ad != null) {
+
+                JSONArray propertyList = new JSONArray();
+
+                for (int i = 0; i < ad.length; i++) {
+                    JSONObject entry = new JSONObject();
+
+                    Object value = props.get(ad[i].getID());
+                    if (value == null) {
+                        value = ad[i].getDefaultValue();
+                        if (value == null) {
+                            if (ad[i].getCardinality() == 0) {
+                                value = "";
+                            } else {
+                                value = new String[0];
+                            }
+                        }
+                    }
+
+                    entry.put("name", ad[i].getName());
+
+                    if (ad[i].getOptionLabels() != null
+                        && ad[i].getOptionLabels().length > 0) {
+                        JSONObject type = new JSONObject();
+                        type.put("labels",
+                            Arrays.asList(ad[i].getOptionLabels()));
+                        type.put("values",
+                            Arrays.asList(ad[i].getOptionValues()));
+                        entry.put("type", type);
+                    } else {
+                        entry.put("type", ad[i].getType());
+                    }
+
+                    if (ad[i].getCardinality() == 0) {
+                        // scalar
+                        if (value instanceof Vector) {
+                            value = ((Vector<?>) value).get(0);
+                        } else if (value.getClass().isArray()) {
+                            value = Array.get(value, 0);
+                        }
+                        entry.put("value", value);
+                    } else {
+                        if (value instanceof Vector) {
+                            value = new JSONArray((Vector<?>) value);
+                        } else if (value.getClass().isArray()) {
+                            value = new JSONArray(
+                                Arrays.asList((Object[]) value));
+                        } else {
+                            JSONArray tmp = new JSONArray();
+                            tmp.put(value);
+                            value = tmp;
+                        }
+                        entry.put("values", value);
+                    }
+
+                    if (ad[i].getDescription() != null) {
+                        entry.put("description", ad[i].getDescription());
+                    }
+
+                    json.put(ad[i].getID(), entry);
+                    propertyList.put(ad[i].getID());
+                }
+
+                json.put("propertylist", propertyList);
+            }
+
+            // nothing more to display
+            props = null;
+        }
+
+        return props;
+    }
+
+    private void addConfigurationInfo(Configuration config, JSONObject json,
+            String locale) throws JSONException {
+
+        if (config.getFactoryPid() != null) {
+            json.put("factoryPID", config.getFactoryPid());
+        }
+
+        String location;
+        if (config.getBundleLocation() == null) {
+            location = "None";
+        } else {
+            Bundle bundle = this.getBundle(config.getBundleLocation());
+
+            @SuppressWarnings("unchecked")
+            Dictionary<String, String> headers = bundle.getHeaders(locale);
+            String name = headers.get(Constants.BUNDLE_NAME);
+            if (name == null) {
+                location = bundle.getSymbolicName();
+            } else {
+                location = name + " (" + bundle.getSymbolicName() + ")";
+            }
+
+            Version v = Version.parseVersion(headers.get(Constants.BUNDLE_VERSION));
+            location += ", Version " + v.toString();
+        }
+        json.put("bundleLocation", location);
+    }
+
+    private boolean applyConfiguration(HttpServletRequest request)
+            throws IOException {
+
+        ConfigurationAdmin ca = this.getConfigurationAdmin();
+        if (ca == null) {
+            return false;
+        }
+
+        String pid = request.getParameter("pid");
+
+        if (request.getParameter("delete") != null) {
+            // TODO: should log this here !!
+            Configuration config = ca.getConfiguration(pid, null);
+            config.delete();
+            return true;
+        } else if (request.getParameter("create") != null) {
+            // pid is a factory PID and we have to create a new configuration
+            // we should actually also display that one !
+            Configuration config = ca.createFactoryConfiguration(pid, null);
+
+            // add sling context into the configuration
+            if (request.getParameter("sling.context") != null) {
+                @SuppressWarnings("unchecked")
+                Dictionary<String, Object> props = config.getProperties();
+                if (props == null) {
+                    props = new Hashtable<String, Object>();
+                }
+                props.put("sling.context",
+                    request.getParameter("sling.context"));
+                config.update(props);
+            }
+
+            // request.setAttribute(ATTR_REDIRECT_PARAMETERS, "pid=" +
+            // config.getPid());
+            return true;
+        }
+
+        String propertyList = request.getParameter("propertylist");
+        if (propertyList == null) {
+            String propertiesString = request.getParameter("properties");
+
+            if (propertiesString != null) {
+                byte[] propBytes = propertiesString.getBytes("ISO-8859-1");
+                ByteArrayInputStream bin = new ByteArrayInputStream(propBytes);
+                Properties props = new Properties();
+                props.load(bin);
+
+                Configuration config = ca.getConfiguration(pid, null);
+                config.update(props);
+            }
+        } else {
+            Configuration config = ca.getConfiguration(pid, null);
+            @SuppressWarnings("unchecked")
+            Dictionary<String, Object> props = config.getProperties();
+            if (props == null) {
+                props = new Hashtable<String, Object>();
+            }
+
+            Map<String, AttributeDefinition> adMap = this.getAttributeDefinitionMap(
+                config, null);
+            if (adMap != null) {
+                StringTokenizer propTokens = new StringTokenizer(propertyList,
+                    ",");
+                while (propTokens.hasMoreTokens()) {
+                    String propName = propTokens.nextToken();
+                    AttributeDefinition ad = adMap.get(propName);
+                    if (ad == null
+                        || (ad.getCardinality() == 0 && ad.getType() == AttributeDefinition.STRING)) {
+                        String prop = request.getParameter(propName);
+                        if (prop != null) {
+                            props.put(propName, prop);
+                        }
+                    } else if (ad.getCardinality() == 0) {
+                        // scalar of non-string
+                        String prop = request.getParameter(propName);
+                        props.put(propName, this.toType(ad.getType(), prop));
+                    } else {
+                        // array or vector of any type
+                        Vector<Object> vec = new Vector<Object>();
+
+                        String[] properties = request.getParameterValues(propName);
+                        if (properties != null) {
+                            for (int i = 0; i < properties.length; i++) {
+                                vec.add(this.toType(ad.getType(), properties[i]));
+                            }
+                        }
+
+                        // but ensure size
+                        int maxSize = Math.abs(ad.getCardinality());
+                        if (vec.size() > maxSize) {
+                            vec.setSize(maxSize);
+                        }
+
+                        if (ad.getCardinality() < 0) {
+                            // keep the vector
+                            props.put(propName, vec);
+                        } else {
+                            // convert to an array
+                            props.put(propName, this.toArray(ad.getType(), vec));
+                        }
+                    }
+                }
+            }
+
+            config.update(props);
+        }
+
+        // request.setAttribute(ATTR_REDIRECT_PARAMETERS, "pid=" + pid);
+        return true;
+    }
+
+    private Object toType(int type, String value) {
+        switch (type) {
+            case AttributeDefinition.BOOLEAN:
+                return Boolean.valueOf(value);
+            case AttributeDefinition.BYTE:
+                return Byte.valueOf(value);
+            case AttributeDefinition.CHARACTER:
+                char c = (value.length() > 0) ? value.charAt(0) : 0;
+                return new Character(c);
+            case AttributeDefinition.DOUBLE:
+                return Double.valueOf(value);
+            case AttributeDefinition.FLOAT:
+                return Float.valueOf(value);
+            case AttributeDefinition.LONG:
+                return Long.valueOf(value);
+            case AttributeDefinition.INTEGER:
+                return Integer.valueOf(value);
+            case AttributeDefinition.SHORT:
+                return Short.valueOf(value);
+
+            default:
+                // includes AttributeDefinition.STRING
+                return value;
+        }
+    }
+
+    private Object toArray(int type, Vector<Object> values) {
+        int size = values.size();
+
+        // short cut for string array
+        if (type == AttributeDefinition.STRING) {
+            return values.toArray(new String[size]);
+        }
+
+        Object array;
+        switch (type) {
+            case AttributeDefinition.BOOLEAN:
+                array = new boolean[size];
+            case AttributeDefinition.BYTE:
+                array = new byte[size];
+            case AttributeDefinition.CHARACTER:
+                array = new char[size];
+            case AttributeDefinition.DOUBLE:
+                array = new double[size];
+            case AttributeDefinition.FLOAT:
+                array = new float[size];
+            case AttributeDefinition.LONG:
+                array = new long[size];
+            case AttributeDefinition.INTEGER:
+                array = new int[size];
+            case AttributeDefinition.SHORT:
+                array = new short[size];
+            default:
+                // unexpected, but assume string
+                array = new String[size];
+        }
+
+        for (int i = 0; i < size; i++) {
+            Array.set(array, i, values.get(i));
+        }
+
+        return array;
+    }
+}
diff --git a/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/compendium/ComponentConfigurationPrinter.java b/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/compendium/ComponentConfigurationPrinter.java
new file mode 100644
index 0000000..59ecaa9
--- /dev/null
+++ b/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/compendium/ComponentConfigurationPrinter.java
@@ -0,0 +1,195 @@
+/*
+ * 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.sling.osgi.console.web.internal.compendium;
+
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.Iterator;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+import org.apache.felix.scr.Component;
+import org.apache.felix.scr.Reference;
+import org.apache.felix.scr.ScrService;
+import org.apache.sling.osgi.console.web.ConfigurationPrinter;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.component.ComponentConstants;
+
+public class ComponentConfigurationPrinter extends AbstractScrPlugin implements
+        ConfigurationPrinter {
+
+    private ServiceRegistration registration;
+
+    @Override
+    public void setBundleContext(BundleContext bundleContext) {
+        super.setBundleContext(bundleContext);
+
+        registration = bundleContext.registerService(
+            ConfigurationPrinter.SERVICE, this, null);
+    }
+
+    public String getTitle() {
+        return "Declarative Services Components";
+    }
+
+    public void printConfiguration(PrintWriter pw) {
+        ScrService scrService = getScrService();
+        if (scrService != null) {
+            Component[] components = scrService.getComponents();
+
+            if (components == null || components.length == 0) {
+
+                pw.println("  No Components Registered");
+
+            } else {
+
+                // order components by id
+                TreeMap<Long, Component> componentMap = new TreeMap<Long, Component>();
+                for (Component component : components) {
+                    componentMap.put(component.getId(), component);
+                }
+
+                // render components
+                for (Component component : componentMap.values()) {
+                    component(pw, component);
+                }
+            }
+        } else {
+            pw.println("  Apache Felix Declarative Service not installed");
+        }
+    }
+
+    private void component(PrintWriter pw, Component component) {
+
+        pw.print(component.getId());
+        pw.print("=[");
+        pw.print(component.getName());
+        pw.println("]");
+
+        pw.println("  Bundle" + component.getBundle().getSymbolicName() + " ("
+            + component.getBundle().getBundleId() + ")");
+        pw.println("  State="
+            + ComponentRenderAction.toStateString(component.getState()));
+        pw.println("  DefaultState="
+            + (component.isDefaultEnabled() ? "enabled" : "disabled"));
+        pw.println("  Activation="
+            + (component.isImmediate() ? "immediate" : "delayed"));
+
+        listServices(pw, component);
+        listReferences(pw, component);
+        listProperties(pw, component);
+
+        pw.println();
+    }
+
+    private void listServices(PrintWriter pw, Component component) {
+        String[] services = component.getServices();
+        if (services == null) {
+            return;
+        }
+
+        pw.println("  ServiceType="
+            + (component.isServiceFactory() ? "service factory" : "service"));
+
+        StringBuffer buf = new StringBuffer();
+        for (int i = 0; i < services.length; i++) {
+            if (i > 0) {
+                buf.append(", ");
+            }
+            buf.append(services[i]);
+        }
+
+        pw.println("  Services=" + buf);
+    }
+
+    private void listReferences(PrintWriter pw, Component component) {
+        Reference[] refs = component.getReferences();
+        if (refs != null) {
+            for (int i = 0; i < refs.length; i++) {
+
+                pw.println("  Reference=" + refs[i].getName() + ", "
+                    + (refs[i].isSatisfied() ? "Satisfied" : "Unsatisfied"));
+
+                pw.println("    Service Name: " + refs[i].getServiceName());
+
+                if (refs[i].getTarget() != null) {
+                    pw.println("  Target Filter: " + refs[i].getTarget());
+                }
+
+                pw.println("    Multiple: "
+                    + (refs[i].isMultiple() ? "multiple" : "single"));
+                pw.println("    Optional: "
+                    + (refs[i].isOptional() ? "optional" : "mandatory"));
+                pw.println("    Policy: "
+                    + (refs[i].isStatic() ? "static" : "dynamic"));
+
+                // list bound services
+                ServiceReference[] boundRefs = refs[i].getServiceReferences();
+                if (boundRefs != null && boundRefs.length > 0) {
+                    for (int j = 0; j < boundRefs.length; j++) {
+                        pw.print("    Bound Service: ID ");
+                        pw.print(boundRefs[j].getProperty(Constants.SERVICE_ID));
+
+                        String name = (String) boundRefs[j].getProperty(ComponentConstants.COMPONENT_NAME);
+                        if (name == null) {
+                            name = (String) boundRefs[j].getProperty(Constants.SERVICE_PID);
+                            if (name == null) {
+                                name = (String) boundRefs[j].getProperty(Constants.SERVICE_DESCRIPTION);
+                            }
+                        }
+                        if (name != null) {
+                            pw.print(" (");
+                            pw.print(name);
+                            pw.print(")");
+                        }
+                    }
+                } else {
+                    pw.print("    No Services bound");
+                }
+                pw.println();
+            }
+        }
+    }
+
+    private void listProperties(PrintWriter pw, Component component) {
+        @SuppressWarnings("unchecked")
+        Dictionary<String, Object> props = component.getProperties();
+        if (props != null) {
+
+            pw.println("  Properties=");
+            TreeSet<String> keys = new TreeSet<String>(
+                Collections.list(props.keys()));
+            for (Iterator<String> ki = keys.iterator(); ki.hasNext();) {
+                String key = ki.next();
+                Object value = props.get(key);
+                if (value.getClass().isArray()) {
+                    value = Arrays.asList((Object[]) value);
+                }
+                pw.println("    " + key + "=" + value);
+            }
+        }
+
+    }
+
+}
diff --git a/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/compendium/ComponentRenderAction.java b/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/compendium/ComponentRenderAction.java
new file mode 100644
index 0000000..04d1c39
--- /dev/null
+++ b/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/compendium/ComponentRenderAction.java
@@ -0,0 +1,431 @@
+/*
+ * 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.sling.osgi.console.web.internal.compendium;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.Iterator;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.felix.scr.Component;
+import org.apache.felix.scr.Reference;
+import org.apache.felix.scr.ScrService;
+import org.apache.sling.commons.json.JSONArray;
+import org.apache.sling.commons.json.JSONException;
+import org.apache.sling.commons.json.JSONObject;
+import org.apache.sling.osgi.console.web.Action;
+import org.apache.sling.osgi.console.web.Render;
+import org.apache.sling.osgi.console.web.internal.Util;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.component.ComponentConstants;
+
+public class ComponentRenderAction extends AbstractScrPlugin implements Render,
+        Action {
+
+    public static final String NAME = "components";
+
+    public static final String LABEL = "Components";
+
+    public static final String COMPONENT_ID = "componentId";
+
+    public static final String OPERATION = "op";
+
+    public static final String OPERATION_DETAILS = "details";
+
+    public static final String OPERATION_ENABLE = "enable";
+
+    public static final String OPERATION_DISABLE = "disable";
+
+    public String getName() {
+        return NAME;
+    }
+
+    public String getLabel() {
+        return LABEL;
+    }
+
+    public boolean performAction(HttpServletRequest request,
+            HttpServletResponse response) throws IOException {
+
+        ScrService scrService = getScrService();
+        if (scrService != null) {
+
+            long componentId = getComponentId(request);
+            Component component = scrService.getComponent(componentId);
+
+            if (component != null) {
+                String op = request.getParameter(OPERATION);
+                if (OPERATION_DETAILS.equals(op)) {
+                    return sendAjaxDetails(component, response);
+                } else if (OPERATION_ENABLE.equals(op)) {
+                    component.enable();
+                } else if (OPERATION_DISABLE.equals(op)) {
+                    component.disable();
+                }
+            }
+
+        }
+
+        return true;
+    }
+
+    public void render(HttpServletRequest request, HttpServletResponse response)
+            throws IOException {
+
+        PrintWriter pw = response.getWriter();
+
+        this.header(pw);
+
+        pw.println("<tr class='content'>");
+        pw.println("<td colspan='5' class='content'>&nbsp;</th>");
+        pw.println("</tr>");
+
+        this.tableHeader(pw);
+
+        ScrService scrService = getScrService();
+        if (scrService == null) {
+            pw.println("<tr class='content'>");
+            pw.println("<td class='content' colspan='5'>Apache Felix Declarative Service required for this function</td>");
+            pw.println("</tr>");
+        } else {
+            Component[] components = scrService.getComponents();
+            if (components == null || components.length == 0) {
+                pw.println("<tr class='content'>");
+                pw.println("<td class='content' colspan='5'>No "
+                    + this.getLabel() + " installed currently</td>");
+                pw.println("</tr>");
+
+            } else {
+
+                // order components by id
+                TreeMap<String, Component> componentMap = new TreeMap<String, Component>();
+                for (Component component : components) {
+                    componentMap.put(component.getName(), component);
+                }
+
+                // render components
+                long previousComponent = -1;
+                for (Component component : componentMap.values()) {
+                    if (previousComponent >= 0) {
+                        // prepare for injected table information row
+                        pw.println("<tr id='component" + previousComponent
+                            + "'></tr>");
+                    }
+
+                    component(pw, component);
+
+                    previousComponent = component.getId();
+                }
+
+                if (previousComponent >= 0) {
+                    // prepare for injected table information row
+                    pw.println("<tr id='component" + previousComponent
+                        + "'></tr>");
+                }
+            }
+        }
+
+        pw.println("<tr class='content'>");
+        pw.println("<td colspan='5' class='content'>&nbsp;</th>");
+        pw.println("</tr>");
+
+        this.footer(pw);
+    }
+
+    private void header(PrintWriter pw) {
+        Util.startScript(pw);
+        pw.println("function showDetails(componentId) {");
+        pw.println("    var span = document.getElementById('component' + componentId);");
+        pw.println("    if (!span) {");
+        pw.println("        return;");
+        pw.println("    }");
+        pw.println("    if (span.innerHTML) {");
+        pw.println("        span.innerHTML = '';");
+        pw.println("        return;");
+        pw.println("    }");
+        pw.println("    var parm = '?" + Util.PARAM_ACTION + "=" + NAME + "&"
+            + OPERATION + "=" + OPERATION_DETAILS + "&" + COMPONENT_ID
+            + "=' + componentId;");
+        pw.println("    sendRequest('GET', parm, displayComponentDetails);");
+        pw.println("}");
+        pw.println("function displayComponentDetails(obj) {");
+        pw.println("    var span = document.getElementById('component' + obj."
+            + COMPONENT_ID + ");");
+        pw.println("    if (!span) {");
+        pw.println("        return;");
+        pw.println("    }");
+        pw.println("    var innerHtml = '<td class=\"content\">&nbsp;</td><td class=\"content\" colspan=\"4\"><table broder=\"0\">';");
+        pw.println("    var props = obj.props;");
+        pw.println("    for (var i=0; i < props.length; i++) {");
+        pw.println("        innerHtml += '<tr><td valign=\"top\" noWrap>' + props[i].key + '</td><td valign=\"top\">' + props[i].value + '</td></tr>';");
+        pw.println("    }");
+        pw.println("    innerHtml += '</table></td>';");
+        pw.println("    span.innerHTML = innerHtml;");
+        pw.println("}");
+        Util.endScript(pw);
+
+        pw.println("<table class='content' cellpadding='0' cellspacing='0' width='100%'>");
+    }
+
+    private void tableHeader(PrintWriter pw) {
+
+        pw.println("<tr class='content'>");
+        pw.println("<th class='content'>ID</th>");
+        pw.println("<th class='content' width='100%'>Name</th>");
+        pw.println("<th class='content'>Status</th>");
+        pw.println("<th class='content' colspan='2'>Actions</th>");
+        pw.println("</tr>");
+    }
+
+    private void footer(PrintWriter pw) {
+        pw.println("</table>");
+    }
+
+    private void component(PrintWriter pw, Component component) {
+        String name = component.getName();
+
+        pw.println("<tr>");
+        pw.println("<td class='content right'>" + component.getId() + "</td>");
+        pw.println("<td class='content'><a href='javascript:showDetails("
+            + component.getId() + ")'>" + name + "</a></td>");
+        pw.println("<td class='content center'>"
+            + toStateString(component.getState()) + "</td>");
+
+        boolean enabled = component.getState() == Component.STATE_DISABLED;
+        this.actionForm(pw, enabled, component.getId(), OPERATION_ENABLE,
+            "Enable");
+
+        enabled = component.getState() != Component.STATE_DISABLED
+            && component.getState() != Component.STATE_DESTROYED;
+        this.actionForm(pw, enabled, component.getId(), OPERATION_DISABLE,
+            "Disable");
+
+        pw.println("</tr>");
+    }
+
+    private void actionForm(PrintWriter pw, boolean enabled, long componentId,
+            String op, String opLabel) {
+        pw.println("<form name='form" + componentId + "' method='post'>");
+        pw.println("<td class='content' align='right'>");
+        pw.println("<input type='hidden' name='" + Util.PARAM_ACTION
+            + "' value='" + NAME + "' />");
+        pw.println("<input type='hidden' name='" + OPERATION + "' value='" + op
+            + "' />");
+        pw.println("<input type='hidden' name='" + COMPONENT_ID + "' value='"
+            + componentId + "' />");
+        pw.println("<input class='submit' type='submit' value='" + opLabel
+            + "'" + (enabled ? "" : "disabled") + " />");
+        pw.println("</td>");
+        pw.println("</form>");
+    }
+
+    private boolean sendAjaxDetails(Component component,
+            HttpServletResponse response) throws IOException {
+        JSONObject result = null;
+        try {
+            if (component != null) {
+
+                JSONArray props = new JSONArray();
+                keyVal(props, "Bundle", component.getBundle().getSymbolicName()
+                    + " (" + component.getBundle().getBundleId() + ")");
+                keyVal(props, "Default State", component.isDefaultEnabled()
+                        ? "enabled"
+                        : "disabled");
+                keyVal(props, "Activation", component.isImmediate()
+                        ? "immediate"
+                        : "delayed");
+
+                listServices(props, component);
+                listReferences(props, component);
+                listProperties(props, component);
+
+                result = new JSONObject();
+                result.put(ComponentRenderAction.COMPONENT_ID,
+                    component.getId());
+                result.put("props", props);
+            }
+        } catch (Exception exception) {
+            // create an empty result on problems
+            result = new JSONObject();
+        }
+
+        // send the result
+        response.setContentType("text/javascript");
+        response.getWriter().print(result.toString());
+
+        return false;
+    }
+
+    private void listServices(JSONArray props, Component component) {
+        String[] services = component.getServices();
+        if (services == null) {
+            return;
+        }
+
+        keyVal(props, "Service Type", component.isServiceFactory()
+                ? "service factory"
+                : "service");
+
+        StringBuffer buf = new StringBuffer();
+        for (int i = 0; i < services.length; i++) {
+            if (i > 0) {
+                buf.append("<br />");
+            }
+            buf.append(services[i]);
+        }
+
+        keyVal(props, "Services", buf.toString());
+    }
+
+    private void listReferences(JSONArray props, Component component) {
+        Reference[] refs = component.getReferences();
+        if (refs != null) {
+            for (int i = 0; i < refs.length; i++) {
+                StringBuffer buf = new StringBuffer();
+                buf.append(refs[i].isSatisfied() ? "Satisfied" : "Unsatisfied").append(
+                    "<br />");
+                buf.append("Service Name: ").append(refs[i].getServiceName()).append(
+                    "<br />");
+                if (refs[i].getTarget() != null) {
+                    buf.append("Target Filter: ").append(refs[i].getTarget()).append(
+                        "<br />");
+                }
+                buf.append("Multiple: ").append(
+                    refs[i].isMultiple() ? "multiple" : "single").append(
+                    "<br />");
+                buf.append("Optional: ").append(
+                    refs[i].isOptional() ? "optional" : "mandatory").append(
+                    "<br />");
+                buf.append("Policy: ").append(
+                    refs[i].isStatic() ? "static" : "dynamic").append("<br />");
+
+                // list bound services
+                ServiceReference[] boundRefs = refs[i].getServiceReferences();
+                if (boundRefs != null && boundRefs.length > 0) {
+                    for (int j = 0; j < boundRefs.length; j++) {
+                        buf.append("Bound Service ID ");
+                        buf.append(boundRefs[j].getProperty(Constants.SERVICE_ID));
+
+                        String name = (String) boundRefs[j].getProperty(ComponentConstants.COMPONENT_NAME);
+                        if (name == null) {
+                            name = (String) boundRefs[j].getProperty(Constants.SERVICE_PID);
+                            if (name == null) {
+                                name = (String) boundRefs[j].getProperty(Constants.SERVICE_DESCRIPTION);
+                            }
+                        }
+                        if (name != null) {
+                            buf.append(" (");
+                            buf.append(name);
+                            buf.append(")");
+                        }
+                    }
+                } else {
+                    buf.append("No Services bound");
+                }
+                buf.append("<br />");
+
+                keyVal(props, "Reference " + refs[i].getName(), buf.toString());
+            }
+        }
+    }
+
+    private void listProperties(JSONArray jsonProps, Component component) {
+        @SuppressWarnings("unchecked")
+        Dictionary<String, Object> props = component.getProperties();
+        if (props != null) {
+            StringBuffer buf = new StringBuffer();
+            TreeSet<String> keys = new TreeSet<String>(
+                Collections.list(props.keys()));
+            for (Iterator<String> ki = keys.iterator(); ki.hasNext();) {
+                String key = ki.next();
+                buf.append(key).append(" = ");
+
+                Object prop = props.get(key);
+                if (prop.getClass().isArray()) {
+                    prop = Arrays.asList((Object[]) prop);
+                }
+                buf.append(prop);
+                if (ki.hasNext()) {
+                    buf.append("<br />");
+                }
+            }
+            keyVal(jsonProps, "Properties", buf.toString());
+        }
+
+    }
+
+    private void keyVal(JSONArray props, String key, Object value) {
+        if (key != null && value != null) {
+            try {
+                JSONObject obj = new JSONObject();
+                obj.put("key", key);
+                obj.put("value", value);
+                props.put(obj);
+            } catch (JSONException je) {
+                // don't care
+            }
+        }
+    }
+
+    static String toStateString(int state) {
+        switch (state) {
+            case Component.STATE_DISABLED:
+                return "disabled";
+            case Component.STATE_ENABLED:
+                return "enabled";
+            case Component.STATE_UNSATISFIED:
+                return "unsatisifed";
+            case Component.STATE_ACTIVATING:
+                return "activating";
+            case Component.STATE_ACTIVE:
+                return "active";
+            case Component.STATE_REGISTERED:
+                return "registered";
+            case Component.STATE_FACTORY:
+                return "factory";
+            case Component.STATE_DEACTIVATING:
+                return "deactivating";
+            case Component.STATE_DESTROYED:
+                return "destroyed";
+            default:
+                return String.valueOf(state);
+        }
+    }
+
+    protected long getComponentId(HttpServletRequest request) {
+        String componentIdPar = request.getParameter(ComponentRenderAction.COMPONENT_ID);
+        if (componentIdPar != null) {
+            try {
+                return Long.parseLong(componentIdPar);
+            } catch (NumberFormatException nfe) {
+                // TODO: log
+            }
+        }
+
+        // no bundleId or wrong format
+        return -1;
+    }
+
+}
diff --git a/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/compendium/ConfigManager.java b/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/compendium/ConfigManager.java
new file mode 100644
index 0000000..a24b31f
--- /dev/null
+++ b/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/compendium/ConfigManager.java
@@ -0,0 +1,217 @@
+/*
+ * 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.sling.osgi.console.web.internal.compendium;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Dictionary;
+import java.util.Locale;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.Map.Entry;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.sling.osgi.console.web.Render;
+import org.apache.sling.osgi.console.web.internal.Util;
+import org.apache.sling.osgi.console.web.internal.core.SetStartLevelAction;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.cm.Configuration;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.service.cm.ManagedServiceFactory;
+import org.osgi.service.metatype.ObjectClassDefinition;
+
+/**
+ * The <code>ConfigManager</code> TODO
+ */
+public class ConfigManager extends ConfigManagerBase implements Render {
+
+    public static final String NAME = "configMgr";
+
+    public static final String LABEL = "Configuration";
+
+    public static final String PID = "pid";
+
+    public String getLabel() {
+        return LABEL;
+    }
+
+    public String getName() {
+        return NAME;
+    }
+
+    public void render(HttpServletRequest request, HttpServletResponse response)
+            throws IOException {
+
+        // true if MetaType service information is not required
+        boolean optionalMetaType = false;
+
+        PrintWriter pw = response.getWriter();
+
+        pw.println("<script type='text/javascript' src='res/ui/configmanager.js'></script>");
+
+        pw.println("<table class='content' cellpadding='0' cellspacing='0' width='100%'>");
+
+        pw.println("<tr class='content' id='configField'>");
+        pw.println("<td class='content'>Configurations</th>");
+        pw.println("<td class='content'>");
+        this.listConfigurations(pw, optionalMetaType, getLocale(request));
+        pw.println("</td>");
+        pw.println("</tr>");
+
+        pw.println("</table>");
+    }
+
+    private void listConfigurations(PrintWriter pw, boolean optionalMetaType, Locale loc) {
+
+        ConfigurationAdmin ca = this.getConfigurationAdmin();
+        if (ca == null) {
+            pw.print("Configuration Admin Service not available");
+            return;
+        }
+
+        String locale = (loc != null) ? loc.toString() : null;
+
+        try {
+            // get a list of all pids for which MetaData exists
+            Map<String, Bundle> metaDataPids = this.getMetadataPids();
+
+            // sorted map of options
+            SortedMap<String, String> options = new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER);
+
+            // find all ManagedServiceFactories to get the factoryPIDs
+            ServiceReference[] refs = this.getBundleContext().getServiceReferences(
+                ManagedServiceFactory.class.getName(), null);
+            for (int i = 0; refs != null && i < refs.length; i++) {
+                Object factoryPid = refs[i].getProperty(Constants.SERVICE_PID);
+                if (factoryPid instanceof String) {
+                    String pid = (String) factoryPid;
+                    Object slingContext = refs[i].getProperty("sling.context");
+                    String name;
+                    ObjectClassDefinition ocd = this.getObjectClassDefinition(
+                        refs[i].getBundle(), pid, locale);
+                    if (ocd != null) {
+                        name = ocd.getName() + " (";
+                        if (slingContext != null) {
+                            name += slingContext + ", ";
+                        }
+                        name += pid + ")";
+                    } else if (slingContext != null) {
+                        name = pid + " (" + slingContext + ")";
+                    } else {
+                        name = pid;
+                    }
+
+                    if (ocd != null || optionalMetaType) {
+                        options.put("factoryPid="+pid, name);
+                    }
+                }
+            }
+
+            // get a sorted list of configuration PIDs
+            Configuration[] cfgs = ca.listConfigurations(null);
+            for (int i = 0; cfgs != null && i < cfgs.length; i++) {
+
+                // ignore configuration object if an entry already exists in the map
+                String pid = cfgs[i].getPid();
+                if (options.containsKey("pid="+pid) || options.containsKey("factoryPid="+pid)) {
+                    continue;
+                }
+
+                Dictionary<?, ?> props = cfgs[i].getProperties();
+                Object slingContext = (props != null) ? props.get("sling.context") : null;
+
+                // insert and entry for the pid
+                ObjectClassDefinition ocd = this.getObjectClassDefinition(cfgs[i],
+                    locale);
+                String name;
+                if (ocd != null) {
+                    name = ocd.getName() + " (";
+                    if (slingContext != null) {
+                        name += slingContext + ", ";
+                    }
+                    name += pid + ")";
+
+                    // remove from the list of known pids
+                    metaDataPids.remove(pid);
+
+                } else if (slingContext != null) {
+                    name = pid + " (" + slingContext + ")";
+                } else {
+                    name = pid;
+                }
+
+                if (ocd != null || optionalMetaType) {
+                    options.put("pid="+pid, name);
+                }
+
+                // if the configuration is part of a factory, ensure an entry for the factory
+                if (cfgs[i].getFactoryPid() != null) {
+                    pid = cfgs[i].getFactoryPid();
+                    if (options.containsValue("factoryPid="+pid)) {
+                        continue;
+                    }
+
+                    String existing = options.remove("pid="+pid);
+                    if (existing != null) {
+                        options.put("factoryPid="+pid, existing);
+                    } else {
+                        Bundle bundle = this.getBundle(cfgs[i].getBundleLocation());
+                        ocd = this.getObjectClassDefinition(bundle, pid, locale);
+                        if (ocd != null) {
+                            options.put("factoryPid="+pid, ocd.getName());
+                        } else if (optionalMetaType) {
+                            options.put("factoryPid="+pid, pid);
+                        }
+                    }
+                }
+            }
+
+            // If there are any meta data PIDs for which there is no existing
+            // configuration, we add them to the list to create configuration
+            if (!metaDataPids.isEmpty()) {
+                for (Entry<String, Bundle> mdp : metaDataPids.entrySet()) {
+                    ObjectClassDefinition ocd = this.getObjectClassDefinition(
+                        mdp.getValue(), mdp.getKey(), locale);
+                    options.put("pid=" + mdp.getKey(), ocd.getName() + " ("
+                        + mdp.getKey() + ")");
+                }
+            }
+
+            pw.println("<form method='post' name='configSelection' onSubmit='configure(); return false;'");
+            pw.println("<input type='hidden' name='" + Util.PARAM_ACTION
+                + "' value='" + SetStartLevelAction.NAME + "'>");
+            pw.println("<select class='select' name='pid' onChange='configure();'>");
+            for (Entry<String, String> entry : options.entrySet()) {
+                pw.print("<option value='" + entry.getKey() + "'>");
+                pw.print(entry.getValue());
+                pw.println("</option>");
+            }
+            pw.println("</select>");
+            pw.println("&nbsp;&nbsp;");
+            pw.println("<input class='submit' type='submit' value='Configure' />");
+            pw.println("</form>");
+
+        } catch (Exception e) {
+            // write a message or ignore
+        }
+    }
+}
diff --git a/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/compendium/ConfigManagerBase.java b/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/compendium/ConfigManagerBase.java
new file mode 100644
index 0000000..e55a201
--- /dev/null
+++ b/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/compendium/ConfigManagerBase.java
@@ -0,0 +1,212 @@
+/*
+ * 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.sling.osgi.console.web.internal.compendium;
+
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.sling.osgi.console.web.internal.BaseManagementPlugin;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.cm.Configuration;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.service.metatype.AttributeDefinition;
+import org.osgi.service.metatype.MetaTypeInformation;
+import org.osgi.service.metatype.MetaTypeService;
+import org.osgi.service.metatype.ObjectClassDefinition;
+import org.osgi.util.tracker.ServiceTracker;
+
+/**
+ * The <code>ConfigManagerBase</code> TODO
+ * 
+ */
+abstract class ConfigManagerBase extends BaseManagementPlugin {
+
+    private ServiceTracker configurationAdmin;
+
+    private ServiceTracker metaTypeService;
+
+    public void setBundleContext(BundleContext bundleContext) {
+        super.setBundleContext(bundleContext);
+        
+        configurationAdmin = new ServiceTracker(bundleContext, ConfigurationAdmin.class.getName(), null);
+        configurationAdmin.open();
+        metaTypeService = new ServiceTracker(bundleContext, MetaTypeService.class.getName(), null);
+        metaTypeService.open();
+    }
+    
+    public void destroy() {
+        if (configurationAdmin != null) {
+            configurationAdmin.close();
+        }
+        if (metaTypeService != null) {
+            metaTypeService.close();
+        }
+    }
+    
+    protected ConfigurationAdmin getConfigurationAdmin() {
+        return (ConfigurationAdmin) configurationAdmin.getService();
+    }
+
+    protected MetaTypeService getMetaTypeService() {
+        return (MetaTypeService) metaTypeService.getService();
+    }
+
+    protected Map<String, Bundle> getMetadataPids() {
+        Map<String, Bundle> pids = new HashMap<String, Bundle>();
+        MetaTypeService mts = this.getMetaTypeService();
+        if (mts != null) {
+            Bundle[] bundles = this.getBundleContext().getBundles();
+            for (int i = 0; i < bundles.length; i++) {
+                MetaTypeInformation mti = mts.getMetaTypeInformation(bundles[i]);
+                if (mti != null) {
+                    String[] pidList = mti.getPids();
+                    for (int j = 0; pidList != null && j < pidList.length; j++) {
+                        pids.put(pidList[j], bundles[i]);
+                    }
+                }
+            }
+        }
+        return pids;
+    }
+
+    protected ObjectClassDefinition getObjectClassDefinition(
+            Configuration config, String locale) {
+
+        // if the configuration is not bound, search in the bundles
+        if (config.getBundleLocation() == null) {
+            ObjectClassDefinition ocd = this.getObjectClassDefinition(
+                config.getPid(), locale);
+            if (ocd != null) {
+                return ocd;
+            }
+
+            // if none, check whether there might be one for the factory PID
+            if (config.getFactoryPid() != null) {
+                return this.getObjectClassDefinition(config.getFactoryPid(),
+                    locale);
+            }
+        }
+
+        MetaTypeService mts = this.getMetaTypeService();
+        if (mts != null) {
+            Bundle bundle = this.getBundle(config.getBundleLocation());
+            if (bundle != null) {
+                MetaTypeInformation mti = mts.getMetaTypeInformation(bundle);
+                if (mti != null) {
+                    // try OCD by PID first
+                    ObjectClassDefinition ocd = mti.getObjectClassDefinition(
+                        config.getPid(), locale);
+                    if (ocd != null) {
+                        return ocd;
+                    }
+
+                    // if none, check whether there might be one for the factory
+                    // PID
+                    if (config.getFactoryPid() != null) {
+                        return mti.getObjectClassDefinition(
+                            config.getFactoryPid(), locale);
+                    }
+                }
+            }
+        }
+
+        // fallback to nothing found
+        return null;
+    }
+
+    protected ObjectClassDefinition getObjectClassDefinition(Bundle bundle,
+            String pid, String locale) {
+        if (bundle != null) {
+            MetaTypeService mts = this.getMetaTypeService();
+            if (mts != null) {
+                MetaTypeInformation mti = mts.getMetaTypeInformation(bundle);
+                if (mti != null) {
+                    return mti.getObjectClassDefinition(pid, locale);
+                }
+            }
+        }
+
+        // fallback to nothing found
+        return null;
+    }
+
+    protected ObjectClassDefinition getObjectClassDefinition(String pid,
+            String locale) {
+        Bundle[] bundles = this.getBundleContext().getBundles();
+        for (int i = 0; i < bundles.length; i++) {
+            try {
+                ObjectClassDefinition ocd = this.getObjectClassDefinition(
+                    bundles[i], pid, locale);
+                if (ocd != null) {
+                    return ocd;
+                }
+            } catch (IllegalArgumentException iae) {
+                // don't care
+            }
+        }
+        return null;
+    }
+
+    protected Map<String, AttributeDefinition> getAttributeDefinitionMap(
+            Configuration config, String locale) {
+        ObjectClassDefinition ocd = this.getObjectClassDefinition(config,
+            locale);
+        if (ocd != null) {
+            AttributeDefinition[] ad = ocd.getAttributeDefinitions(ObjectClassDefinition.ALL);
+            if (ad != null) {
+                Map<String, AttributeDefinition> adMap = new HashMap<String, AttributeDefinition>();
+                for (int i = 0; i < ad.length; i++) {
+                    adMap.put(ad[i].getID(), ad[i]);
+                }
+                return adMap;
+            }
+        }
+
+        // fallback to nothing found
+        return null;
+    }
+
+    protected Bundle getBundle(String bundleLocation) {
+        if (bundleLocation == null) {
+            return null;
+        }
+
+        Bundle[] bundles = this.getBundleContext().getBundles();
+        for (int i = 0; i < bundles.length; i++) {
+            if (bundleLocation.equals(bundles[i].getLocation())) {
+                return bundles[i];
+            }
+        }
+
+        return null;
+    }
+
+    protected Locale getLocale(HttpServletRequest request) {
+        try {
+            return request.getLocale();
+        } catch (Throwable t) {
+            // expected in standard OSGi Servlet 2.1 environments
+            // fallback to using the default locale
+            return Locale.getDefault();
+        }
+    }
+
+}
diff --git a/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/core/AjaxBundleDetailsAction.java b/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/core/AjaxBundleDetailsAction.java
new file mode 100644
index 0000000..e5cc104
--- /dev/null
+++ b/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/core/AjaxBundleDetailsAction.java
@@ -0,0 +1,478 @@
+/*
+ * 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.sling.osgi.console.web.internal.core;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.StringTokenizer;
+import java.util.TreeMap;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+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.sling.commons.json.JSONArray;
+import org.apache.sling.commons.json.JSONException;
+import org.apache.sling.commons.json.JSONObject;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+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 AjaxBundleDetailsAction extends BundleAction {
+
+    public static final String NAME = "ajaxBundleDetails";
+
+    // 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;
+
+    @Override
+    public void setBundleContext(BundleContext bundleContext) {
+        super.setBundleContext(bundleContext);
+
+        // bootdelegation property parsing from Apache Felix R4SearchPolicyCore
+        String bootDelegation = bundleContext.getProperty(Constants.FRAMEWORK_BOOTDELEGATION);
+        bootDelegation = (bootDelegation == null) ? "java.*" : bootDelegation
+            + ",java.*";
+        StringTokenizer st = new StringTokenizer(bootDelegation, " ,");
+        bootPkgs = new String[st.countTokens()];
+        bootPkgWildcards = new boolean[bootPkgs.length];
+        for (int i = 0; i < bootPkgs.length; i++) {
+            bootDelegation = st.nextToken();
+            if (bootDelegation.endsWith("*")) {
+                bootPkgWildcards[i] = true;
+                bootDelegation = bootDelegation.substring(0,
+                    bootDelegation.length() - 1);
+            }
+            bootPkgs[i] = bootDelegation;
+        }
+    }
+
+    public String getName() {
+        return NAME;
+    }
+
+    public String getLabel() {
+        return NAME;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.apache.sling.manager.web.internal.Action#performAction(javax.servlet.http.HttpServletRequest)
+     */
+    public boolean performAction(HttpServletRequest request,
+            HttpServletResponse response) throws IOException {
+        JSONObject result = null;
+        try {
+            long bundleId = getBundleId(request);
+            Bundle bundle = getBundleContext().getBundle(bundleId);
+            if (bundle != null) {
+                @SuppressWarnings("unchecked")
+                Dictionary<String, String> headers = bundle.getHeaders();
+
+                JSONArray props = new JSONArray();
+                keyVal(props, "Symbolic Name", bundle.getSymbolicName());
+                keyVal(props, "Version", headers.get(Constants.BUNDLE_VERSION));
+                keyVal(props, "Location", bundle.getLocation());
+                keyVal(props, "Last Modification", new Date(
+                    bundle.getLastModified()));
+
+                keyVal(props, "Vendor", headers.get(Constants.BUNDLE_VENDOR));
+                keyVal(props, "Copyright",
+                    headers.get(Constants.BUNDLE_COPYRIGHT));
+                keyVal(props, "Description",
+                    headers.get(Constants.BUNDLE_DESCRIPTION));
+
+                keyVal(props, "Start Level", getStartLevel(bundle));
+
+                if (bundle.getState() == Bundle.INSTALLED) {
+                    listImportExportsUnresolved(props, bundle);
+                } else {
+                    listImportExport(props, bundle);
+                }
+
+                listServices(props, bundle);
+
+                result = new JSONObject();
+                result.put(BundleListRender.BUNDLE_ID, bundleId);
+                result.put("props", props);
+            }
+        } catch (Exception exception) {
+            // create an empty result on problems
+            result = new JSONObject();
+        }
+
+        // send the result
+        response.setContentType("text/javascript");
+        response.getWriter().print(result.toString());
+
+        return false;
+    }
+
+    private Integer getStartLevel(Bundle bundle) {
+        StartLevel sl = getStartLevel();
+        return (sl != null) ? sl.getBundleStartLevel(bundle) : null;
+    }
+
+    private void listImportExport(JSONArray props, Bundle bundle) {
+        PackageAdmin packageAdmin = getPackageAdmin();
+        if (packageAdmin == null) {
+            return;
+        }
+
+        Map<String, Bundle> usingBundles = new TreeMap<String, Bundle>();
+        
+        ExportedPackage[] exports = packageAdmin.getExportedPackages(bundle);
+        if (exports != null && exports.length > 0) {
+            // do alphabetical sort
+            Arrays.sort(exports, new Comparator<ExportedPackage>() {
+                public int compare(ExportedPackage p1, ExportedPackage p2) {
+                    return p1.getName().compareTo(p2.getName());
+                }
+            });
+
+            StringBuffer val = new StringBuffer();
+            for (ExportedPackage export : exports) {
+                printExport(val, export.getName(), export.getVersion());
+                Bundle[] ubList = export.getImportingBundles();
+                if (ubList != null) {
+                    for (Bundle ub : ubList) {
+                        usingBundles.put(ub.getSymbolicName(), ub);
+                    }
+                }
+            }
+            keyVal(props, "Exported Packages", val.toString());
+        } else {
+            keyVal(props, "Exported Packages", "None");
+        }
+
+        exports = packageAdmin.getExportedPackages((Bundle) null);
+        if (exports != null && exports.length > 0) {
+            // collect import packages first
+            final List<ExportedPackage> imports = new ArrayList<ExportedPackage>();
+            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
+            StringBuffer val = new StringBuffer();
+            if (imports.size() > 0) {
+                final ExportedPackage[] packages = imports.toArray(new ExportedPackage[imports.size()]);
+                Arrays.sort(packages, new Comparator<ExportedPackage>() {
+                    public int compare(ExportedPackage p1, ExportedPackage p2) {
+                        return p1.getName().compareTo(p2.getName());
+                    }
+                });
+                // and finally print out
+                for (ExportedPackage ep : packages) {
+                    printImport(val, ep.getName(), ep.getVersion(), ep);
+                }
+            } else {
+                // add description if there are no imports
+                val.append("None");
+            }
+
+            keyVal(props, "Imported Packages", val.toString());
+        }
+        
+        if (!usingBundles.isEmpty()) {
+            StringBuffer val = new StringBuffer();
+            for (Bundle usingBundle : usingBundles.values()) {
+                val.append(getBundleDescriptor(usingBundle));
+                val.append("<br />");
+            }
+            keyVal(props, "Importing Bundles", val.toString());
+        }
+    }
+
+    private void listImportExportsUnresolved(JSONArray props, Bundle bundle) {
+        Dictionary<?, ?> dict = bundle.getHeaders();
+
+        String target = (String) dict.get(Constants.EXPORT_PACKAGE);
+        if (target != null) {
+            R4Package[] pkgs = R4Package.parseImportOrExportHeader(target);
+            if (pkgs != null && pkgs.length > 0) {
+                // do alphabetical sort
+                Arrays.sort(pkgs, new Comparator<R4Package>() {
+                    public int compare(R4Package p1, R4Package p2) {
+                        return p1.getName().compareTo(p2.getName());
+                    }
+                });
+
+                StringBuffer val = new StringBuffer();
+                for (R4Package pkg : pkgs) {
+                    R4Export export = new R4Export(pkg);
+
+                    printExport(val, export.getName(), export.getVersion());
+                }
+                keyVal(props, "Exported Packages", val.toString());
+            } else {
+                keyVal(props, "Exported Packages", "None");
+            }
+        }
+
+        target = (String) dict.get(Constants.IMPORT_PACKAGE);
+        if (target != null) {
+            R4Package[] pkgs = R4Package.parseImportOrExportHeader(target);
+            if (pkgs != null && pkgs.length > 0) {
+                Map<String, R4Import> imports = new TreeMap<String, R4Import>();
+                for (R4Package pkg : pkgs) {
+                    imports.put(pkg.getName(), new R4Import(pkg));
+                }
+
+                // collect import packages first
+                final Map<String, ExportedPackage> candidates = new HashMap<String, ExportedPackage>();
+                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 = imports.get(ep.getName());
+                            if (imp != null && imp.isSatisfied(toR4Export(ep))) {
+                                candidates.put(ep.getName(), ep);
+                            }
+                        }
+                    }
+                }
+
+                // now sort
+                StringBuffer val = new StringBuffer();
+                if (imports.size() > 0) {
+                    for (R4Import r4Import : imports.values()) {
+                        ExportedPackage ep = 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;
+                            }
+                        }
+
+                        printImport(val, r4Import.getName(),
+                            r4Import.getVersion(), ep);
+                    }
+                } else {
+                    // add description if there are no imports
+                    val.append("None");
+                }
+
+                keyVal(props, "Imported Packages", val.toString());
+            }
+        }
+    }
+
+    private void listServices(JSONArray props, Bundle bundle) {
+        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);
+
+            StringBuffer val = new StringBuffer();
+
+            appendProperty(val, refs[i], Constants.OBJECTCLASS, "Types");
+            appendProperty(val, refs[i], "sling.context", "Sling Context");
+            appendProperty(val, refs[i], Constants.SERVICE_PID, "PID");
+            appendProperty(val, refs[i], ConfigurationAdmin.SERVICE_FACTORYPID,
+                "Factory PID");
+            appendProperty(val, refs[i], ComponentConstants.COMPONENT_NAME,
+                "Component Name");
+            appendProperty(val, refs[i], ComponentConstants.COMPONENT_ID,
+                "Component ID");
+            appendProperty(val, refs[i], ComponentConstants.COMPONENT_FACTORY,
+                "Component Factory");
+            appendProperty(val, refs[i], Constants.SERVICE_DESCRIPTION,
+                "Description");
+            appendProperty(val, refs[i], Constants.SERVICE_VENDOR, "Vendor");
+
+            keyVal(props, key, val.toString());
+        }
+    }
+
+    private void appendProperty(StringBuffer dest, ServiceReference ref,
+            String name, String label) {
+        Object value = ref.getProperty(name);
+        if (value instanceof Object[]) {
+            Object[] values = (Object[]) value;
+            dest.append(label).append(": ");
+            for (int j = 0; j < values.length; j++) {
+                if (j > 0) dest.append(", ");
+                dest.append(values[j]);
+            }
+            dest.append("<br />"); // assume HTML use of result
+        } else if (value != null) {
+            dest.append(label).append(": ").append(value).append("<br />");
+        }
+    }
+
+    private void keyVal(JSONArray props, String key, Object value) {
+        if (key != null && value != null) {
+            try {
+                JSONObject obj = new JSONObject();
+                obj.put("key", key);
+                obj.put("value", value);
+                props.put(obj);
+            } catch (JSONException je) {
+                // don't care
+            }
+        }
+    }
+
+    private void printExport(StringBuffer val, String name, Version version) {
+        boolean bootDel = isBootDelegated(name);
+        if (bootDel) {
+            val.append("<span style=\"color: red\">!! ");
+        }
+
+        val.append(name);
+        val.append(",version=");
+        val.append(version);
+
+        if (bootDel) {
+            val.append(" -- Overwritten by Boot Delegation</span>");
+        }
+
+        val.append("<br />");
+    }
+
+    private void printImport(StringBuffer val, String name, Version version,
+            ExportedPackage export) {
+        boolean bootDel = isBootDelegated(name);
+        if (bootDel || export == null) {
+            val.append("<span style=\"color: red\">!! ");
+        }
+
+        val.append(name);
+        val.append(",version=").append(version);
+        val.append(" from ");
+
+        if (export != null) {
+            val.append(getBundleDescriptor(export.getExportingBundle()));
+
+            if (bootDel) {
+                val.append(" -- Overwritten by Boot Delegation</span>");
+            }
+        } else {
+            val.append(" -- Cannot be resolved");
+            if (bootDel) {
+                val.append(" and overwritten by Boot Delegation");
+            }
+            val.append("</span>");
+        }
+
+        val.append("<br />");
+    }
+
+    // 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 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 String getBundleDescriptor(Bundle bundle) {
+        StringBuffer val = new StringBuffer();
+        if (bundle.getSymbolicName() != null) {
+            // list the bundle name if not null
+            val.append(bundle.getSymbolicName());
+            val.append(" (").append(bundle.getBundleId());
+            val.append(")");
+        } else if (bundle.getLocation() != null) {
+            // otherwise try the location
+            val.append(bundle.getLocation());
+            val.append(" (").append(bundle.getBundleId());
+            val.append(")");
+        } else {
+            // fallback to just the bundle id
+            // only append the bundle
+            val.append(bundle.getBundleId());
+        }
+        return val.toString();
+    }
+}
diff --git a/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/core/BundleAction.java b/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/core/BundleAction.java
new file mode 100644
index 0000000..98130f6
--- /dev/null
+++ b/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/core/BundleAction.java
@@ -0,0 +1,40 @@
+/*
+ * 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.sling.osgi.console.web.internal.core;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.sling.osgi.console.web.Action;
+import org.apache.sling.osgi.console.web.internal.BaseManagementPlugin;
+
+abstract class BundleAction extends BaseManagementPlugin implements Action {
+
+    protected long getBundleId(HttpServletRequest request) {
+        String bundleIdPar = request.getParameter(BundleListRender.BUNDLE_ID);
+        if (bundleIdPar != null) {
+            try {
+                return Long.parseLong(bundleIdPar);
+            } catch (NumberFormatException nfe) {
+                // TODO: log
+            }
+        }
+
+        // no bundleId or wrong format
+        return -1;
+    }
+
+}
diff --git a/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/core/BundleListRender.java b/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/core/BundleListRender.java
new file mode 100644
index 0000000..590ce74
--- /dev/null
+++ b/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/core/BundleListRender.java
@@ -0,0 +1,394 @@
+/*
+ * 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.sling.osgi.console.web.internal.core;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.Comparator;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.sling.osgi.console.web.Render;
+import org.apache.sling.osgi.console.web.internal.BaseManagementPlugin;
+import org.apache.sling.osgi.console.web.internal.Util;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.Version;
+import org.osgi.util.tracker.ServiceTracker;
+
+/**
+ * The <code>BundleListRender</code> TODO
+ */
+public class BundleListRender extends BaseManagementPlugin implements Render {
+
+    public static final String NAME = "list";
+
+    public static final String LABEL = "Bundles";
+
+    public static final String BUNDLE_ID = "bundleId";
+
+    private static final String INSTALLER_SERVICE_NAME = "org.apache.sling.osgi.assembly.installer.InstallerService";
+
+    // track the optional installer service manually
+    private ServiceTracker installerService;
+
+    public void setBundleContext(BundleContext bundleContext) {
+        super.setBundleContext(bundleContext);
+
+        installerService = new ServiceTracker(bundleContext,
+            INSTALLER_SERVICE_NAME, null);
+        installerService.open();
+    }
+
+    // protected void deactivate(ComponentContext context) {
+    // if (installerService != null) {
+    // installerService.close();
+    // installerService = null;
+    // }
+    // }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see org.apache.sling.manager.web.internal.Render#getName()
+     */
+    public String getName() {
+        return NAME;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see org.apache.sling.manager.web.internal.Render#getLabel()
+     */
+    public String getLabel() {
+        return LABEL;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see org.apache.sling.manager.web.internal.internal.Render#render(javax.servlet.http.HttpServletRequest,
+     *      javax.servlet.http.HttpServletResponse)
+     */
+    public void render(HttpServletRequest request, HttpServletResponse response)
+            throws IOException {
+
+        PrintWriter pw = response.getWriter();
+
+        this.header(pw);
+
+        this.installForm(pw);
+        pw.println("<tr class='content'>");
+        pw.println("<td colspan='7' class='content'>&nbsp;</th>");
+        pw.println("</tr>");
+
+        this.tableHeader(pw);
+
+        Bundle[] bundles = this.getBundles();
+        if (bundles == null || bundles.length == 0) {
+            pw.println("<tr class='content'>");
+            pw.println("<td class='content' colspan='6'>No " + this.getLabel()
+                + " installed currently</td>");
+            pw.println("</tr>");
+        } else {
+
+            sort(bundles);
+
+            long previousBundle = -1;
+            for (int i = 0; i < bundles.length; i++) {
+
+                if (previousBundle >= 0) {
+                    // prepare for injected table information row
+                    pw.println("<tr id='bundle" + previousBundle + "'></tr>");
+                }
+
+                this.bundle(pw, bundles[i]);
+
+                previousBundle = bundles[i].getBundleId();
+            }
+
+            if (previousBundle >= 0) {
+                // prepare for injected table information row
+                pw.println("<tr id='bundle" + previousBundle + "'></tr>");
+            }
+        }
+
+        pw.println("<tr class='content'>");
+        pw.println("<td colspan='7' class='content'>&nbsp;</th>");
+        pw.println("</tr>");
+
+        this.installForm(pw);
+
+        this.footer(pw);
+    }
+
+    protected Bundle[] getBundles() {
+        return getBundleContext().getBundles();
+    }
+
+    private void header(PrintWriter pw) {
+        Util.startScript(pw);
+        pw.println("function showDetails(bundleId) {");
+        pw.println("    var span = document.getElementById('bundle' + bundleId);");
+        pw.println("    if (!span) {");
+        pw.println("        return;");
+        pw.println("    }");
+        pw.println("    if (span.innerHTML) {");
+        pw.println("        span.innerHTML = '';");
+        pw.println("        return;");
+        pw.println("    }");
+        pw.println("    var parm = '?" + Util.PARAM_ACTION + "="
+            + AjaxBundleDetailsAction.NAME + "&" + BUNDLE_ID + "=' + bundleId;");
+        pw.println("    sendRequest('GET', parm, displayBundleDetails);");
+        pw.println("}");
+        pw.println("function displayBundleDetails(obj) {");
+        pw.println("    var span = document.getElementById('bundle' + obj."
+            + BUNDLE_ID + ");");
+        pw.println("    if (!span) {");
+        pw.println("        return;");
+        pw.println("    }");
+        pw.println("    var innerHtml = '<td class=\"content\">&nbsp;</td><td class=\"content\" colspan=\"6\"><table broder=\"0\">';");
+        pw.println("    var props = obj.props;");
+        pw.println("    for (var i=0; i < props.length; i++) {");
+        pw.println("        innerHtml += '<tr><td valign=\"top\" noWrap>' + props[i].key + '</td><td valign=\"top\">' + props[i].value + '</td></tr>';");
+        pw.println("    }");
+        pw.println("    innerHtml += '</table></td>';");
+        pw.println("    span.innerHTML = innerHtml;");
+        pw.println("}");
+        Util.endScript(pw);
+
+        pw.println("<table class='content' cellpadding='0' cellspacing='0' width='100%'>");
+    }
+
+    private void tableHeader(PrintWriter pw) {
+        // pw.println("<tr class='content'>");
+        // pw.println("<th class='content container' colspan='7'>Installed " +
+        // getLabel() + "</th>");
+        // pw.println("</tr>");
+
+        pw.println("<tr class='content'>");
+        pw.println("<th class='content'>ID</th>");
+        pw.println("<th class='content' width='100%'>Name</th>");
+        pw.println("<th class='content'>Status</th>");
+        pw.println("<th class='content' colspan='4'>Actions</th>");
+        pw.println("</tr>");
+    }
+
+    private void footer(PrintWriter pw) {
+        pw.println("</table>");
+    }
+
+    private void bundle(PrintWriter pw, Bundle bundle) {
+        String name = getName(bundle);
+
+        pw.println("<tr>");
+        pw.println("<td class='content right'>" + bundle.getBundleId()
+            + "</td>");
+        pw.println("<td class='content'><a href='javascript:showDetails("
+            + bundle.getBundleId() + ")'>" + name + "</a></td>");
+        pw.println("<td class='content center'>"
+            + this.toStateString(bundle.getState()) + "</td>");
+
+        // no buttons for system bundle
+        if (bundle.getBundleId() == 0) {
+            pw.println("<td class='content' colspan='4'>&nbsp;</td>");
+        } else {
+            boolean enabled = bundle.getState() == Bundle.INSTALLED
+                || bundle.getState() == Bundle.RESOLVED;
+            this.actionForm(pw, enabled, bundle.getBundleId(),
+                StartAction.NAME, StartAction.LABEL);
+
+            enabled = bundle.getState() == Bundle.ACTIVE;
+            this.actionForm(pw, enabled, bundle.getBundleId(), StopAction.NAME,
+                StopAction.LABEL);
+
+            enabled = bundle.getState() != Bundle.UNINSTALLED
+                && this.hasUpdates(bundle);
+            this.actionForm(pw, enabled, bundle.getBundleId(),
+                UpdateAction.NAME, UpdateAction.LABEL);
+
+            enabled = bundle.getState() == Bundle.INSTALLED
+                || bundle.getState() == Bundle.RESOLVED
+                || bundle.getState() == Bundle.ACTIVE;
+            this.actionForm(pw, enabled, bundle.getBundleId(),
+                UninstallAction.NAME, UninstallAction.LABEL);
+        }
+
+        pw.println("</tr>");
+    }
+
+    private void actionForm(PrintWriter pw, boolean enabled, long bundleId,
+            String action, String actionLabel) {
+        pw.println("<form name='form" + bundleId + "' method='post'>");
+        pw.println("<td class='content' align='right'>");
+        pw.println("<input type='hidden' name='" + Util.PARAM_ACTION
+            + "' value='" + action + "' />");
+        pw.println("<input type='hidden' name='" + BUNDLE_ID + "' value='"
+            + bundleId + "' />");
+        pw.println("<input class='submit' type='submit' value='" + actionLabel
+            + "'" + (enabled ? "" : "disabled") + " />");
+        pw.println("</td>");
+        pw.println("</form>");
+    }
+
+    private void installForm(PrintWriter pw) {
+        int startLevel = getStartLevel().getInitialBundleStartLevel();
+
+        pw.println("<form method='post' enctype='multipart/form-data'>");
+        pw.println("<tr class='content'>");
+        pw.println("<td class='content'>&nbsp;</td>");
+        pw.println("<td class='content'>");
+        pw.println("<input type='hidden' name='" + Util.PARAM_ACTION
+            + "' value='" + InstallAction.NAME + "' />");
+        pw.println("<input class='input' type='file' name='"
+            + InstallAction.FIELD_BUNDLEFILE + "'>");
+        pw.println(" - Start <input class='checkradio' type='checkbox' name='"
+            + InstallAction.FIELD_START + "' value='start'>");
+        pw.println(" - Start Level <input class='input' type='input' name='"
+            + InstallAction.FIELD_STARTLEVEL + "' value='" + startLevel
+            + "' width='4'>");
+        pw.println("</td>");
+        pw.println("<td class='content' align='right' colspan='5' noWrap>");
+        pw.println("<input class='submit' style='width:auto' type='submit' value='"
+            + InstallAction.LABEL + "'>");
+        pw.println("&nbsp;");
+        pw.println("<input class='submit' style='width:auto' type='submit' value='"
+            + RefreshPackagesAction.LABEL
+            + "' onClick='this.form[\""
+            + Util.PARAM_ACTION
+            + "\"].value=\""
+            + RefreshPackagesAction.NAME
+            + "\"; return true;'>");
+        pw.println("</td>");
+        pw.println("</tr>");
+        pw.println("</form>");
+    }
+
+    private String toStateString(int bundleState) {
+        switch (bundleState) {
+            case Bundle.INSTALLED:
+                return "Installed";
+            case Bundle.RESOLVED:
+                return "Resolved";
+            case Bundle.STARTING:
+                return "Starting";
+            case Bundle.ACTIVE:
+                return "Active";
+            case Bundle.STOPPING:
+                return "Stopping";
+            case Bundle.UNINSTALLED:
+                return "Uninstalled";
+            default:
+                return "Unknown: " + bundleState;
+        }
+    }
+
+    private boolean hasUpdates(Bundle bundle) {
+
+        // no updates if there is no installer service
+        Object isObject = installerService.getService();
+        if (isObject == null) {
+            return false;
+        }
+
+        // don't care for bundles with no symbolic name
+        if (bundle.getSymbolicName() == null) {
+            return false;
+        }
+/*
+        Version bundleVersion = Version.parseVersion((String) bundle.getHeaders().get(
+            Constants.BUNDLE_VERSION));
+
+        BundleRepositoryAdmin repoAdmin = ((InstallerService) isObject).getBundleRepositoryAdmin();
+        for (Iterator<Resource> ri = repoAdmin.getResources(); ri.hasNext();) {
+            Resource res = ri.next();
+            if (bundle.getSymbolicName().equals(res.getSymbolicName())) {
+                if (res.getVersion().compareTo(bundleVersion) > 0) {
+                    return true;
+                }
+            }
+        }
+*/
+
+        return false;
+    }
+
+    private void sort(Bundle[] bundles) {
+        Arrays.sort(bundles, BUNDLE_NAME_COMPARATOR);
+    }
+
+    private static String getName(Bundle bundle) {
+        String name = (String) bundle.getHeaders().get(Constants.BUNDLE_NAME);
+        if (name == null || name.length() == 0) {
+            name = bundle.getSymbolicName();
+            if (name == null) {
+                name = bundle.getLocation();
+                if (name == null) {
+                    name = String.valueOf(bundle.getBundleId());
+                }
+            }
+        }
+        return name;
+    }
+
+    // ---------- inner classes ------------------------------------------------
+
+    private static final Comparator<Bundle> BUNDLE_NAME_COMPARATOR = new Comparator<Bundle>() {
+        public int compare(Bundle b1, Bundle b2) {
+
+            // the same bundles
+            if (b1 == b2 || b1.getBundleId() == b2.getBundleId()) {
+                return 0;
+            }
+
+            // special case for system bundle, which always is first
+            if (b1.getBundleId() == 0) {
+                return -1;
+            } else if (b2.getBundleId() == 0) {
+                return 1;
+            }
+
+            // compare the symbolic names
+            int snComp = getName(b1).compareToIgnoreCase(getName(b2));
+            if (snComp != 0) {
+                return snComp;
+            }
+
+            // same names, compare versions
+            Version v1 = Version.parseVersion((String) b1.getHeaders().get(
+                Constants.BUNDLE_VERSION));
+            Version v2 = Version.parseVersion((String) b2.getHeaders().get(
+                Constants.BUNDLE_VERSION));
+            int vComp = v1.compareTo(v2);
+            if (vComp != 0) {
+                return vComp;
+            }
+
+            // same version ? Not really, but then, we compare by bundle id
+            if (b1.getBundleId() < b2.getBundleId()) {
+                return -1;
+            }
+
+            // b1 id must be > b2 id because equality is already checked
+            return 1;
+        }
+    };
+}
diff --git a/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/core/InstallAction.java b/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/core/InstallAction.java
new file mode 100644
index 0000000..15b2ada
--- /dev/null
+++ b/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/core/InstallAction.java
@@ -0,0 +1,296 @@
+/*
+ * 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.sling.osgi.console.web.internal.core;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Map;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.fileupload.FileItem;
+import org.apache.sling.osgi.console.web.internal.Util;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.Constants;
+import org.osgi.service.log.LogService;
+import org.osgi.service.startlevel.StartLevel;
+
+/**
+ * The <code>InstallAction</code> TODO
+ */
+public class InstallAction extends BundleAction {
+
+    public static final String NAME = "install";
+
+    public static final String LABEL = "Install or Update";
+
+    public static final String FIELD_STARTLEVEL = "bundlestartlevel";
+
+    public static final String FIELD_START = "bundlestart";
+
+    public static final String FIELD_BUNDLEFILE = "bundlefile";
+
+    public String getName() {
+        return NAME;
+    }
+
+    public String getLabel() {
+        return LABEL;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.apache.sling.manager.web.internal.Action#performAction(javax.servlet.http.HttpServletRequest)
+     */
+    public boolean performAction(HttpServletRequest request,
+            HttpServletResponse response) {
+
+        // get the uploaded data
+        @SuppressWarnings("unchecked")
+        Map<String, FileItem[]> params = (Map<String, FileItem[]>) request.getAttribute(Util.ATTR_FILEUPLOAD);
+        if (params == null) {
+            return true;
+        }
+
+        FileItem startItem = this.getFileItem(params, FIELD_START, true);
+        FileItem startLevelItem = this.getFileItem(params, FIELD_STARTLEVEL,
+            true);
+        FileItem bundleItem = this.getFileItem(params, FIELD_BUNDLEFILE, false);
+
+        // don't care any more if not bundle item
+        if (bundleItem == null || bundleItem.getSize() <= 0) {
+            return true;
+        }
+
+        // default values
+        boolean start = startItem != null; // don't care for the value, as long
+        // it exists
+        int startLevel = -1;
+        String bundleLocation = "inputstream:";
+
+        // convert the start level value
+        if (startLevelItem != null) {
+            try {
+                startLevel = Integer.parseInt(startLevelItem.getString());
+            } catch (NumberFormatException nfe) {
+                getLog().log(
+                    LogService.LOG_INFO,
+                    "Cannot parse start level parameter " + startLevelItem
+                        + " to a number, not setting start level");
+            }
+        }
+
+        // write the bundle data to a temporary file to ease processing
+        File tmpFile = null;
+        try {
+            // copy the data to a file for better processing
+            tmpFile = File.createTempFile("install", ".tmp");
+            bundleItem.write(tmpFile);
+        } catch (Exception e) {
+            getLog().log(LogService.LOG_ERROR,
+                "Problem accessing uploaded bundle file", e);
+
+            // remove the tmporary file
+            if (tmpFile != null) {
+                tmpFile.delete();
+                tmpFile = null;
+            }
+        }
+
+        // install or update the bundle now
+        if (tmpFile != null) {
+            bundleLocation = "inputstream:" + bundleItem.getName();
+            installBundle(bundleLocation, tmpFile, startLevel, start);
+        }
+
+        return true;
+    }
+
+    private FileItem getFileItem(Map<String, FileItem[]> params, String name,
+            boolean isFormField) {
+        FileItem[] items = params.get(name);
+        if (items != null) {
+            for (int i = 0; i < items.length; i++) {
+                if (items[i].isFormField() == isFormField) {
+                    return items[i];
+                }
+            }
+        }
+
+        // nothing found, fail
+        return null;
+    }
+
+    private void installBundle(String location, File bundleFile,
+            int startLevel, boolean start) {
+        if (bundleFile != null) {
+
+            // try to get the bundle name, fail if none
+            String symbolicName = this.getSymbolicName(bundleFile);
+            if (symbolicName == null) {
+                bundleFile.delete();
+                return;
+            }
+
+            // check for existing bundle first
+            Bundle updateBundle = null;
+            Bundle[] bundles = this.getBundleContext().getBundles();
+            for (int i = 0; i < bundles.length; i++) {
+                if ((bundles[i].getLocation() != null && bundles[i].getLocation().equals(
+                    location))
+                    || (bundles[i].getSymbolicName() != null && bundles[i].getSymbolicName().equals(
+                        symbolicName))) {
+                    updateBundle = bundles[i];
+                    break;
+                }
+            }
+
+            if (updateBundle != null) {
+
+                updateBackground(updateBundle, bundleFile);
+
+            } else {
+
+                installBackground(bundleFile, location, startLevel, start);
+
+            }
+        }
+    }
+
+    private String getSymbolicName(File bundleFile) {
+        JarFile jar = null;
+        try {
+            jar = new JarFile(bundleFile);
+            Manifest m = jar.getManifest();
+            if (m != null) {
+                return m.getMainAttributes().getValue(
+                    Constants.BUNDLE_SYMBOLICNAME);
+            }
+        } catch (IOException ioe) {
+            getLog().log(LogService.LOG_WARNING,
+                "Cannot extract symbolic name of bundle file " + bundleFile,
+                ioe);
+        } finally {
+            if (jar != null) {
+                try {
+                    jar.close();
+                } catch (IOException ioe) {
+                    // ignore
+                }
+            }
+        }
+
+        // fall back to "not found"
+        return null;
+    }
+
+    private void installBackground(final File bundleFile,
+            final String location, final int startlevel, final boolean doStart) {
+
+        Thread t = new InstallHelper(this, "Background Install " + bundleFile,
+            bundleFile) {
+
+            @Override
+            protected void doRun(InputStream bundleStream)
+                    throws BundleException {
+                Bundle bundle = getBundleContext().installBundle(location,
+                    bundleStream);
+
+                StartLevel sl = getStartLevel();
+                if (sl != null) {
+                    sl.setBundleStartLevel(bundle, startlevel);
+                }
+
+                if (doStart) {
+                    bundle.start();
+                }
+            }
+        };
+
+        t.start();
+    }
+
+    private void updateBackground(final Bundle bundle, final File bundleFile) {
+        Thread t = new InstallHelper(this, "Background Update"
+            + bundle.getSymbolicName() + " (" + bundle.getBundleId() + ")",
+            bundleFile) {
+
+            @Override
+            protected void doRun(InputStream bundleStream)
+                    throws BundleException {
+                bundle.update(bundleStream);
+            }
+        };
+
+        t.start();
+    }
+
+    private static abstract class InstallHelper extends Thread {
+
+        private final InstallAction installAction;
+
+        private final File bundleFile;
+
+        InstallHelper(InstallAction installAction, String name, File bundleFile) {
+            super(name);
+            setDaemon(true);
+
+            this.installAction = installAction;
+            this.bundleFile = bundleFile;
+        }
+
+        protected abstract void doRun(InputStream bundleStream)
+                throws BundleException;
+
+        public void run() {
+            // wait some time for the request to settle
+            try {
+                sleep(500L);
+            } catch (InterruptedException ie) {
+                // don't care
+            }
+
+            // now deploy the resolved bundles
+            InputStream bundleStream = null;
+            try {
+                bundleStream = new FileInputStream(bundleFile);
+                doRun(bundleStream);
+            } catch (IOException ioe) {
+                installAction.getLog().log(LogService.LOG_ERROR,
+                    "Cannot install or update bundle from " + bundleFile, ioe);
+            } catch (BundleException be) {
+                installAction.getLog().log(LogService.LOG_ERROR,
+                    "Cannot install or update bundle from " + bundleFile, be);
+            } finally {
+                if (bundleStream != null) {
+                    try {
+                        bundleStream.close();
+                    } catch (IOException ignore) {
+                    }
+                }
+                bundleFile.delete();
+            }
+        }
+    }
+}
diff --git a/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/core/RefreshPackagesAction.java b/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/core/RefreshPackagesAction.java
new file mode 100644
index 0000000..9fc4821
--- /dev/null
+++ b/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/core/RefreshPackagesAction.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.sling.osgi.console.web.internal.core;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * The <code>RefreshPackagesAction</code> TODO
+ */
+public class RefreshPackagesAction extends BundleAction {
+
+    public static final String NAME = "refreshPackages";
+
+    public static final String LABEL = "Refresh Packages";
+
+    public String getName() {
+        return NAME;
+    }
+
+    public String getLabel() {
+        return LABEL;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.apache.sling.manager.web.internal.Action#performAction(javax.servlet.http.HttpServletRequest,
+     *      javax.servlet.http.HttpServletResponse)
+     */
+    public boolean performAction(HttpServletRequest request,
+            HttpServletResponse response) {
+
+        getPackageAdmin().refreshPackages(null);
+
+        return true;
+    }
+
+}
diff --git a/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/core/SetStartLevelAction.java b/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/core/SetStartLevelAction.java
new file mode 100644
index 0000000..6a263f0
--- /dev/null
+++ b/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/core/SetStartLevelAction.java
@@ -0,0 +1,77 @@
+/*
+ * 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.sling.osgi.console.web.internal.core;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.sling.osgi.console.web.Action;
+import org.apache.sling.osgi.console.web.internal.BaseManagementPlugin;
+import org.osgi.service.startlevel.StartLevel;
+
+/**
+ * The <code>SetStartLevelAction</code> TODO
+ */
+public class SetStartLevelAction extends BaseManagementPlugin implements Action {
+
+    public static final String NAME = "setStartLevel";
+
+    public static final String LABEL = "Set Start Level";
+
+    public String getName() {
+        return NAME;
+    }
+
+    public String getLabel() {
+        return LABEL;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.apache.sling.manager.web.internal.Action#performAction(javax.servlet.http.HttpServletRequest)
+     */
+    public boolean performAction(HttpServletRequest request,
+            HttpServletResponse response) {
+
+        StartLevel sl = getStartLevel();
+        if (sl != null) {
+            int bundleSL = this.getParameterInt(request, "bundleStartLevel");
+            if (bundleSL > 0 && bundleSL != sl.getInitialBundleStartLevel()) {
+                sl.setInitialBundleStartLevel(bundleSL);
+            }
+
+            int systemSL = this.getParameterInt(request, "systemStartLevel");
+            if (systemSL > 0 && systemSL != sl.getStartLevel()) {
+                sl.setStartLevel(systemSL);
+            }
+        }
+
+        return true;
+    }
+
+    private int getParameterInt(HttpServletRequest request, String name) {
+        try {
+            return Integer.parseInt(request.getParameter(name));
+        } catch (NumberFormatException nfe) {
+            // don't care
+        }
+
+        return -1;
+    }
+
+}
diff --git a/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/core/StartAction.java b/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/core/StartAction.java
new file mode 100644
index 0000000..d365aa0
--- /dev/null
+++ b/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/core/StartAction.java
@@ -0,0 +1,56 @@
+/*
+ * 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.sling.osgi.console.web.internal.core;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleException;
+import org.osgi.service.log.LogService;
+
+public class StartAction extends BundleAction {
+
+    public static final String NAME = "start";
+    public static final String LABEL = "Start";
+
+    public String getName() {
+        return NAME;
+    }
+
+    public String getLabel() {
+        return LABEL;
+    }
+
+    public boolean performAction(HttpServletRequest request, HttpServletResponse response) {
+
+        long bundleId = this.getBundleId(request);
+        if (bundleId > 0) { // cannot start system bundle !!
+            Bundle bundle = this.getBundleContext().getBundle(bundleId);
+            if (bundle != null) {
+                try {
+                    bundle.start();
+                } catch (BundleException be) {
+                    getLog().log(LogService.LOG_ERROR, "Cannot start", be);
+                }
+
+            }
+        }
+        return true;
+    }
+}
+
diff --git a/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/core/StopAction.java b/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/core/StopAction.java
new file mode 100644
index 0000000..1f576b6
--- /dev/null
+++ b/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/core/StopAction.java
@@ -0,0 +1,60 @@
+/*
+ * 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.sling.osgi.console.web.internal.core;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleException;
+import org.osgi.service.log.LogService;
+
+/**
+ * The <code>StopAction</code> TODO
+ */
+public class StopAction extends BundleAction {
+
+    public static final String NAME = "stop";
+    public static final String LABEL = "Stop";
+
+    public String getName() {
+        return NAME;
+    }
+
+    public String getLabel() {
+        return LABEL;
+    }
+
+    public boolean performAction(HttpServletRequest request, HttpServletResponse response) {
+
+        long bundleId = this.getBundleId(request);
+        if (bundleId > 0) { // cannot stop system bundle !!
+            Bundle bundle = this.getBundleContext().getBundle(bundleId);
+            if (bundle != null) {
+                try {
+                    bundle.stop();
+                } catch (BundleException be) {
+                    getLog().log(LogService.LOG_ERROR, "Cannot stop", be);
+                }
+
+            }
+        }
+
+        return true;
+    }
+}
+
diff --git a/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/core/UninstallAction.java b/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/core/UninstallAction.java
new file mode 100644
index 0000000..8bfe801
--- /dev/null
+++ b/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/core/UninstallAction.java
@@ -0,0 +1,57 @@
+/*
+ * 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.sling.osgi.console.web.internal.core;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleException;
+import org.osgi.service.log.LogService;
+
+public class UninstallAction extends BundleAction {
+
+    public static final String NAME = "uninstall";
+    public static final String LABEL = "Uninstall";
+
+    public String getName() {
+        return NAME;
+    }
+
+    public String getLabel() {
+        return LABEL;
+    }
+
+    public boolean performAction(HttpServletRequest request, HttpServletResponse response) {
+
+        long bundleId = this.getBundleId(request);
+        if (bundleId > 0) { // cannot stop system bundle !!
+            Bundle bundle = this.getBundleContext().getBundle(bundleId);
+            if (bundle != null) {
+                try {
+                    bundle.uninstall();
+                } catch (BundleException be) {
+                    getLog().log(LogService.LOG_ERROR, "Cannot uninstall", be);
+                }
+
+            }
+        }
+
+        return true;
+    }
+}
+
diff --git a/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/core/UpdateAction.java b/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/core/UpdateAction.java
new file mode 100644
index 0000000..f0beb2a
--- /dev/null
+++ b/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/core/UpdateAction.java
@@ -0,0 +1,128 @@
+/*
+ * 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.sling.osgi.console.web.internal.core;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.log.LogService;
+import org.osgi.util.tracker.ServiceTracker;
+
+/**
+ * The <code>UpdateAction</code> TODO
+ */
+public class UpdateAction extends BundleAction {
+
+    public static final String NAME = "update";
+
+    public static final String LABEL = "Update";
+
+    private static final String INSTALLER_SERVICE_NAME = "org.apache.sling.osgi.assembly.installer.InstallerService";
+
+    // track the optional installer service manually
+    private ServiceTracker installerService;
+
+    public void setBundleContext(BundleContext bundleContext) {
+        super.setBundleContext(bundleContext);
+
+        installerService = new ServiceTracker(bundleContext,
+            INSTALLER_SERVICE_NAME, null);
+        installerService.open();
+    }
+
+    public String getName() {
+        return NAME;
+    }
+
+    public String getLabel() {
+        return LABEL;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see org.apache.sling.manager.web.internal.internal.Action#performAction(javax.servlet.http.HttpServletRequest)
+     */
+    public boolean performAction(HttpServletRequest request,
+            HttpServletResponse response) {
+
+        long bundleId = this.getBundleId(request);
+        if (bundleId > 0) { // cannot stop system bundle !!
+            Bundle bundle = this.getBundleContext().getBundle(bundleId);
+            if (bundle != null) {
+                try {
+                    this.updateFromRepo(bundle);
+                } catch (Throwable t) {
+                    getLog().log(LogService.LOG_ERROR, "Uncaught Problem", t);
+                }
+
+            }
+        }
+
+        return true;
+    }
+
+    private void updateFromRepo(final Bundle bundle) {
+/*
+        final InstallerService is = (InstallerService) installerService.getService();
+        if (is == null) {
+            return;
+        }
+
+        final String name = bundle.getSymbolicName();
+        final String version = (String) bundle.getHeaders().get(
+            Constants.BUNDLE_VERSION);
+
+        // the name is required, otherwise we can do nothing
+        if (name == null) {
+            return;
+        }
+
+        // TODO: Should be restrict to same major.micro ??
+
+        Thread t = new Thread("Background Update") {
+            public void run() {
+                // wait some time for the request to settle
+                try {
+                    sleep(500L);
+                } catch (InterruptedException ie) {
+                    // don't care
+                }
+
+                Installer installer = is.getInstaller();
+                installer.addBundle(name, new VersionRange(version), -1);
+                try {
+                    installer.install(false);
+                } catch (InstallerException ie) {
+                    Throwable cause = (ie.getCause() != null)
+                            ? ie.getCause()
+                            : ie;
+                    getLog().log(LogService.LOG_ERROR, "Cannot update", cause);
+                } finally {
+                    installer.dispose();
+                }
+            }
+        };
+
+        t.setDaemon(true); // make a daemon thread (detach from current thread)
+        t.start();
+        */
+    }
+
+}
diff --git a/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/misc/AssemblyListRender.java b/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/misc/AssemblyListRender.java
new file mode 100644
index 0000000..7b4b48d
--- /dev/null
+++ b/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/misc/AssemblyListRender.java
@@ -0,0 +1,48 @@
+/*
+ * 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.sling.osgi.console.web.internal.misc;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.sling.osgi.console.web.internal.core.BundleListRender;
+import org.osgi.framework.Bundle;
+
+public class AssemblyListRender extends BundleListRender {
+
+    public static final String NAME = "assemblyList";
+    public static final String LABEL = "Assemblies";
+
+    public String getName() {
+        return NAME;
+    }
+
+    public String getLabel() {
+        return LABEL;
+    }
+
+    protected Bundle[] getBundles() {
+        Bundle[] bundles = this.getBundleContext().getBundles();
+        List<Bundle> assList = new ArrayList<Bundle>();
+        for (int i=0; i < bundles.length; i++) {
+            if (bundles[i].getHeaders().get("Assembly-Bundles") != null) {
+                assList.add(bundles[i]);
+            }
+        }
+        return assList.toArray(new Bundle[assList.size()]);
+    }
+}
diff --git a/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/misc/ConfigurationRender.java b/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/misc/ConfigurationRender.java
new file mode 100644
index 0000000..df7e3c0
--- /dev/null
+++ b/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/misc/ConfigurationRender.java
@@ -0,0 +1,474 @@
+/*
+ * 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.sling.osgi.console.web.internal.misc;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Properties;
+import java.util.SortedMap;
+import java.util.SortedSet;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.sling.osgi.console.web.ConfigurationPrinter;
+import org.apache.sling.osgi.console.web.Render;
+import org.apache.sling.osgi.console.web.internal.BaseManagementPlugin;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.Constants;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.cm.Configuration;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.service.prefs.BackingStoreException;
+import org.osgi.service.prefs.Preferences;
+import org.osgi.service.prefs.PreferencesService;
+import org.osgi.util.tracker.ServiceTracker;
+
+public class ConfigurationRender extends BaseManagementPlugin implements Render {
+
+    public static final String NAME = "config";
+
+    public static final String LABEL = "Configuration Status";
+
+    private ServiceTracker cfgPrinterTracker;
+
+    private int cfgPrinterTrackerCount;
+
+    private SortedMap<String, ConfigurationPrinter> configurationPrinters = new TreeMap<String, ConfigurationPrinter>();
+
+    public String getName() {
+        return NAME;
+    }
+
+    public String getLabel() {
+        return LABEL;
+    }
+
+    public void render(HttpServletRequest request, HttpServletResponse response)
+            throws IOException {
+
+        PrintWriter pw = response.getWriter();
+
+        pw.println("<table class='content' cellpadding='0' cellspacing='0' width='100%'>");
+
+        pw.println("<tr class='content'>");
+        pw.println("<th class='content container'>Configuration Details</th>");
+        pw.println("</tr>");
+
+        pw.println("<tr class='content'>");
+        pw.println("<td class='content'>");
+        pw.println("<pre>");
+
+        pw.println("*** Date: "
+            + SimpleDateFormat.getDateTimeInstance(SimpleDateFormat.LONG,
+                SimpleDateFormat.LONG, Locale.US).format(new Date()));
+        pw.println();
+
+        this.printSystemProperties(pw);
+        this.printRawFrameworkProperties(pw);
+        this.printAssemblies(pw);
+        this.printBundles(pw);
+        this.printServices(pw);
+        this.printPreferences(pw);
+        this.printConfigurations(pw);
+
+        for (ConfigurationPrinter cp : getConfigurationPrinters()) {
+            printConfigurationPrinter(pw, cp);
+        }
+
+        pw.println("</pre>");
+        pw.println("</td>");
+        pw.println("</tr>");
+        pw.println("</table>");
+    }
+
+    private Collection<ConfigurationPrinter> getConfigurationPrinters() {
+        if (cfgPrinterTracker == null) {
+            cfgPrinterTracker = new ServiceTracker(getBundleContext(),
+                ConfigurationPrinter.SERVICE, null);
+            cfgPrinterTracker.open();
+            cfgPrinterTrackerCount = -1;
+        }
+
+        if (cfgPrinterTrackerCount != cfgPrinterTracker.getTrackingCount()) {
+            SortedMap<String, ConfigurationPrinter> cp = new TreeMap<String, ConfigurationPrinter>();
+            Object[] services = cfgPrinterTracker.getServices();
+            if (services != null) {
+                for (Object srv : services) {
+                    ConfigurationPrinter cfgPrinter = (ConfigurationPrinter) srv;
+                    cp.put(cfgPrinter.getTitle(), cfgPrinter);
+                }
+            }
+            configurationPrinters = cp;
+            cfgPrinterTrackerCount = cfgPrinterTracker.getTrackingCount();
+        }
+
+        return configurationPrinters.values();
+    }
+
+    private void printSystemProperties(PrintWriter pw) {
+        pw.println("*** System properties:");
+
+        Properties props = System.getProperties();
+        SortedSet<Object> keys = new TreeSet<Object>(props.keySet());
+        for (Object key : keys) {
+            this.infoLine(pw, null, (String) key, props.get(key));
+        }
+
+        pw.println();
+    }
+
+    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<Object> keys = new TreeSet<Object>(props.keySet());
+            for (Object key : keys) {
+                this.infoLine(pw, null, (String) key, props.get(key));
+            }
+
+        } else {
+            pw.println("  No Framework properties in " + file);
+        }
+
+        pw.println();
+    }
+
+    private void printAssemblies(PrintWriter pw) {
+        pw.println("*** Assemblies:");
+
+        Bundle[] bundles = getBundleContext().getBundles();
+        SortedSet<String> keys = new TreeSet<String>();
+        for (int i = 0; i < bundles.length; i++) {
+            if (bundles[i].getHeaders().get("Assembly-Bundles") != null) {
+                keys.add(this.getBundleString(bundles[i], false));
+            }
+        }
+
+        if (keys.isEmpty()) {
+            pw.println("  No Assemblies installed");
+        } else {
+            for (Iterator<String> ki = keys.iterator(); ki.hasNext();) {
+                this.infoLine(pw, null, null, ki.next());
+            }
+        }
+
+        pw.println();
+    }
+
+    private void printBundles(PrintWriter pw) {
+        pw.println("*** Bundles:");
+        // biz.junginger.freemem.FreeMem (1.3.0) "FreeMem Eclipse Memory
+        // Monitor" [Resolved]
+
+        Bundle[] bundles = getBundleContext().getBundles();
+        SortedSet<String> keys = new TreeSet<String>();
+        for (int i = 0; i < bundles.length; i++) {
+            keys.add(this.getBundleString(bundles[i], true));
+        }
+
+        for (Iterator<String> ki = keys.iterator(); ki.hasNext();) {
+            this.infoLine(pw, null, null, ki.next());
+        }
+
+        pw.println();
+    }
+
+    private void printServices(PrintWriter pw) {
+        pw.println("*** Services:");
+
+        // get the list of services sorted by service ID (ascending)
+        SortedMap<Object, ServiceReference> srMap = new TreeMap<Object, ServiceReference>();
+        try {
+            ServiceReference[] srs = getBundleContext().getAllServiceReferences(
+                null, null);
+            for (int i = 0; i < srs.length; i++) {
+                srMap.put(srs[i].getProperty(Constants.SERVICE_ID), srs[i]);
+            }
+        } catch (InvalidSyntaxException ise) {
+            // should handle, for now just print nothing, actually this is not
+            // expected
+        }
+
+        for (Iterator<ServiceReference> si = srMap.values().iterator(); si.hasNext();) {
+            ServiceReference sr = si.next();
+
+            this.infoLine(pw, null,
+                String.valueOf(sr.getProperty(Constants.SERVICE_ID)),
+                sr.getProperty(Constants.OBJECTCLASS));
+            this.infoLine(pw, "  ", "Bundle", this.getBundleString(
+                sr.getBundle(), false));
+
+            Bundle[] users = sr.getUsingBundles();
+            if (users != null && users.length > 0) {
+                List<String> userString = new ArrayList<String>();
+                for (int i = 0; i < users.length; i++) {
+                    userString.add(this.getBundleString(users[i], false));
+                }
+                this.infoLine(pw, "  ", "Using Bundles", userString);
+            }
+
+            String[] keys = sr.getPropertyKeys();
+            Arrays.sort(keys);
+            for (int i = 0; i < keys.length; i++) {
+                if (!Constants.SERVICE_ID.equals(keys[i])
+                    && !Constants.OBJECTCLASS.equals(keys[i])) {
+                    this.infoLine(pw, "  ", keys[i], sr.getProperty(keys[i]));
+                }
+            }
+
+            pw.println();
+        }
+    }
+
+    private void printPreferences(PrintWriter pw) {
+        pw.println("*** System Preferences:");
+
+        ServiceReference sr = getBundleContext().getServiceReference(
+            PreferencesService.class.getName());
+        if (sr == null) {
+            pw.println("  Preferences Service not registered");
+            pw.println();
+            return;
+        }
+
+        PreferencesService ps = (PreferencesService) getBundleContext().getService(
+            sr);
+        try {
+            this.printPreferences(pw, ps.getSystemPreferences());
+
+            String[] users = ps.getUsers();
+            for (int i = 0; users != null && i < users.length; i++) {
+                pw.println("*** User Preferences " + users[i] + ":");
+                this.printPreferences(pw, ps.getUserPreferences(users[i]));
+            }
+        } catch (BackingStoreException bse) {
+            // todo or not :-)
+        } finally {
+            getBundleContext().ungetService(sr);
+        }
+    }
+
+    private void printPreferences(PrintWriter pw, Preferences prefs)
+            throws BackingStoreException {
+
+        String[] children = prefs.childrenNames();
+        for (int i = 0; i < children.length; i++) {
+            this.printPreferences(pw, prefs.node(children[i]));
+        }
+
+        String[] keys = prefs.keys();
+        for (int i = 0; i < keys.length; i++) {
+            this.infoLine(pw, null, prefs.absolutePath() + "/" + keys[i],
+                prefs.get(keys[i], null));
+        }
+
+        pw.println();
+    }
+
+    private void printConfigurations(PrintWriter pw) {
+        pw.println("*** Configurations:");
+
+        ServiceReference sr = getBundleContext().getServiceReference(
+            ConfigurationAdmin.class.getName());
+        if (sr == null) {
+            pw.println("  Configuration Admin Service not registered");
+        } else {
+
+            ConfigurationAdmin ca = (ConfigurationAdmin) getBundleContext().getService(
+                sr);
+            try {
+                Configuration[] configs = ca.listConfigurations(null);
+                if (configs != null && configs.length > 0) {
+                    SortedMap<Object, Configuration> sm = new TreeMap<Object, Configuration>();
+                    for (int i = 0; i < configs.length; i++) {
+                        sm.put(configs[i].getPid(), configs[i]);
+                    }
+
+                    for (Iterator<Configuration> mi = sm.values().iterator(); mi.hasNext();) {
+                        this.printConfiguration(pw, mi.next());
+                    }
+                } else {
+                    pw.println("  No Configurations available");
+                }
+            } catch (Exception e) {
+                // todo or not :-)
+            } finally {
+                getBundleContext().ungetService(sr);
+            }
+        }
+
+        pw.println();
+    }
+
+    private void printConfigurationPrinter(PrintWriter pw,
+            ConfigurationPrinter cp) {
+        pw.println("*** " + cp.getTitle() + ":");
+        cp.printConfiguration(pw);
+        pw.println();
+    }
+
+    private void printConfiguration(PrintWriter pw, Configuration config) {
+        this.infoLine(pw, "", "PID", config.getPid());
+
+        if (config.getFactoryPid() != null) {
+            this.infoLine(pw, "  ", "Factory PID", config.getFactoryPid());
+        }
+
+        String loc = (config.getBundleLocation() != null)
+                ? config.getBundleLocation()
+                : "Unbound";
+        this.infoLine(pw, "  ", "BundleLocation", loc);
+
+        @SuppressWarnings("unchecked")
+        Dictionary<String, Object> props = config.getProperties();
+        if (props != null) {
+            SortedSet<String> keys = new TreeSet<String>();
+            for (Enumeration<String> ke = props.keys(); ke.hasMoreElements();) {
+                keys.add(ke.nextElement());
+            }
+
+            for (Iterator<String> ki = keys.iterator(); ki.hasNext();) {
+                String key = ki.next();
+                this.infoLine(pw, "  ", key, props.get(key));
+            }
+        }
+
+        pw.println();
+    }
+
+    private void infoLine(PrintWriter pw, String indent, String label,
+            Object value) {
+        if (indent != null) {
+            pw.print(indent);
+        }
+
+        if (label != null) {
+            pw.print(label);
+            pw.print('=');
+        }
+
+        this.printObject(pw, value);
+
+        pw.println();
+    }
+
+    private void printObject(PrintWriter pw, Object value) {
+        if (value == null) {
+            pw.print("null");
+        } else if (value.getClass().isArray()) {
+            this.printArray(pw, (Object[]) value);
+        } else {
+            pw.print(value);
+        }
+    }
+
+    private void printArray(PrintWriter pw, Object[] values) {
+        pw.print('[');
+        if (values != null && values.length > 0) {
+            for (int i = 0; i < values.length; i++) {
+                if (i > 0) {
+                    pw.print(", ");
+                }
+                this.printObject(pw, values[i]);
+            }
+        }
+        pw.print(']');
+    }
+
+    private String getBundleString(Bundle bundle, boolean withState) {
+        StringBuffer buf = new StringBuffer();
+
+        if (bundle.getSymbolicName() != null) {
+            buf.append(bundle.getSymbolicName());
+        } else if (bundle.getLocation() != null) {
+            buf.append(bundle.getLocation());
+        } else {
+            buf.append(bundle.getBundleId());
+        }
+
+        @SuppressWarnings("unchecked")
+        Dictionary<String, String> headers = bundle.getHeaders();
+        if (headers.get(Constants.BUNDLE_VERSION) != null) {
+            buf.append(" (").append(headers.get(Constants.BUNDLE_VERSION)).append(
+                ')');
+        }
+
+        if (headers.get(Constants.BUNDLE_NAME) != null) {
+            buf.append(" \"").append(headers.get(Constants.BUNDLE_NAME)).append(
+                '"');
+        }
+
+        if (withState) {
+            buf.append(" [");
+            switch (bundle.getState()) {
+                case Bundle.INSTALLED:
+                    buf.append("Installed");
+                    break;
+                case Bundle.RESOLVED:
+                    buf.append("Resolved");
+                    break;
+                case Bundle.STARTING:
+                    buf.append("Starting");
+                    break;
+                case Bundle.ACTIVE:
+                    buf.append("Active");
+                    break;
+                case Bundle.STOPPING:
+                    buf.append("Stopping");
+                    break;
+                case Bundle.UNINSTALLED:
+                    buf.append("Uninstalled");
+                    break;
+            }
+            buf.append(']');
+        }
+
+        return buf.toString();
+    }
+
+}
diff --git a/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/obr/AbstractObrPlugin.java b/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/obr/AbstractObrPlugin.java
new file mode 100644
index 0000000..c5f35dc
--- /dev/null
+++ b/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/obr/AbstractObrPlugin.java
@@ -0,0 +1,57 @@
+/*
+ * 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.sling.osgi.console.web.internal.obr;
+
+//import org.apache.sling.osgi.assembly.installer.BundleRepositoryAdmin;
+//import org.apache.sling.osgi.assembly.installer.InstallerService;
+import org.apache.sling.osgi.console.web.internal.BaseManagementPlugin;
+import org.osgi.framework.BundleContext;
+import org.osgi.util.tracker.ServiceTracker;
+
+public class AbstractObrPlugin extends BaseManagementPlugin {
+
+    // track the optional installer service manually
+    private ServiceTracker installerService;
+
+    public void setBundleContext(BundleContext bundleContext) {
+        super.setBundleContext(bundleContext);
+
+    }
+/*
+    protected InstallerService getInstallerService() {
+        if (installerService == null) {
+            try {
+                installerService = new ServiceTracker(getBundleContext(),
+                    InstallerService.class.getName(), null);
+                installerService.open();
+            } catch (Throwable t) {
+                // missing InstallerService class ??
+                return null;
+            }
+
+        }
+
+        return (InstallerService) installerService.getService();
+    }
+
+    protected BundleRepositoryAdmin getBundleRepositoryAdmin() {
+        InstallerService is = getInstallerService();
+        return (is != null) ? is.getBundleRepositoryAdmin() : null;
+    }*/
+}
diff --git a/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/obr/BundleRepositoryRender.java b/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/obr/BundleRepositoryRender.java
new file mode 100644
index 0000000..c33a4ea
--- /dev/null
+++ b/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/obr/BundleRepositoryRender.java
@@ -0,0 +1,278 @@
+/*
+ * 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.sling.osgi.console.web.internal.obr;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.StringTokenizer;
+
+import org.apache.sling.osgi.console.web.Render;
+import org.apache.sling.osgi.console.web.internal.Util;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.Version;
+
+public abstract class BundleRepositoryRender extends AbstractObrPlugin implements Render {
+
+    public static final String NAME = "bundlerepo";
+
+    public static final String LABEL = "OSGi Repository";
+
+    public static final String PARAM_REPO_ID = "repositoryId";
+
+    public static final String PARAM_REPO_URL = "repositoryURL";
+
+    private static final String REPOSITORY_PROPERTY = "obr.repository.url";
+
+    private String[] repoURLs;
+
+    public void setBundleContext(BundleContext bundleContext) {
+        super.setBundleContext(bundleContext);
+
+        String urlStr = bundleContext.getProperty(REPOSITORY_PROPERTY);
+        List<String> urlList = new ArrayList<String>();
+
+        if (urlStr != null) {
+            StringTokenizer st = new StringTokenizer(urlStr);
+            while (st.hasMoreTokens()) {
+                urlList.add(st.nextToken());
+            }
+        }
+
+        this.repoURLs = urlList.toArray(new String[urlList.size()]);
+    }
+
+    public String getName() {
+        return NAME;
+    }
+
+    public String getLabel() {
+        return LABEL;
+    }
+/*
+    public void render(HttpServletRequest request, HttpServletResponse response)
+            throws IOException {
+
+        PrintWriter pw = response.getWriter();
+        this.header(pw);
+
+        Iterator<?> repos;
+        BundleRepositoryAdmin repoAdmin = getBundleRepositoryAdmin();
+        if (repoAdmin != null) {
+            repos = repoAdmin.getRepositories();
+        } else {
+            repos = Collections.emptyList().iterator();
+        }
+
+        Set<String> activeURLs = new HashSet<String>();
+        if (!repos.hasNext()) {
+            pw.println("<tr class='content'>");
+            pw.println("<td class='content' colspan='4'>No Active Repositories</td>");
+            pw.println("</tr>");
+        } else {
+            while (repos.hasNext()) {
+                Repository repo = (Repository) repos.next();
+
+                activeURLs.add(repo.getURL().toString());
+
+                pw.println("<tr class='content'>");
+                pw.println("<td class='content'>" + repo.getName() + "</td>");
+                pw.println("<td class='content'>" + repo.getURL() + "</td>");
+                pw.println("<td class='content'>"
+                    + new Date(repo.getLastModified()) + "</td>");
+                pw.println("<td class='content'>");
+                pw.println("<form>");
+                pw.println("<input type='hidden' name='" + Util.PARAM_ACTION
+                    + "' value='" + RefreshRepoAction.NAME + "'>");
+                pw.println("<input type='hidden' name='"
+                    + RefreshRepoAction.PARAM_REPO + "' value='"
+                    + repo.getURL() + "'>");
+                pw.println("<input class='submit' type='submit' value='Refresh'>");
+                pw.println("</form>");
+                pw.println("</td>");
+                pw.println("</tr>");
+            }
+        }
+
+        // list any repositories configured but not active
+        for (int i = 0; i < this.repoURLs.length; i++) {
+            if (!activeURLs.contains(this.repoURLs[i])) {
+                pw.println("<tr class='content'>");
+                pw.println("<td class='content'>-</td>");
+                pw.println("<td class='content'>" + this.repoURLs[i] + "</td>");
+                pw.println("<td class='content'>[inactive, click Refresh to activate]</td>");
+                pw.println("<td class='content'>");
+                pw.println("<form>");
+                pw.println("<input type='hidden' name='" + Util.PARAM_ACTION
+                    + "' value='" + RefreshRepoAction.NAME + "'>");
+                pw.println("<input type='hidden' name='"
+                    + RefreshRepoAction.PARAM_REPO + "' value='"
+                    + this.repoURLs[i] + "'>");
+                pw.println("<input class='submit' type='submit' value='Refresh'>");
+                pw.println("</form>");
+                pw.println("</td>");
+                pw.println("</tr>");
+            }
+        }
+
+        this.footer(pw);
+
+        this.listResources(pw);
+    }
+*/
+    private void header(PrintWriter pw) {
+        pw.println("<table class='content' cellpadding='0' cellspacing='0' width='100%'>");
+        pw.println("<tr class='content'>");
+        pw.println("<th class='content container' colspan='4'>Bundle Repositories</th>");
+        pw.println("</tr>");
+        pw.println("<tr class='content'>");
+        pw.println("<th class='content'>Name</th>");
+        pw.println("<th class='content'>URL</th>");
+        pw.println("<th class='content'>Last Modification Time</th>");
+        pw.println("<th class='content'>&nbsp;</th>");
+        pw.println("</tr>");
+    }
+
+    private void footer(PrintWriter pw) {
+        pw.println("</table>");
+    }
+
+    private void resourcesHeader(PrintWriter pw, boolean doForm) {
+
+        if (doForm) {
+            pw.println("<form method='post'>");
+            pw.println("<input type='hidden' name='" + Util.PARAM_ACTION
+                + "' value='" + InstallFromRepoAction.NAME + "'>");
+        }
+
+        pw.println("<table class='content' cellpadding='0' cellspacing='0' width='100%'>");
+        pw.println("<tr class='content'>");
+        pw.println("<th class='content container' colspan='3'>Available Resources</th>");
+        pw.println("</tr>");
+        pw.println("<tr class='content'>");
+        pw.println("<th class='content'>Deploy</th>");
+        pw.println("<th class='content'>Name</th>");
+        pw.println("<th class='content'>Version</th>");
+        pw.println("</tr>");
+    }
+/*
+    private void listResources(PrintWriter pw) {
+        InstallerService is = getInstallerService();
+        if (is == null) {
+            return;
+        }
+
+        Map<String, Version> bundles = this.getBundles();
+
+        Iterator<?> resources = is.getBundleRepositoryAdmin().getResources();
+        SortedSet<Resource> resSet = new TreeSet<Resource>(
+            new Comparator<Resource>() {
+                public int compare(Resource o1, Resource o2) {
+                    if (o1 == o2 || o1.equals(o2)) {
+                        return 0;
+                    }
+
+                    if (o1.getPresentationName().equals(
+                        o2.getPresentationName())) {
+                        return o1.getVersion().compareTo(o2.getVersion());
+                    }
+
+                    return o1.getPresentationName().compareTo(
+                        o2.getPresentationName());
+                }
+            });
+
+        while (resources.hasNext()) {
+            Resource res = (Resource) resources.next();
+            Version ver = bundles.get(res.getSymbolicName());
+            if (ver == null || ver.compareTo(res.getVersion()) < 0) {
+                resSet.add(res);
+            }
+        }
+
+        this.resourcesHeader(pw, !resSet.isEmpty());
+
+        for (Resource resource : resSet) {
+            this.printResource(pw, resource);
+        }
+
+        this.resourcesFooter(pw, !resSet.isEmpty());
+    }
+
+    private void printResource(PrintWriter pw, Resource res) {
+        pw.println("<tr class='content'>");
+        pw.println("<td class='content' valign='top' align='center'><input class='checkradio' type='checkbox' name='bundle' value='"
+            + res.getSymbolicName() + "," + res.getVersion() + "'></td>");
+
+        // check whether the resource is an assembly (category name)
+        String style = "";
+        String[] cat = res.getCategories();
+        for (int i = 0; cat != null && i < cat.length; i++) {
+            if ("assembly".equals(cat[i])) {
+                style = "style='font-weight:bold'";
+            }
+        }
+        pw.println("<td class='content' " + style + ">"
+            + res.getPresentationName() + " (" + res.getSymbolicName()
+            + ")</td>");
+        pw.println("<td class='content' " + style + " valign='top'>"
+            + res.getVersion() + "</td>");
+
+        pw.println("</tr>");
+    }
+*/
+    private void resourcesButtons(PrintWriter pw) {
+        pw.println("<tr class='content'>");
+        pw.println("<td class='content'>&nbsp;</td>");
+        pw.println("<td class='content' colspan='2'>");
+        pw.println("<input class='submit' style='width:auto' type='submit' name='deploy' value='Deploy Selected'>");
+        pw.println("&nbsp;&nbsp;&nbsp;");
+        pw.println("<input class='submit' style='width:auto' type='submit' name='deploystart' value='Deploy and Start Selected'>");
+        pw.println("</td></tr>");
+    }
+
+    private void resourcesFooter(PrintWriter pw, boolean doForm) {
+        if (doForm) {
+            this.resourcesButtons(pw);
+        }
+        pw.println("</table></form>");
+    }
+
+    private Map<String, Version> getBundles() {
+        Map<String, Version> bundles = new HashMap<String, Version>();
+
+        Bundle[] installed = getBundleContext().getBundles();
+        for (int i = 0; i < installed.length; i++) {
+            String ver = (String) installed[i].getHeaders().get(
+                Constants.BUNDLE_VERSION);
+            Version bundleVersion = Version.parseVersion(ver);
+
+            // assume one bundle instance per symbolic name !!
+            // only add if there is a symbolic name !
+            if (installed[i].getSymbolicName() != null) {
+                bundles.put(installed[i].getSymbolicName(), bundleVersion);
+            }
+        }
+
+        return bundles;
+    }
+
+}
diff --git a/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/obr/InstallFromRepoAction.java b/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/obr/InstallFromRepoAction.java
new file mode 100644
index 0000000..0135a91
--- /dev/null
+++ b/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/obr/InstallFromRepoAction.java
@@ -0,0 +1,87 @@
+/*
+ * 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.sling.osgi.console.web.internal.obr;
+
+import org.apache.sling.osgi.console.web.Action;
+
+public abstract class InstallFromRepoAction extends AbstractObrPlugin implements Action {
+
+    public static final String NAME = "installFromOBR";
+
+    public String getName() {
+        return NAME;
+    }
+
+    public String getLabel() {
+        return NAME;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see org.apache.sling.manager.web.internal.Action#performAction(javax.servlet.http.HttpServletRequest,
+     *      javax.servlet.http.HttpServletResponse)
+    public boolean performAction(HttpServletRequest request,
+            HttpServletResponse response) {
+
+        // check whether we have to do something
+        String[] bundles = request.getParameterValues("bundle");
+        if (bundles == null || bundles.length == 0) {
+            getLog().log(LogService.LOG_INFO, "No resources to deploy");
+            return true;
+        }
+
+        InstallerService installerService = getInstallerService();
+        if (installerService != null) {
+            Installer installer = installerService.getInstaller();
+
+            // prepare the deployment
+            for (int i = 0; i < bundles.length; i++) {
+                String bundle = bundles[i];
+                int comma = bundle.indexOf(',');
+                String name = (comma > 0) ? bundle.substring(0, comma) : bundle;
+                String version = (comma < bundle.length() - 1)
+                        ? bundle.substring(comma + 1)
+                        : null;
+
+                if (name.length() > 0) {
+                    // no name, ignore this one
+                    VersionRange versionRange = new VersionRange(version);
+                    installer.addBundle(name, versionRange, -1);
+                }
+            }
+
+            // check whether the "deploystart" button was clicked
+            boolean start = request.getParameter("deploystart") != null;
+
+            try {
+                installer.install(start);
+            } catch (InstallerException ie) {
+                Throwable cause = (ie.getCause() != null) ? ie.getCause() : ie;
+                getLog().log(LogService.LOG_ERROR, "Cannot install bundles",
+                    cause);
+            } finally {
+                installer.dispose();
+            }
+        }
+
+        // redirect to bundle list
+        return true;
+    }
+     */
+
+}
diff --git a/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/obr/RefreshRepoAction.java b/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/obr/RefreshRepoAction.java
new file mode 100644
index 0000000..8ab5dd6
--- /dev/null
+++ b/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/obr/RefreshRepoAction.java
@@ -0,0 +1,87 @@
+/*
+ * 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.sling.osgi.console.web.internal.obr;
+
+import org.apache.sling.osgi.console.web.Action;
+
+public abstract class RefreshRepoAction extends AbstractObrPlugin implements Action {
+
+    public static final String NAME = "refreshOBR";
+
+    public static final String PARAM_REPO = "repository";
+
+    public String getName() {
+        return NAME;
+    }
+
+    public String getLabel() {
+        return NAME;
+    }
+/*
+    public boolean performAction(HttpServletRequest request,
+            HttpServletResponse response) {
+
+        BundleRepositoryAdmin repoAdmin = getBundleRepositoryAdmin();
+        if (repoAdmin != null) {
+            String repositoryURL = request.getParameter("repository");
+            Iterator<Repository> repos = repoAdmin.getRepositories();
+            Repository repo = this.getRepository(repos, repositoryURL);
+
+            URL repoURL = null;
+            if (repo != null) {
+                repoURL = repo.getURL();
+            } else {
+                try {
+                    repoURL = new URL(repositoryURL);
+                } catch (Throwable t) {
+                    // don't care, just ignore
+                }
+            }
+
+            // log.log(LogService.LOG_DEBUG, "Refreshing " + repo.getURL());
+            if (repoURL != null) {
+                try {
+                    repoAdmin.addRepository(repoURL);
+                } catch (Exception e) {
+                    // TODO: log.log(LogService.LOG_ERROR, "Cannot refresh
+                    // Repository " + repo.getURL());
+                }
+            }
+        }
+
+        return true;
+    }
+
+    // ---------- internal -----------------------------------------------------
+
+    private Repository getRepository(Iterator<Repository> repos,
+            String repositoryUrl) {
+        if (repositoryUrl == null || repositoryUrl.length() == 0) {
+            return null;
+        }
+
+        while (repos.hasNext()) {
+            Repository repo = repos.next();
+            if (repositoryUrl.equals(repo.getURL().toString())) {
+                return repo;
+            }
+        }
+
+        return null;
+    }
+*/
+}
diff --git a/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/servlet/ConfigurationListener.java b/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/servlet/ConfigurationListener.java
new file mode 100644
index 0000000..9c01539
--- /dev/null
+++ b/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/servlet/ConfigurationListener.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.sling.osgi.console.web.internal.servlet;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.cm.ManagedService;
+
+class ConfigurationListener implements ManagedService {
+
+    private final SlingManager slingManager;
+
+    static ServiceRegistration create(SlingManager slingManager) {
+        ConfigurationListener cl = new ConfigurationListener(slingManager);
+
+        Dictionary<String, String> props = new Hashtable<String, String>();
+        props.put(Constants.SERVICE_VENDOR, "The Apache Software Foundation");
+        props.put(Constants.SERVICE_DESCRIPTION,
+            "Sling Management Console Configuration Receiver");
+        props.put(Constants.SERVICE_PID, slingManager.getClass().getName());
+
+        return slingManager.getBundleContext().registerService(
+            ManagedService.class.getName(), cl, props);
+    }
+
+    private ConfigurationListener(SlingManager slingManager) {
+        this.slingManager = slingManager;
+    }
+
+    @SuppressWarnings("unchecked")
+    public void updated(Dictionary config) {
+        slingManager.updateConfiguration(config);
+    }
+}
\ No newline at end of file
diff --git a/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/servlet/Logger.java b/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/servlet/Logger.java
new file mode 100644
index 0000000..1926497
--- /dev/null
+++ b/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/servlet/Logger.java
@@ -0,0 +1,79 @@
+/*
+ * 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.sling.osgi.console.web.internal.servlet;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.service.log.LogService;
+import org.osgi.util.tracker.ServiceTracker;
+
+public class Logger {
+
+    private ServiceTracker logTracker;
+
+    Logger(BundleContext bundleContext) {
+        logTracker = new ServiceTracker(bundleContext,
+            LogService.class.getName(), null);
+        logTracker.open();
+    }
+
+    void dispose() {
+        if (logTracker != null) {
+            logTracker.close();
+        }
+    }
+
+    public void log(int logLevel, String message) {
+        log(logLevel, message, null);
+    }
+
+    public void log(int logLevel, String message, Throwable t) {
+        Object log = logTracker.getService();
+        if (log != null) {
+            ((LogService) log).log(logLevel, message, t);
+        } else {
+            String level;
+            switch (logLevel) {
+                case LogService.LOG_DEBUG:
+                    level = "*DEBUG*";
+                    break;
+                case LogService.LOG_INFO:
+                    level = "*INFO *";
+                    break;
+                case LogService.LOG_WARNING:
+                    level = "*WARN *";
+                    break;
+                case LogService.LOG_ERROR:
+                    level = "*ERROR*";
+                    break;
+                default:
+                    level = "*" + logLevel + "*";
+                    break;
+            }
+
+            if (message == null && t != null) {
+                message = t.getMessage();
+            }
+
+            System.out.println(level + " " + message);
+            if (t != null) {
+                t.printStackTrace(System.out);
+            }
+        }
+    }
+}
diff --git a/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/servlet/SlingHttpContext.java b/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/servlet/SlingHttpContext.java
new file mode 100644
index 0000000..1f7ad6d
--- /dev/null
+++ b/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/servlet/SlingHttpContext.java
@@ -0,0 +1,227 @@
+/*
+ * 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.sling.osgi.console.web.internal.servlet;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URL;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.osgi.service.http.HttpContext;
+import org.osgi.service.http.HttpService;
+
+final class SlingHttpContext implements HttpContext {
+
+    private static final String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate";
+
+    private static final String HEADER_AUTHORIZATION = "Authorization";
+
+    private static final String AUTHENTICATION_SCHEME_BASIC = "Basic";
+
+    /**
+     * The encoding table which causes BaseFlex encoding/deconding to work like
+     * Base64 encoding/deconding.
+     */
+    private static final String base64Table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+    /**
+     * The pad character used in Base64 encoding/deconding.
+     */
+    private static final char base64Pad = '=';
+
+    String realm;
+
+    String userId;
+    String user;
+
+    private final HttpContext base;
+
+    SlingHttpContext(HttpService httpService, String realm, String userId, String password) {
+        this.base = httpService.createDefaultHttpContext();
+        this.realm = realm;
+        this.userId = userId;
+        this.user = encode(userId, password);
+    }
+
+    public String getMimeType(String name) {
+        return this.base.getMimeType(name);
+    }
+
+    public URL getResource(String name) {
+        URL url = this.base.getResource(name);
+        if (url == null && name.endsWith("/")) {
+            return this.base.getResource(name.substring(0, name.length() - 1));
+        }
+        return url;
+    }
+
+    /**
+     * Checks the <code>Authorization</code> header of the request for Basic
+     * authentication user name and password. If contained, the credentials are
+     * compared to the user name and password set for the Sling Console.
+     * <p>
+     * If no user name is set, the <code>Authorization</code> header is
+     * ignored and the client is assumed to be authenticated.
+     *
+     * @param request The HTTP request used to get the
+     *            <code>Authorization</code> header.
+     * @param response The HTTP response used to send the authentication request
+     *            if authentication is required but not satisfied.
+     * @return <code>true</code> if authentication is required and not
+     *         satisfied by the request.
+     */
+    public boolean handleSecurity(HttpServletRequest request,
+            HttpServletResponse response) {
+
+        // don't care for authentication if no user name is configured
+        if (this.user == null) {
+            return true;
+        }
+
+        // Return immediately if the header is missing
+        String authHeader = request.getHeader(HEADER_AUTHORIZATION);
+        if (authHeader != null && authHeader.length() > 0) {
+
+            // Get the authType (Basic, Digest) and authInfo (user/password)
+            // from
+            // the header
+            authHeader = authHeader.trim();
+            int blank = authHeader.indexOf(' ');
+            if (blank > 0) {
+                String authType = authHeader.substring(0, blank);
+                String authInfo = authHeader.substring(blank).trim();
+
+                // Check whether authorization type matches
+                if (authType.equalsIgnoreCase(AUTHENTICATION_SCHEME_BASIC)
+                    && this.user.equals(authInfo)) {
+
+                    // as per the spec, set attributes
+                    request.setAttribute(HttpContext.AUTHENTICATION_TYPE, "");
+                    request.setAttribute(HttpContext.REMOTE_USER, this.userId);
+
+                    // succeed
+                    return true;
+                }
+            }
+        }
+
+        // request authentication
+        response.setHeader(HEADER_WWW_AUTHENTICATE, AUTHENTICATION_SCHEME_BASIC
+            + " realm=\"" + this.realm + "\"");
+        try {
+            response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+        } catch (IOException ioe) {
+            // failed sending the error, fall back to setting the status
+            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+        }
+
+        // inform HttpService that authentication failed
+        return false;
+    }
+
+
+    /**
+     * Base64 encodes the user name and password for comparison to the value of
+     * a Basic encoded HTTP header authentication.
+     *
+     * @param user The name of the user in the username/password pair
+     * @param password The password in the username/password pair
+     * @return The Base64 encoded username/password pair or <code>null</code>
+     *         if <code>user</code> is <code>null</code> or empty.
+     */
+    public static String encode(String user, String password) {
+
+        /* check arguments */
+        if (user == null || user.length() == 0) return null;
+
+        String srcString = user + ":";
+        if (password != null && password.length() > 0) {
+            srcString += password;
+        }
+
+        // need bytes
+        byte[] src;
+        try {
+            src = srcString.getBytes("ISO-8859-1");
+        } catch (UnsupportedEncodingException uee) {
+            // we do not expect this, the API presribes ISO-8859-1 to be present
+            // anyway, fallback to platform default
+            src = srcString.getBytes();
+        }
+
+        int srcsize = src.length;
+        int tbllen = base64Table.length();
+
+        StringBuffer result = new StringBuffer(srcsize);
+
+        /* encode */
+        int tblpos = 0;
+        int bitpos = 0;
+        int bitsread = -1;
+        int inpos = 0;
+        int pos = 0;
+
+        while (inpos <= srcsize) {
+
+            if (bitsread < 0) {
+                if (inpos < srcsize) {
+                    pos = src[inpos++];
+                } else {
+                    // inpos++;
+                    // pos = 0;
+                    break;
+                }
+                bitsread = 7;
+            }
+
+            tblpos = 0;
+            bitpos = tbllen / 2;
+            while (bitpos > 0) {
+                if (bitsread < 0) {
+                    pos = (inpos < srcsize) ? src[inpos] : '\0';
+                    inpos++;
+                    bitsread = 7;
+                }
+
+                /* test if bit at pos <bitpos> in <pos> is set.. */
+                if (((1 << bitsread) & pos) != 0) tblpos += bitpos;
+
+                bitpos /= 2;
+                bitsread--;
+            }
+
+            // got one
+            result.append(base64Table.charAt(tblpos));
+        }
+
+        /* add the padding bytes */
+        while (bitsread != -1) {
+            bitpos = tbllen / 2;
+            while (bitpos > 0) {
+                if (bitsread < 0) bitsread = 7;
+                bitpos /= 2;
+                bitsread--;
+            }
+
+            result.append(base64Pad);
+        }
+
+        return result.toString();
+    }
+}
\ No newline at end of file
diff --git a/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/servlet/SlingManager.java b/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/servlet/SlingManager.java
new file mode 100644
index 0000000..57109a0
--- /dev/null
+++ b/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/servlet/SlingManager.java
@@ -0,0 +1,607 @@
+/*
+ * 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.sling.osgi.console.web.internal.servlet;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import javax.servlet.GenericServlet;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.fileupload.FileItem;
+import org.apache.commons.fileupload.FileUploadException;
+import org.apache.commons.fileupload.disk.DiskFileItemFactory;
+import org.apache.commons.fileupload.servlet.ServletFileUpload;
+import org.apache.commons.fileupload.servlet.ServletRequestContext;
+import org.apache.sling.osgi.console.web.Action;
+import org.apache.sling.osgi.console.web.Render;
+import org.apache.sling.osgi.console.web.internal.BaseManagementPlugin;
+import org.apache.sling.osgi.console.web.internal.Util;
+import org.apache.sling.osgi.console.web.internal.compendium.AjaxConfigManagerAction;
+import org.apache.sling.osgi.console.web.internal.compendium.ComponentConfigurationPrinter;
+import org.apache.sling.osgi.console.web.internal.compendium.ComponentRenderAction;
+import org.apache.sling.osgi.console.web.internal.compendium.ConfigManager;
+import org.apache.sling.osgi.console.web.internal.core.AjaxBundleDetailsAction;
+import org.apache.sling.osgi.console.web.internal.core.BundleListRender;
+import org.apache.sling.osgi.console.web.internal.core.InstallAction;
+import org.apache.sling.osgi.console.web.internal.core.RefreshPackagesAction;
+import org.apache.sling.osgi.console.web.internal.core.SetStartLevelAction;
+import org.apache.sling.osgi.console.web.internal.core.StartAction;
+import org.apache.sling.osgi.console.web.internal.core.StopAction;
+import org.apache.sling.osgi.console.web.internal.core.UninstallAction;
+import org.apache.sling.osgi.console.web.internal.core.UpdateAction;
+import org.apache.sling.osgi.console.web.internal.misc.ConfigurationRender;
+import org.apache.sling.osgi.console.web.internal.system.GCAction;
+import org.apache.sling.osgi.console.web.internal.system.ShutdownAction;
+import org.apache.sling.osgi.console.web.internal.system.ShutdownRender;
+import org.apache.sling.osgi.console.web.internal.system.VMStatRender;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.http.HttpContext;
+import org.osgi.service.http.HttpService;
+import org.osgi.service.log.LogService;
+import org.osgi.util.tracker.ServiceTracker;
+
+/**
+ * The <code>Sling Manager</code> TODO
+ *
+ * @scr.component ds="no" label="%manager.name"
+ *                description="%manager.description"
+ */
+public class SlingManager extends GenericServlet {
+
+    /** Pseudo class version ID to keep the IDE quite. */
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * The name and value of a parameter which will prevent redirection to a
+     * render after the action has been executed (value is "_noredir_"). This
+     * may be used by programmatic action submissions.
+     */
+    public static final String PARAM_NO_REDIRECT_AFTER_ACTION = "_noredir_";
+
+    /**
+     * @scr.property valueRef="DEFAULT_MANAGER_ROOT"
+     */
+    private static final String PROP_MANAGER_ROOT = "manager.root";
+
+    /**
+     * @scr.property value="list"
+     */
+    private static final String PROP_DEFAULT_RENDER = "default.render";
+
+    /**
+     * @scr.property value="Sling Management Console"
+     */
+    private static final String PROP_REALM = "realm";
+
+    /**
+     * @scr.property value="admin"
+     */
+    private static final String PROP_USER_NAME = "username";
+
+    /**
+     * @scr.property value="admin"
+     */
+    private static final String PROP_PASSWORD = "password";
+
+    /**
+     * The default value for the {@link #PROP_MANAGER_ROOT} configuration
+     * property (value is "/system/console").
+     */
+    private static final String DEFAULT_MANAGER_ROOT = "/system/console";
+
+    private static final Class<?>[] PLUGIN_CLASSES = {
+        AjaxConfigManagerAction.class, ComponentConfigurationPrinter.class,
+        ComponentRenderAction.class, ConfigManager.class,
+        AjaxBundleDetailsAction.class, BundleListRender.class,
+        InstallAction.class, RefreshPackagesAction.class,
+        SetStartLevelAction.class, StartAction.class, StopAction.class,
+        UninstallAction.class, UpdateAction.class,
+        ConfigurationRender.class, GCAction.class,
+        ShutdownAction.class, ShutdownRender.class, VMStatRender.class };
+
+    private BundleContext bundleContext;
+
+    private Logger log;
+
+    private ServiceTracker httpServiceTracker;
+
+    private HttpService httpService;
+
+    private ServiceTracker operationsTracker;
+
+    private ServiceTracker rendersTracker;
+
+    private ServiceRegistration configurationListener;
+
+    private Map<String, Action> operations = new HashMap<String, Action>();
+
+    private SortedMap<String, Render> renders = new TreeMap<String, Render>();
+
+    private Render defaultRender;
+
+    private String defaultRenderName;
+
+    private String webManagerRoot;
+
+    private Dictionary<String, Object> configuration;
+
+    public SlingManager(BundleContext bundleContext) {
+
+        this.bundleContext = bundleContext;
+        this.log = new Logger(bundleContext);
+
+        updateConfiguration(null);
+
+        try {
+            this.configurationListener = ConfigurationListener.create(this);
+        } catch (Throwable t) {
+            // might be caused by CM not available
+        }
+
+        // track renders and operations
+        operationsTracker = new OperationServiceTracker(this);
+        operationsTracker.open();
+        rendersTracker = new RenderServiceTracker(this);
+        rendersTracker.open();
+        httpServiceTracker = new HttpServiceTracker(this);
+        httpServiceTracker.open();
+
+        for (Class<?> pluginClass : PLUGIN_CLASSES) {
+            try {
+                Object plugin = pluginClass.newInstance();
+                if (plugin instanceof BaseManagementPlugin) {
+                    ((BaseManagementPlugin) plugin).setBundleContext(bundleContext);
+                    ((BaseManagementPlugin) plugin).setLogger(log);
+                }
+                if (plugin instanceof Action) {
+                    bindOperation((Action) plugin);
+                }
+                if (plugin instanceof Render) {
+                    bindRender((Render) plugin);
+                }
+            } catch (Throwable t) {
+                // todo: log
+            }
+        }
+    }
+
+    public void dispose() {
+
+        if (configurationListener != null) {
+            configurationListener.unregister();
+            configurationListener = null;
+        }
+
+        if (operationsTracker != null) {
+            operationsTracker.close();
+            operationsTracker = null;
+        }
+
+        if (rendersTracker != null) {
+            rendersTracker.close();
+            rendersTracker = null;
+        }
+
+        if (httpServiceTracker != null) {
+            httpServiceTracker.close();
+            httpServiceTracker = null;
+        }
+
+        // simply remove all operations, we should not be used anymore
+        this.defaultRender = null;
+        this.operations.clear();
+        this.renders.clear();
+
+        if (log != null) {
+            log.dispose();
+        }
+
+        this.bundleContext = null;
+    }
+
+    public void service(ServletRequest req, ServletResponse res)
+            throws ServletException, IOException {
+
+        HttpServletRequest request = (HttpServletRequest) req;
+        HttpServletResponse response = (HttpServletResponse) res;
+
+        // handle the request action, terminate if done
+        if (this.handleAction(request, response)) {
+            return;
+        }
+
+        // check whether we are not at .../{webManagerRoot}
+        if (request.getRequestURI().endsWith(this.webManagerRoot)) {
+            response.sendRedirect(request.getRequestURI() + "/"
+                + this.defaultRender.getName());
+            return;
+        }
+
+        // otherwise we render the response
+        Render render = this.getRender(request);
+        if (render == null) {
+            response.sendError(HttpServletResponse.SC_NOT_FOUND);
+            return;
+        }
+
+        String current = render.getName();
+        boolean disabled = false; // should take action==shutdown into
+        // account:
+        // Boolean.valueOf(request.getParameter("disabled")).booleanValue();
+
+        PrintWriter pw = Util.startHtml(response, render.getLabel());
+        Util.navigation(pw, this.renders.values(), current, disabled);
+
+        render.render(request, response);
+
+        Util.endHhtml(pw);
+    }
+
+    protected boolean handleAction(HttpServletRequest req,
+            HttpServletResponse resp) throws IOException {
+        // check action
+        String actionName = this.getParameter(req, Util.PARAM_ACTION);
+        if (actionName != null) {
+            Action action = this.operations.get(actionName);
+            if (action != null) {
+                boolean redirect = true;
+                try {
+                    redirect = action.performAction(req, resp);
+                } catch (IOException ioe) {
+                    this.log(ioe.getMessage(), ioe);
+                } catch (ServletException se) {
+                    this.log(se.getMessage(), se.getRootCause());
+                }
+
+                // maybe overwrite redirect
+                if (PARAM_NO_REDIRECT_AFTER_ACTION.equals(getParameter(req,
+                    PARAM_NO_REDIRECT_AFTER_ACTION))) {
+                    resp.setStatus(HttpServletResponse.SC_OK);
+                    resp.setContentType("text/html");
+                    resp.getWriter().println("Ok");
+                    return true;
+                }
+
+                if (redirect) {
+                    String uri = req.getRequestURI();
+                    // Object pars =
+                    // req.getAttribute(Action.ATTR_REDIRECT_PARAMETERS);
+                    // if (pars instanceof String) {
+                    // uri += "?" + pars;
+                    // }
+                    resp.sendRedirect(uri);
+                }
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    protected Render getRender(HttpServletRequest request) {
+
+        String page = request.getRequestURI();
+
+        // remove trailing slashes
+        while (page.endsWith("/")) {
+            page = page.substring(0, page.length() - 1);
+        }
+
+        // take last part of the name
+        int lastSlash = page.lastIndexOf('/');
+        if (lastSlash >= 0) {
+            page = page.substring(lastSlash + 1);
+        }
+
+        Render render = this.renders.get(page);
+        return (render == null) ? this.defaultRender : render;
+    }
+
+    private String getParameter(HttpServletRequest request, String name) {
+        // just get the parameter if not a multipart/form-data POST
+        if (!ServletFileUpload.isMultipartContent(new ServletRequestContext(
+            request))) {
+            return request.getParameter(name);
+        }
+
+        // check, whether we alread have the parameters
+        @SuppressWarnings("unchecked")
+        Map<String, FileItem[]> params = (Map<String, FileItem[]>) request.getAttribute(Util.ATTR_FILEUPLOAD);
+        if (params == null) {
+            // parameters not read yet, read now
+            // Create a factory for disk-based file items
+            DiskFileItemFactory factory = new DiskFileItemFactory();
+            factory.setSizeThreshold(256000);
+
+            // Create a new file upload handler
+            ServletFileUpload upload = new ServletFileUpload(factory);
+            upload.setSizeMax(-1);
+
+            // Parse the request
+            params = new HashMap<String, FileItem[]>();
+            try {
+                @SuppressWarnings("unchecked")
+                List<FileItem> items = upload.parseRequest(request);
+                for (FileItem fi : items) {
+                    FileItem[] current = params.get(fi.getFieldName());
+                    if (current == null) {
+                        current = new FileItem[] { fi };
+                    } else {
+                        FileItem[] newCurrent = new FileItem[current.length + 1];
+                        System.arraycopy(current, 0, newCurrent, 0,
+                            current.length);
+                        newCurrent[current.length] = fi;
+                        current = newCurrent;
+                    }
+                    params.put(fi.getFieldName(), current);
+                }
+            } catch (FileUploadException fue) {
+                // TODO: log
+            }
+            request.setAttribute(Util.ATTR_FILEUPLOAD, params);
+        }
+
+        FileItem[] param = params.get(name);
+        if (param != null) {
+            for (int i = 0; i < param.length; i++) {
+                if (param[i].isFormField()) {
+                    return param[i].getString();
+                }
+            }
+        }
+
+        // no valid string parameter, fail
+        return null;
+    }
+
+    BundleContext getBundleContext() {
+        return bundleContext;
+    }
+
+    private static class HttpServiceTracker extends ServiceTracker {
+
+        private final SlingManager slingManager;
+
+        HttpServiceTracker(SlingManager slingManager) {
+            super(slingManager.getBundleContext(), HttpService.class.getName(),
+                null);
+            this.slingManager = slingManager;
+        }
+
+        @Override
+        public Object addingService(ServiceReference reference) {
+            Object operation = super.addingService(reference);
+            if (operation instanceof HttpService) {
+                slingManager.bindHttpService((HttpService) operation);
+            }
+            return operation;
+        }
+
+        @Override
+        public void removedService(ServiceReference reference, Object service) {
+            if (service instanceof HttpService) {
+                slingManager.unbindHttpService((HttpService) service);
+            }
+
+            super.removedService(reference, service);
+        }
+    }
+
+    private static class OperationServiceTracker extends ServiceTracker {
+
+        private final SlingManager slingManager;
+
+        OperationServiceTracker(SlingManager slingManager) {
+            super(slingManager.getBundleContext(), Action.SERVICE, null);
+            this.slingManager = slingManager;
+        }
+
+        @Override
+        public Object addingService(ServiceReference reference) {
+            Object operation = super.addingService(reference);
+            if (operation instanceof Action) {
+                slingManager.bindOperation((Action) operation);
+            }
+            return operation;
+        }
+
+        @Override
+        public void removedService(ServiceReference reference, Object service) {
+            if (service instanceof Action) {
+                slingManager.bindOperation((Action) service);
+            }
+
+            super.removedService(reference, service);
+        }
+    }
+
+    private static class RenderServiceTracker extends ServiceTracker {
+
+        private final SlingManager slingManager;
+
+        RenderServiceTracker(SlingManager slingManager) {
+            super(slingManager.getBundleContext(), Render.SERVICE, null);
+            this.slingManager = slingManager;
+        }
+
+        @Override
+        public Object addingService(ServiceReference reference) {
+            Object operation = super.addingService(reference);
+            if (operation instanceof Render) {
+                slingManager.bindRender((Render) operation);
+            }
+            return operation;
+        }
+
+        @Override
+        public void removedService(ServiceReference reference, Object service) {
+            if (service instanceof Render) {
+                slingManager.bindRender((Render) service);
+            }
+
+            super.removedService(reference, service);
+        }
+    }
+
+    protected synchronized void bindHttpService(HttpService httpService) {
+        Dictionary<String, Object> config = getConfiguration();
+
+        // get authentication details
+        String realm = this.getProperty(config, PROP_REALM,
+            "Sling Management Console");
+        String userId = this.getProperty(config, PROP_USER_NAME, null);
+        String password = this.getProperty(config, PROP_PASSWORD, null);
+
+        // register the servlet and resources
+        try {
+            HttpContext httpContext = new SlingHttpContext(httpService, realm,
+                userId, password);
+
+            Dictionary<String, String> servletConfig = toStringConfig(config);
+
+            // rest of sling
+            httpService.registerServlet(this.webManagerRoot, this,
+                servletConfig, httpContext);
+            httpService.registerResources(this.webManagerRoot + "/res", "/res",
+                httpContext);
+
+        } catch (Exception e) {
+            log.log(LogService.LOG_ERROR, "Problem setting up", e);
+        }
+
+        this.httpService = httpService;
+    }
+
+    protected synchronized void unbindHttpService(HttpService httpService) {
+        httpService.unregister(this.webManagerRoot + "/res");
+        httpService.unregister(this.webManagerRoot);
+
+        if (this.httpService == httpService) {
+            this.httpService = null;
+        }
+    }
+
+    protected void bindOperation(Action operation) {
+        this.operations.put(operation.getName(), operation);
+    }
+
+    protected void unbindOperation(Action operation) {
+        this.operations.remove(operation.getName());
+    }
+
+    protected void bindRender(Render render) {
+        this.renders.put(render.getName(), render);
+
+        if (this.defaultRender == null) {
+            this.defaultRender = render;
+        } else if (render.getName().equals(this.defaultRenderName)) {
+            this.defaultRender = render;
+        }
+    }
+
+    protected void unbindRender(Render render) {
+        this.renders.remove(render.getName());
+
+        if (this.defaultRender == render) {
+            if (this.renders.isEmpty()) {
+                this.defaultRender = null;
+            } else {
+                this.defaultRender = this.renders.values().iterator().next();
+            }
+        }
+    }
+
+    private Dictionary<String, Object> getConfiguration() {
+        return configuration;
+    }
+
+    void updateConfiguration(Dictionary<String, Object> config) {
+        if (config == null) {
+            config = new Hashtable<String, Object>();
+        }
+
+        configuration = config;
+
+        defaultRenderName = (String) config.get(PROP_DEFAULT_RENDER);
+        if (defaultRenderName != null && renders.get(defaultRenderName) != null) {
+            defaultRender = renders.get(defaultRenderName);
+        }
+
+        // get the web manager root path
+        webManagerRoot = this.getProperty(config, PROP_MANAGER_ROOT, DEFAULT_MANAGER_ROOT);
+        if (!webManagerRoot.startsWith("/")) {
+            webManagerRoot = "/" + webManagerRoot;
+        }
+
+        // might update http service registration
+        HttpService httpService = this.httpService;
+        if (httpService != null) {
+            synchronized (this) {
+                unbindHttpService(httpService);
+                bindHttpService(httpService);
+            }
+        }
+    }
+
+    /**
+     * Returns the named property from the configuration. If the property does
+     * not exist, the default value <code>def</code> is returned.
+     *
+     * @param config The properties from which to returned the named one
+     * @param name The name of the property to return
+     * @param def The default value if the named property does not exist
+     * @return The value of the named property as a string or <code>def</code>
+     *         if the property does not exist
+     */
+    private String getProperty(Dictionary<String, Object> config, String name,
+            String def) {
+        Object value = config.get(name);
+        if (value instanceof String) {
+            return (String) value;
+        }
+
+        if (value == null) {
+            return def;
+        }
+
+        return String.valueOf(value);
+    }
+
+    private Dictionary<String, String> toStringConfig(Dictionary<?, ?> config) {
+        Dictionary<String, String> stringConfig = new Hashtable<String, String>();
+        for (Enumeration<?> ke = config.keys(); ke.hasMoreElements();) {
+            Object key = ke.nextElement();
+            stringConfig.put(key.toString(), String.valueOf(config.get(key)));
+        }
+        return stringConfig;
+    }
+}
diff --git a/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/system/GCAction.java b/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/system/GCAction.java
new file mode 100644
index 0000000..9de94e6
--- /dev/null
+++ b/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/system/GCAction.java
@@ -0,0 +1,42 @@
+/*
+ * 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.sling.osgi.console.web.internal.system;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.sling.osgi.console.web.Action;
+
+public class GCAction implements Action {
+
+    public static final String NAME = "gc";
+    public static final String LABEL = "Collect Garbage";
+
+    public String getName() {
+        return NAME;
+    }
+
+    public String getLabel() {
+        return LABEL;
+    }
+
+    public boolean performAction(HttpServletRequest request, HttpServletResponse response) {
+        System.gc();
+        return false;
+    }
+
+}
diff --git a/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/system/ShutdownAction.java b/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/system/ShutdownAction.java
new file mode 100644
index 0000000..9e45bfb
--- /dev/null
+++ b/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/system/ShutdownAction.java
@@ -0,0 +1,66 @@
+/*
+ * 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.sling.osgi.console.web.internal.system;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.sling.osgi.console.web.Action;
+import org.apache.sling.osgi.console.web.internal.BaseManagementPlugin;
+import org.osgi.framework.BundleException;
+import org.osgi.service.log.LogService;
+
+public class ShutdownAction extends BaseManagementPlugin implements Action {
+
+    public static final String NAME = "shutdown";
+
+    public String getName() {
+        return NAME;
+    }
+
+    public String getLabel() {
+        return NAME;
+    }
+
+    public boolean performAction(HttpServletRequest request,
+            HttpServletResponse response) {
+        // simply terminate VM in case of shutdown :-)
+        Thread t = new Thread("Stopper") {
+            public void run() {
+                try {
+                    Thread.sleep(2000L);
+                } catch (InterruptedException ie) {
+                    // ignore
+                }
+
+                getLog().log(LogService.LOG_INFO, "Shutting down server now!");
+
+                // stopping bundle 0 (system bundle) stops the framework
+                try {
+                    getBundleContext().getBundle(0).stop();
+                } catch (BundleException be) {
+                    getLog().log(LogService.LOG_ERROR,
+                        "Problem stopping Framework", be);
+                }
+            }
+        };
+        t.start();
+
+        return true;
+    }
+
+}
diff --git a/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/system/ShutdownRender.java b/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/system/ShutdownRender.java
new file mode 100644
index 0000000..3eb16d5
--- /dev/null
+++ b/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/system/ShutdownRender.java
@@ -0,0 +1,56 @@
+/*
+ * 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.sling.osgi.console.web.internal.system;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.sling.osgi.console.web.Render;
+
+public class ShutdownRender implements Render {
+
+    public static final String NAME = "shutdown";
+    public static final String LABEL = null; // hide from navigation
+
+    public String getName() {
+        return NAME;
+    }
+
+    public String getLabel() {
+        return LABEL;
+    }
+
+    public void render(HttpServletRequest request, HttpServletResponse response)
+            throws IOException {
+
+        PrintWriter pw = response.getWriter();
+
+        pw.println("<tr>");
+        pw.println("<td colspan='2' class='techcontentcell'>");
+        pw.println("<table class='content' cellpadding='0' cellspacing='0' width='100%'>");
+        pw.println("<tr class='content'>");
+        pw.println("<th class='content important'>Server terminated</th>");
+        pw.println("</tr>");
+        pw.println("</table>");
+        pw.println("</td>");
+        pw.println("</tr>");
+    }
+
+}
diff --git a/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/system/VMStatRender.java b/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/system/VMStatRender.java
new file mode 100644
index 0000000..97ae8ef
--- /dev/null
+++ b/webconsole/src/main/java/org/apache/sling/osgi/console/web/internal/system/VMStatRender.java
@@ -0,0 +1,172 @@
+/*
+ * 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.sling.osgi.console.web.internal.system;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Date;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.sling.osgi.console.web.Render;
+import org.apache.sling.osgi.console.web.internal.BaseManagementPlugin;
+import org.apache.sling.osgi.console.web.internal.Util;
+import org.apache.sling.osgi.console.web.internal.core.SetStartLevelAction;
+
+public class VMStatRender extends BaseManagementPlugin implements Render {
+
+    public static final String NAME = "vmstat";
+
+    public static final String LABEL = "System Information";
+
+    private static final long startDate = (new Date()).getTime();
+
+    public String getName() {
+        return NAME;
+    }
+
+    public String getLabel() {
+        return LABEL;
+    }
+
+    public void render(HttpServletRequest request, HttpServletResponse response)
+            throws IOException {
+
+        PrintWriter pw = response.getWriter();
+
+        pw.println("");
+        boolean shutdown = false;
+
+        String target = request.getRequestURI();
+        if (request.getParameter(Util.PARAM_SHUTDOWN) != null) {
+            target = ShutdownRender.NAME;
+            shutdown = true;
+        }
+
+        pw.println("<table class='content' cellpadding='0' cellspacing='0' width='100%'>");
+
+        pw.println("<tr class='content'>");
+        pw.println("<th colspan='2' class='content container'>Start Level Information:</th>");
+        pw.println("</tr>");
+
+        pw.println("<tr class='content'>");
+        pw.println("<td class='content'>System Start Level</td>");
+        pw.println("<td class='content'>");
+        pw.println("<form method='post'>");
+        pw.println("<input type='hidden' name='" + Util.PARAM_ACTION
+            + "' value='" + SetStartLevelAction.NAME + "'>");
+        pw.println("<input class='input' type='text' size='3' name='systemStartLevel' value='"
+            + getStartLevel().getStartLevel() + "'/>");
+        pw.println("&nbsp;&nbsp;<input class='submit' type='submit' name='"
+            + SetStartLevelAction.LABEL + "' value='Change'>");
+        pw.println("</form>");
+        pw.println("</td>");
+        pw.println("</tr>");
+        pw.println("<tr class='content'>");
+        pw.println("<td class='content'>Default Bundle Start Level</td>");
+        pw.println("<td class='content'>");
+        pw.println("<form method='post'>");
+        pw.println("<input type='hidden' name='" + Util.PARAM_ACTION
+            + "' value='" + SetStartLevelAction.NAME + "'>");
+        pw.println("<input class='input' type='text' size='3' name='bundleStartLevel' value='"
+            + getStartLevel().getInitialBundleStartLevel() + "'/>");
+        pw.println("&nbsp;&nbsp;<input class='submit' type='submit' name='"
+            + SetStartLevelAction.LABEL + "' value='Change'>");
+        pw.println("</form>");
+        pw.println("</td>");
+        pw.println("</tr>");
+
+        pw.println("<tr class='content'>");
+        pw.println("<td colspan='2' class='content'>&nbsp;</th>");
+        pw.println("</tr>");
+
+        pw.println("<tr class='content'>");
+        pw.println("<th colspan='2' class='content container'>Server Information:</th>");
+        pw.println("</tr>");
+
+        pw.println("<tr class='content'>");
+        pw.println("<td class='content'>Last Started</td>");
+        pw.println("<td class='content'>");
+        pw.println("<script language='JavaScript'>");
+        pw.println("localDate(" + startDate /* <%= Server.getStartTime() %> */
+            + ")");
+        pw.println("</script>");
+        pw.println("</td>");
+        pw.println("</tr>");
+
+        pw.println("<tr class='content'>");
+        pw.println("<form name='shutdownform' method='post' action='" + target
+            + "'>");
+        pw.println("<td class='content'>Server</td>");
+        pw.println("<td class='content'>");
+
+        if (!shutdown) {
+            pw.println("<input type='hidden' name='" + Util.PARAM_SHUTDOWN
+                + "' value='" + Util.VALUE_SHUTDOWN + "'>");
+            pw.println("<input class='submit important' type='submit' value='Stop' onclick=\"return confirm('This will terminate all running applications. Do you want to stop the server?')\">");
+        } else {
+            pw.println("<input class='submit important' type='button' value='Abort' onclick=\"abort('"
+                + request.getRequestURI() + "')\">&nbsp;");
+            pw.println("<input type='hidden' name='" + Util.PARAM_ACTION
+                + "' value='" + ShutdownAction.NAME + "'>");
+            pw.println("Shutdown in <span id='countdowncell'>&nbsp;</span>");
+            pw.println("<script language='JavaScript'>");
+            pw.println("shutdown(3, 'shutdownform', 'countdowncell');");
+            pw.println("</script>");
+        }
+
+        pw.println("</td>");
+        pw.println("</form>");
+        pw.println("</tr>");
+
+        pw.println("<tr class='content'>");
+        pw.println("<td colspan='2' class='content'>&nbsp;</th>");
+        pw.println("</tr>");
+
+        pw.println("<tr class='content'>");
+        pw.println("<th colspan='2' class='content container'>Java Information:</th>");
+        pw.println("</tr>");
+
+        this.infoLine(pw, "Java Runtime", "ABOUT_JRT");
+        this.infoLine(pw, "Java Virtual Machine", "ABOUT_JVM");
+        this.infoLine(pw, "Total Memory", "ABOUT_MEM");
+        this.infoLine(pw, "Used Memory", "ABOUT_USED");
+        this.infoLine(pw, "Free Memory", "ABOUT_FREE");
+
+        pw.println("<tr class='content'>");
+        pw.println("<form method='post'>");
+        pw.println("<td class='content'>Garbage Collection</td>");
+        pw.println("<td class='content'>");
+        pw.println("<input type='hidden' name='" + Util.PARAM_ACTION
+            + "' value='" + GCAction.NAME + "'>");
+        pw.println("<input class='submit' type='submit' name='"
+            + GCAction.LABEL + "' value='Run'>");
+        pw.println("</form></td></tr>");
+
+        pw.println("</table>");
+    }
+
+    private void infoLine(PrintWriter pw, String label, String jsName) {
+        pw.println("<tr class='content'>");
+        pw.println("<td class='content'>" + label + "</td>");
+        pw.println("<td class='content'>");
+        pw.println("<script> document.write(" + jsName + "); </script>");
+        pw.println("</td></tr>");
+    }
+
+}
diff --git a/webconsole/src/main/resources/OSGI-INF/metatype/metatype.properties b/webconsole/src/main/resources/OSGI-INF/metatype/metatype.properties
new file mode 100644
index 0000000..be38663
--- /dev/null
+++ b/webconsole/src/main/resources/OSGI-INF/metatype/metatype.properties
@@ -0,0 +1,44 @@
+#
+#  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.
+#
+
+#
+# This file contains localization strings for configuration labels and
+# descriptions as used in the metatype.xml descriptor generated by the
+# the Sling SCR plugin
+
+manager.name = Sling Management Console
+manager.description = Configuration of the Sling Management Console.
+
+manager.root.name = Root URI
+manager.root.description = The root path to the Sling Management Console.
+
+realm.name = Realm
+realm.description = The name of the HTTP Authentication Realm.
+
+username.name = User Name
+username.description = The name of the user allowed to access the Sling \
+ Management Console. To disable authentication clear this value.
+
+password.name = Password
+password.description = The password for the user allowed to access the Sling \
+ Management Console.
+ 
+default.render.name = Default Page
+default.render.name.description = The name of the default configuration page \
+ when invoking the Sling Management console.
\ No newline at end of file
diff --git a/webconsole/src/main/resources/res/imgs/Sling.png b/webconsole/src/main/resources/res/imgs/Sling.png
new file mode 100644
index 0000000..e29d72b
--- /dev/null
+++ b/webconsole/src/main/resources/res/imgs/Sling.png
Binary files differ
diff --git a/webconsole/src/main/resources/res/imgs/element_add.png b/webconsole/src/main/resources/res/imgs/element_add.png
new file mode 100644
index 0000000..badd904
--- /dev/null
+++ b/webconsole/src/main/resources/res/imgs/element_add.png
Binary files differ
diff --git a/webconsole/src/main/resources/res/imgs/element_delete.png b/webconsole/src/main/resources/res/imgs/element_delete.png
new file mode 100644
index 0000000..2e41916
--- /dev/null
+++ b/webconsole/src/main/resources/res/imgs/element_delete.png
Binary files differ
diff --git a/webconsole/src/main/resources/res/imgs/element_run.png b/webconsole/src/main/resources/res/imgs/element_run.png
new file mode 100644
index 0000000..6dcd23b
--- /dev/null
+++ b/webconsole/src/main/resources/res/imgs/element_run.png
Binary files differ
diff --git a/webconsole/src/main/resources/res/imgs/element_stop.png b/webconsole/src/main/resources/res/imgs/element_stop.png
new file mode 100644
index 0000000..a89013d
--- /dev/null
+++ b/webconsole/src/main/resources/res/imgs/element_stop.png
Binary files differ
diff --git a/webconsole/src/main/resources/res/imgs/favicon.ico b/webconsole/src/main/resources/res/imgs/favicon.ico
new file mode 100644
index 0000000..a36e242
--- /dev/null
+++ b/webconsole/src/main/resources/res/imgs/favicon.ico
Binary files differ
diff --git a/webconsole/src/main/resources/res/ui/admin.css b/webconsole/src/main/resources/res/ui/admin.css
new file mode 100644
index 0000000..984d9a0
--- /dev/null
+++ b/webconsole/src/main/resources/res/ui/admin.css
@@ -0,0 +1,546 @@
+/*
+ * 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.
+ */ /* CSS Document */
+#main {
+    font-family: Verdana, Arial, Helvetica, sans-serif;
+    font-size: 10px;
+    color: black;
+    background-color: white;
+    border-collapse: collapse;
+    padding: 0px;
+    margin: 30px 0 30px 30px;
+    width: 957px;
+    position: absolute;
+    text-align: left;
+    border-color: black;
+}
+
+#lead {
+    color: #00678C;
+    /* color: #ffffff; */
+    margin: 0px 0px 26px 0px;
+    padding: 0px;
+    width: 957px;
+    height: 100px;
+}
+
+#lead h1 { /*
+    background-image: url(../imgs/banner_left.jpg);
+    background-repeat: no-repeat;
+    */
+    margin: 0px;
+    padding: 5px 0 0 8px;
+    font-size: 400%;
+    font-weight: bold;
+    line-height: 120%;
+    height: 95px;
+    /* account for 5px top marging to get a total of 100px */
+    float: left;
+}
+
+#lead br {
+    line-height: 20px;
+}
+
+#lead p { /*
+    background-image: url(../imgs/banner_right.jpg);
+    background-repeat: no-repeat;
+    border-left: 1px solid;
+        */
+    margin: 0px;
+    padding: 0px;
+    height: 100px;
+    float: right;
+}
+
+#technav {
+    border-bottom: 1px solid #6181A9;
+    border-top: 1px solid #6181A9;
+    color: black;
+    font-size: 10px;
+    font-weight: bold;
+    /*
+    border-top: 1px solid black;
+    */
+    height: 21px;
+    padding: 0;
+    margin: 0;
+}
+
+#technav a {
+    display: block;
+    float: left;
+    text-decoration: none;
+    padding: 3px 10px 3px 10px;
+    color: #6181A9;
+    text-decoration: none;
+}
+
+#technav a:hover {
+    background-color: black;
+}
+
+#technav .technavat {
+    display: block;
+    float: left;
+    text-decoration: none;
+    padding: 3px 10px 3px 10px;
+    background-color: #B6CAE4;
+    color: black;
+}
+
+#claim {
+    color: white;
+    background-color: black;
+    padding: 2px 10px 2px 10px;
+}
+
+#claim p {
+    margin: 0;
+    padding: 0;
+}
+
+#claim a {
+    color: #C5E2EE;
+    text-decoration: none;
+}
+
+/*
+.claimcell A:link {
+	color: #C5E2EE;
+	text-decoration: none;
+}
+
+.claimcell A:visited {
+	color: #C5E2EE;
+	text-decoration: none;
+}
+*/
+#claim a:hover {
+    color: #99FF33;
+    border-bottom: 1px solid;
+}
+
+#claim a:active {
+    color: #FFFFFF;
+}
+
+/* old styles */
+table {
+    text-align: left;
+}
+
+table.content {
+    clear: both;
+    font-size: 10px;
+    line-height: 13px;
+    border: 0px solid #66dd44;
+    border-top: 1px solid #cccccc;
+    padding: 0px;
+    margin: 0px;
+    margin-top: 26px;
+    margin-bottom: 26px;
+    /* width: 718px; */
+    text-align: left;
+}
+
+tr {
+    border-left: 1px solid #cccccc;  
+    border-right: 1px solid #cccccc;  
+}
+
+td.content {
+    color: #333333;
+    border: 0px solid #66dd44;
+    border-bottom: 1px solid #cccccc;
+    vertical-align: middle;
+    padding: 5px;
+}
+
+td.disabled {
+    color: #999999;
+}
+
+th.content {
+    color: #333333;
+    border: 0px solid #66dd44;
+    border-bottom: 1px solid #cccccc;
+    text-align: left;
+    padding: 5px;
+    padding-left: 10px;
+}
+
+.right {
+    text-align: right;
+}
+.center {
+    text-align: center;
+}
+
+th.container {
+    color: #6181A9;
+    background-color: #f0f0f0;
+}
+
+th.important {
+    color: #B81833;
+}
+
+.important {
+    color: #B81833;
+}
+
+#maintable { /* postion: absolute; */
+    font-size: 10px;
+    line-height: 13px;
+    border: 1px solid #000000;
+    padding: 0px;
+    margin: 0px;
+    width: 960px;
+}
+
+.toolcell {
+    font-size: 10px;
+    color: #999999;
+    line-height: 10px;
+    background-color: #000000;
+    height: 18px;
+    /* width: 960px; */
+    padding: 0px;
+    padding-bottom: 1px;
+    text-align: left;
+}
+
+.toolcell A:link {
+    color: #C5E2EE;
+    text-decoration: none;
+}
+
+.toolcell A:visited {
+    color: #C5E2EE;
+    text-decoration: none;
+}
+
+.toolcell A:hover {
+    color: #99FF33;
+    text-decoration: none;
+    border-bottom: 1px solid;
+}
+
+.toolcell A:active {
+    color: #FFFFFF;
+    text-decoration: none;
+}
+
+.leadcell {
+    margin: 0px;
+    padding: 0px;
+    border: 0px solid #000000;
+    width: 718px;
+    height: 100px;
+    background-image: url(../imgs/banner_left.jpg);
+    vertical-align: top;
+}
+
+.leadcelltext {
+    position: absolute;
+    border: 0px solid #000000;
+    font-size: 26px;
+    line-height: 32px;
+    margin-top: 5px;
+    margin-left: 8px;
+    color: #ffffff;
+    vertical-align: top;
+}
+
+.logocell {
+    border-left: 1px solid #000000;
+    width: 239px;
+    background-image: url(../imgs/banner_right.jpg);
+}
+
+#technavcell {
+    font-size: 10px;
+    font-weight: bold;
+    border: 0px solid #000000;
+    border-top: 1px solid #000000;
+    height: 21px;
+    background-color: #6181A9;
+}
+
+/*
+#technav A:link {
+	float: left;
+	display: block;
+	color: #ffffff;
+	border: 0px solid #000000;
+	border-right: 1px solid #000000;
+	height: 21px;
+	text-decoration: none;
+	padding: 0px 10px 0px 10px;
+	background-color: #6181A9;
+}
+
+#technav A:visited {
+	float: left;
+	display: block;
+	color: #ffffff;
+	border-right: 1px solid #000000;
+	height: 21px;
+	text-decoration: none;
+	padding: 0px 10px 0px 10px;
+	background-color: #6181A9;
+}
+
+#technav A:hover {
+	float: left;
+	display: block;
+	color: #ffffff;
+	border-right: 1px solid #000000;
+	height: 21px;
+	text-decoration: none;
+	padding: 0px 10px 0px 10px;
+	background-color: #000000;
+}
+
+#technav A:active {
+	float: left;
+	display: block;
+	color: #ffffff;
+	border-right: 1px solid #000000;
+	height: 21px;
+	text-decoration: none;
+	padding: 0px 10px 0px 10px;
+	background-color: #6181A9;
+}
+*/
+.techcontentcell { /* width: 718px; */
+    border: 0px solid #000000;
+    border-top: 1px solid #000000;
+}
+
+.relatedcell {
+    border-left: 1px solid #000000;
+    border-top: 1px solid #000000;
+    width: 239px;
+    background-color: #EBF0F6;
+}
+
+/*
+.claimcell {
+	width: 715px;
+	font-size: 10px;
+	color: #ffffff;
+	line-height: 10px;
+	background-color: #000000;
+	height: 18px;
+	padding: 0px;
+	padding-bottom: 1px;
+}
+
+.claimcell A:link {
+	color: #C5E2EE;
+	text-decoration: none;
+}
+
+.claimcell A:visited {
+	color: #C5E2EE;
+	text-decoration: none;
+}
+
+.claimcell A:hover {
+	color: #99FF33;
+	text-decoration: none;
+	border-bottom: 1px solid;
+}
+
+.claimcell A:active {
+	color: #FFFFFF;
+	text-decoration: none;
+}
+*/ /* CENTRAL CONTENT AREA STYLING */
+.content {
+    position: relative;
+    margin: 25px;
+    font-size: 11px;
+    line-height: 16px;
+    color: #000000;
+}
+
+.content A:link {
+    color: #336600;
+    text-decoration: underline;
+}
+
+.content A:visited {
+    color: #666666;
+    text-decoration: underline;
+}
+
+.content A:hover {
+    color: #ffffff;
+    background-color: #336600;
+    text-decoration: none;
+}
+
+.content A:active {
+    color: #ffffff;
+    background-color: #000000;
+    text-decoration: none;
+}
+
+body {
+	background-color: white;
+}
+
+/* TEXT STYLING */ /*
+h1, h2, h3, h4, h5, h6, p
+
+	{
+	margin: 0px;
+	margin-bottom: 8px;
+	font-size: 11px;
+	line-height: 16px;
+	font-weight:bold;
+	}
+
+*/ /*
+h1
+	{
+	color: #000000;
+	margin-top: 32px;
+	clear: left;
+	}
+
+
+h2
+	{
+	color: #336699;
+	}
+
+
+h3
+	{
+	color: #336699;
+	}
+*/ /*
+p
+	{
+	font-size: 11px;
+	line-height: 16px;
+	font-weight: normal;
+	color: #000000;
+	}
+*/ /* FORMS */
+form {
+    font-size: 9px;
+    border-top: 0px solid #000000;
+    border-bottom: 0px solid #000000;
+    border-left: 0px solid #000000;
+    border-right: 0px solid #000000;
+    margin: 0;
+    padding: 0;
+    clear: left;
+}
+
+select,textarea {
+    font-family: Verdana, Arial, Helvetica, san-serif;
+    font-size: 9px;
+    font-weight: normal;
+    display: block;
+    float: left;
+    padding-top: 3px;
+    margin-bottom: 10px;
+}
+
+.input {
+    font-family: Verdana, Arial, Helvetica, san-serif;
+    font-size: 9px;
+    font-weight: normal;
+    color: #184054;
+    background-color: #f0f0f0;
+    border: 1px solid #999999;
+    border-bottom: 1px solid #cccccc;
+    border-right: 1px solid #cccccc;
+}
+
+.submit {
+    cursor: default;
+    width: 60px;
+    font-family: Verdana, Arial, Helvetica, san-serif;
+    font-size: 10px;
+    font-weight: bold;
+    background-color: #f0f0f0;
+    height: 20px;
+    padding: 0px;
+    padding-bottom: 1px;
+    margin: 0px;
+    border: 1px solid #cccccc;
+    border-bottom: 1px solid #666666;
+    border-right: 1px solid #666666;
+}
+
+input.important {
+    background-color: #B81833;
+    color: #ffffff;
+}
+
+select {
+    color: #184054;
+    background-color: #f0f0f0;
+    border: 0px solid #999999;
+}
+
+textarea {
+    color: #184054;
+    background-color: #f0f0f0;
+    width: 234px;
+    height: 100px;
+    border: 1px solid #999999;
+    border-bottom: 1px solid #cccccc;
+    border-right: 1px solid #cccccc;
+}
+
+.clearleft {
+    clear: left;
+}
+
+.clearboth {
+    clear: both;
+}
+
+.checkradio {
+    background-color: #ffffff;
+    width: 20px;
+    padding: 0px;
+    padding-bottom: 10px;
+    margin: 0px;
+    margin-top: 2px;
+    border: 0px solid #999999;
+}
+
+.checkradiotext {
+    font-size: 9px;
+    font-weight: normal;
+    line-height: 11px;
+    width: 100px;
+    color: #000000;
+    padding: 0px;
+    padding-bottom: 10px;
+    margin: 0px;
+    margin-top: 4px;
+    border: 0px solid #999999;
+}
\ No newline at end of file
diff --git a/webconsole/src/main/resources/res/ui/admin.js b/webconsole/src/main/resources/res/ui/admin.js
new file mode 100644
index 0000000..3fe3697
--- /dev/null
+++ b/webconsole/src/main/resources/res/ui/admin.js
@@ -0,0 +1,152 @@
+/*
+ * 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.
+ */
+
+
+/* shuts down server after [num] seconds */
+function shutdown(num, formname, elemid) {
+	var elem;
+	var canCount = document.getElementById;
+	if (canCount) {
+	    elem = document.getElementById(elemid);
+	    canCount = (typeof(elem) != "undefined" && typeof(elem.innerHTML) != "undefined");
+	}
+	var secs=" second";
+	var ellipsis="...";
+	if (num > 0) {
+		if (num != 1) {
+			secs+="s";
+		}
+		if (canCount) {
+		    elem.innerHTML=num+secs+ellipsis;
+		}
+		setTimeout('shutdown('+(--num)+', "'+formname+'", "'+elemid+'")',1000);
+	} else {
+	    document[formname].submit();
+	}
+}
+
+/* aborts server shutdown and redirects to [target] */
+function abort(target) {
+    top.location.href=target;
+}
+
+/* checks if values of [pass1] and [pass2] match */
+function checkPasswd(form, pass0, pass1, pass2) {
+    var check = false;
+    check = (form[pass0].value != form[pass1].value);
+    if (!check) {
+        alert("Old and new password must be different.");
+        form[pass1].value="";
+        form[pass2].value="";
+        form[pass1].focus();
+    }
+    check = (form[pass1].value == form[pass2].value);
+    if (!check) {
+        alert("Passwords did not match. Please type again.");
+        form[pass1].value="";
+        form[pass2].value="";
+        form[pass1].focus();
+    }
+    return check;
+}
+
+/* displays a date in the user's local timezone */
+function localDate(time) {
+    var date = time ? new Date(time) : new Date();
+    document.write(date.toLocaleString());
+}
+
+/* shows the about screen  */
+function showAbout() {
+// Temporarily disabled, as thee is no about.html page (fmeschbe, 20070330)
+//    var arguments = ABOUT_VERSION+";"+ABOUT_JVERSION+";"+ABOUT_MEM+";"+ABOUT_USED+";"+ABOUT_FREE;
+//    if (window.showModalDialog) {
+//        window.showModalDialog("about.html", arguments, "help: no; status: no; resizable: no; center: yes; scroll: no");
+//    } else {
+//        aboutWin = window.open("about.html?a="+arguments, "about", "width=500,height=420,modal,status=no,toolbar=no,menubar=no,personalbar=no");
+//    }
+}
+
+//-----------------------------------------------------------------------------
+// Ajax Support
+
+// request object, do not access directly, use getXmlHttp instead
+var xmlhttp = null;
+function getXmlHttp() {
+	if (xmlhttp) {
+		return xmlhttp;
+	}
+	
+	if (window.XMLHttpRequest) {
+		xmlhttp = new XMLHttpRequest();
+	} else if (window.ActiveXObject) {
+		try {
+			xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
+		} catch (ex) {
+			try {
+				xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
+			} catch (ex) {
+			}
+		}
+	}
+	
+	return xmlhttp;
+}
+
+function sendRequest(/* String */ method, /* url */ url, /* function */ callback) {
+    var xmlhttp = getXmlHttp();
+    if (!xmlhttp) {
+        return;
+    }
+    
+    if (xmlhttp.readyState < 4) {
+    	xmlhttp.abort();
+  	}
+  	
+  	if (!method) {
+  		method = 'GET';
+  	}
+  	
+  	if (!url) {
+  		url = document.location;
+  	} else if (url.charAt(0) == '?') {
+  		url = document.location + url;
+  	}
+  	
+    xmlhttp.open(method, url);
+    xmlhttp.onreadystatechange = handleResult;
+    xmlhttp.priv_callback = callback;
+    xmlhttp.send(null);
+  	
+}
+
+function handleResult() {
+    var xmlhttp = getXmlHttp();
+    if (!xmlhttp || xmlhttp.readyState != 4) {
+        return;
+    }
+    
+    var result = xmlhttp.responseText;
+    if (!result) {
+        return;
+    }
+
+	if (xmlhttp.priv_callback) {
+	    var obj = eval('(' + result + ')');
+	    xmlhttp.priv_callback(obj);
+	}
+}
diff --git a/webconsole/src/main/resources/res/ui/configmanager.js b/webconsole/src/main/resources/res/ui/configmanager.js
new file mode 100644
index 0000000..d15aa17
--- /dev/null
+++ b/webconsole/src/main/resources/res/ui/configmanager.js
@@ -0,0 +1,229 @@
+/*
+ * 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.
+ */
+
+
+function configure() {
+    var span = document.getElementById('configField');
+    if (!span) {
+        return;
+    }
+    var select = document.configSelection.pid;
+    var pid = select.options[select.selectedIndex].value;
+    var parm = '?action=ajaxConfigManager&' + pid;
+    sendRequest('GET', parm, displayConfigForm);
+}
+
+function displayConfigForm(obj) {
+    var span = document.getElementById('configField');
+    if (!span) {
+        return;
+    }
+    var innerHtml = '<tr class="content" id="configField">' + span.innerHTML + '</tr>';
+    innerHtml += '<tr class="content">';
+    innerHtml += '<th colspan="2" class="content" >' + obj.title + '</th></tr>';
+    innerHtml += '<tr class="content">';
+    innerHtml += '<td class="content">&nbsp;</td>';
+    innerHtml += '<td class="content">';
+    innerHtml += '<form method="post">';
+    innerHtml += '<input type="hidden" name="apply" value="true" />';
+    innerHtml += '<input type="hidden" name="pid" value="' + obj.pid + '" />';
+    innerHtml += '<input type="hidden" name="action" value="ajaxConfigManager" />';
+    innerHtml += '<table border="0" width="100%">';
+    if (obj.description) {
+        innerHtml += '<tr class="content">';
+        innerHtml += '<td class="content" colspan="2">' + obj.description + '</td></tr>';
+    }
+    if (obj.propertylist == 'properties') {
+        innerHtml += printTextArea(obj.properties);
+    } else {
+        innerHtml += printForm(obj);
+    }
+    innerHtml += '<tr class="content">';
+    innerHtml += '<td class="content">&nbsp;</td>';
+    innerHtml += '<td class="content">';
+    innerHtml += '<input type="submit" class="submit" name="submit" value="Save" />';
+    innerHtml += '&nbsp;&nbsp;&nbsp;';
+    innerHtml += '<input type="reset" class="submit" name="reset" value="Reset" />';
+    innerHtml += '&nbsp;&nbsp;&nbsp;';
+    innerHtml += '<input type="submit" class="submit" name="delete" value="Delete" onClick="return confirmDelete();"/>';
+    if (obj.isFactory) {
+        innerHtml += '&nbsp;&nbsp;&nbsp;';
+        innerHtml += '<input type="submit" class="submit" name="create" value="Create Configuration" onClick="return promptContext(this);" />';
+        innerHtml += '<input type="hidden" name="sling.context" value="" />';
+    }
+    innerHtml += '</td></tr>';
+    innerHtml += '</table>';
+    innerHtml += '</form>';
+    innerHtml += '</td></tr>';
+    innerHtml += printConfigurationInfo(obj);
+    span.parentNode.innerHTML = innerHtml;
+}
+
+function printTextArea(props) {
+    var innerHtml = '<tr class="content">';
+    innerHtml += '<td class="content" style="vertical-align: top">Properties</td>';
+    innerHtml += '<td class="content" style="width: 99%">';
+    innerHtml += '<textarea name="properties" style="height: 50%; width: 99%">';
+    for (var key in props) {
+        innerHtml += key + ' =  ' + props[key] + '\r\n';
+    }
+    innerHtml += '</textarea>';
+    innerHtml += 'Enter Name-Value pairs of configuration properties.</td>';
+    return innerHtml;
+}
+
+function printForm(obj) {
+    var innerHtml = '';
+    var propList;
+    for (var idx in obj.propertylist) {
+        var prop = obj.propertylist[idx];
+        var attr = obj[prop];
+        innerHtml += '<tr class="content">';
+        innerHtml += '<td class="content" style="vertical-align: top">' + attr.name + '</td>';
+        innerHtml += '<td class="content" style="width: 99%">';
+        if (attr.value != undefined) { // check is required to also handle empty strings, 0 and false
+            innerHtml += createInput(prop, attr.value, attr.type, '99%');
+            innerHtml += '<br />';
+        } else if (typeof(attr.type) == 'object') {
+        	// assume attr.values and multiselect
+        	innerHtml += createMultiSelect(prop, attr.values, attr.type, '99%');
+            innerHtml += '<br />';
+        } else {
+            for (var vidx in attr.values) {
+                var spanElement = createSpan(prop, attr.values[vidx], attr.type);
+                innerHtml += '<span id="' + spanElement.id + '">';
+                innerHtml += spanElement.innerHTML;
+                innerHtml += '</span>';
+            }
+        }
+        if (attr.description) {
+            innerHtml += attr.description;
+        }
+        innerHtml += '</td>';
+        if (propList) {
+            propList += ',' + prop;
+        } else {
+            propList = prop;
+        }
+    }
+    innerHtml += '<input type="hidden" name="propertylist" value="' + propList + '"/>';
+    return innerHtml;
+}
+
+function printConfigurationInfo(obj) {
+    var innerHtml = '<tr class="content">';
+    innerHtml += '<th colspan="2" class="content" >Configuration Information</th></tr>';
+    innerHtml += '<tr class="content">';
+    innerHtml += '<td class="content">Persistent Identity (PID)</td>';
+    innerHtml += '<td class="content">' + obj.pid + '</td></tr>';
+    if (obj.factoryPID) {
+        innerHtml += '<tr class="content">';
+        innerHtml += '<td class="content">Factory Peristent Identifier (Factory PID)</td>';
+        innerHtml += '<td class="content">' + obj.factoryPID + '</td></tr>';
+    }
+    innerHtml += '<tr class="content">';
+    innerHtml += '<td class="content">Configuration Binding</td>';
+    innerHtml += '<td class="content">' + obj.bundleLocation + '</td></tr>';
+    return innerHtml;
+}
+
+function addValue(prop, vidx) {
+    var span = document.getElementById(vidx);
+    if (!span) {
+        return;
+    }
+    var newSpan = createSpan(prop, '');
+    span.parentNode.insertBefore(newSpan, span.nextSibling);
+}
+
+var spanCounter = 0;
+function createSpan(prop, value, type) {
+    spanCounter++;
+    var newId = prop + spanCounter;
+    var innerHtml = createInput(prop, value, type, '89%');
+    innerHtml += '<input class="input" type="button" value="+" onClick="addValue(\'' + prop + '\',\'' + newId + '\');" style="width: 5%" />';
+    innerHtml += '<input class="input" type="button" value="-" onClick="removeValue(\'' + newId + '\');" style="width: 5%" />';
+    innerHtml += '<br />';
+    var newSpan = document.createElement('span');
+    newSpan.id = newId;
+    newSpan.innerHTML = innerHtml;
+    return newSpan;
+}
+
+function createInput(prop, value, type, width) {
+    if (type == 11) { // AttributeDefinition.BOOLEAN
+        if (value && typeof(value) != "boolean") {
+            value = value.toString().toLowerCase() == "true";
+        }
+        var checked = value ? 'checked' : '';
+        return '<input class="input" type="checkbox" name="' + prop + '" value="true" ' + checked + '/>';
+    } else if (typeof(type) == "object") { // predefined values
+    	var labels = type.labels;
+    	var values = type.values;
+    	var innerHtml = '<select class="select" name="' + prop + '" style="width: ' + width + '">';
+    	for (var idx in labels) {
+    		var selected = (value == values[idx]) ? ' selected' : '';
+    		innerHtml += '<option value="' + values[idx] + '"' + selected + '>' + labels[idx] + '</option>';
+    	}
+    	innerHtml += '</select>';
+    	return innerHtml;
+    } else { // Simple 
+        return '<input class="input" type="text" name="' + prop + '" value="' + value + '" style="width: ' + width + '"/>';
+    }
+}
+
+function createMultiSelect(prop, values, options, width) {
+    // convert value list into 'set'
+    var valueSet = new Object();
+    for (var idx in values) {
+    	valueSet[ values[idx] ] = true;
+    }
+    
+   	var labels = options.labels;
+   	var values = options.values;
+   	var innerHtml = '';
+   	for (var idx in labels) {
+   		var checked = valueSet[ values[idx] ] ? ' checked' : '';
+   		innerHtml += '<label><input type="checkbox" name="' + prop + '" value="' + values[idx] + '"' + checked + '>' + labels[idx] + '</label>';
+   	}
+   	return innerHtml;
+}
+
+function removeValue(vidx) {
+    var span = document.getElementById(vidx);
+    if (!span) {
+        return;
+    }
+    span.parentNode.removeChild(span);
+}
+
+function confirmDelete() {
+    return confirm("Are you sure to delete this configuration ?");
+}
+
+function promptContext(form) {
+    var result = prompt("Please give a Sling Context for the new configuration");
+    // alert("You entered: [" + result + "] for form [" + form + "/" + form.form + "]");
+    
+    // set the hidden sling.context input field with the value
+    if (result != null) {
+        form.form["sling.context"].value = result;
+        return true;
+    }
+    
+    return false;
+}
\ No newline at end of file