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'> </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'> </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\"> </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(" ");
+ 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'> </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'> </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\"> </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'> </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'> </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(" ");
+ 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'> </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'> </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(" ");
+ 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(" <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(" <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'> </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() + "')\"> ");
+ pw.println("<input type='hidden' name='" + Util.PARAM_ACTION
+ + "' value='" + ShutdownAction.NAME + "'>");
+ pw.println("Shutdown in <span id='countdowncell'> </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'> </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"> </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"> </td>';
+ innerHtml += '<td class="content">';
+ innerHtml += '<input type="submit" class="submit" name="submit" value="Save" />';
+ innerHtml += ' ';
+ innerHtml += '<input type="reset" class="submit" name="reset" value="Reset" />';
+ innerHtml += ' ';
+ innerHtml += '<input type="submit" class="submit" name="delete" value="Delete" onClick="return confirmDelete();"/>';
+ if (obj.isFactory) {
+ innerHtml += ' ';
+ 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