You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@felix.apache.org by vv...@apache.org on 2010/03/22 07:52:07 UTC

svn commit: r925979 - in /felix/trunk/webconsole-plugins/upnp: ./ src/ src/main/ src/main/java/ src/main/java/org/ src/main/java/org/apache/ src/main/java/org/apache/felix/ src/main/java/org/apache/felix/webconsole/ src/main/java/org/apache/felix/webco...

Author: vvalchev
Date: Mon Mar 22 06:52:06 2010
New Revision: 925979

URL: http://svn.apache.org/viewvc?rev=925979&view=rev
Log:
Resolved FELIX-2121
Initial import of ProSyst donation of UPnP plugin for the web console

Added:
    felix/trunk/webconsole-plugins/upnp/
    felix/trunk/webconsole-plugins/upnp/LICENSE
    felix/trunk/webconsole-plugins/upnp/NOTICE
    felix/trunk/webconsole-plugins/upnp/pom.xml
    felix/trunk/webconsole-plugins/upnp/src/
    felix/trunk/webconsole-plugins/upnp/src/main/
    felix/trunk/webconsole-plugins/upnp/src/main/java/
    felix/trunk/webconsole-plugins/upnp/src/main/java/org/
    felix/trunk/webconsole-plugins/upnp/src/main/java/org/apache/
    felix/trunk/webconsole-plugins/upnp/src/main/java/org/apache/felix/
    felix/trunk/webconsole-plugins/upnp/src/main/java/org/apache/felix/webconsole/
    felix/trunk/webconsole-plugins/upnp/src/main/java/org/apache/felix/webconsole/plugins/
    felix/trunk/webconsole-plugins/upnp/src/main/java/org/apache/felix/webconsole/plugins/upnp/
    felix/trunk/webconsole-plugins/upnp/src/main/java/org/apache/felix/webconsole/plugins/upnp/internal/
    felix/trunk/webconsole-plugins/upnp/src/main/java/org/apache/felix/webconsole/plugins/upnp/internal/Activator.java
    felix/trunk/webconsole-plugins/upnp/src/main/java/org/apache/felix/webconsole/plugins/upnp/internal/ControlServlet.java
    felix/trunk/webconsole-plugins/upnp/src/main/java/org/apache/felix/webconsole/plugins/upnp/internal/SessionObject.java
    felix/trunk/webconsole-plugins/upnp/src/main/java/org/apache/felix/webconsole/plugins/upnp/internal/WebConsolePlugin.java
    felix/trunk/webconsole-plugins/upnp/src/main/native2ascii/
    felix/trunk/webconsole-plugins/upnp/src/main/native2ascii/OSGI-INF/
    felix/trunk/webconsole-plugins/upnp/src/main/native2ascii/OSGI-INF/l10n/
    felix/trunk/webconsole-plugins/upnp/src/main/native2ascii/OSGI-INF/l10n/bundle_bg.properties
    felix/trunk/webconsole-plugins/upnp/src/main/resources/
    felix/trunk/webconsole-plugins/upnp/src/main/resources/OSGI-INF/
    felix/trunk/webconsole-plugins/upnp/src/main/resources/OSGI-INF/l10n/
    felix/trunk/webconsole-plugins/upnp/src/main/resources/OSGI-INF/l10n/bundle.properties
    felix/trunk/webconsole-plugins/upnp/src/main/resources/res/
    felix/trunk/webconsole-plugins/upnp/src/main/resources/res/jquery-treeview-1.4/
    felix/trunk/webconsole-plugins/upnp/src/main/resources/res/jquery-treeview-1.4/images/
    felix/trunk/webconsole-plugins/upnp/src/main/resources/res/jquery-treeview-1.4/images/minus.gif   (with props)
    felix/trunk/webconsole-plugins/upnp/src/main/resources/res/jquery-treeview-1.4/images/plus.gif   (with props)
    felix/trunk/webconsole-plugins/upnp/src/main/resources/res/jquery-treeview-1.4/images/treeview-default-line.gif   (with props)
    felix/trunk/webconsole-plugins/upnp/src/main/resources/res/jquery-treeview-1.4/images/treeview-default.gif   (with props)
    felix/trunk/webconsole-plugins/upnp/src/main/resources/res/jquery-treeview-1.4/jquery.treeview.css
    felix/trunk/webconsole-plugins/upnp/src/main/resources/res/jquery-treeview-1.4/jquery.treeview.pack.js
    felix/trunk/webconsole-plugins/upnp/src/main/resources/res/loading.gif   (with props)
    felix/trunk/webconsole-plugins/upnp/src/main/resources/res/upnp.css
    felix/trunk/webconsole-plugins/upnp/src/main/resources/res/upnp.html
    felix/trunk/webconsole-plugins/upnp/src/main/resources/res/upnp.js

Added: felix/trunk/webconsole-plugins/upnp/LICENSE
URL: http://svn.apache.org/viewvc/felix/trunk/webconsole-plugins/upnp/LICENSE?rev=925979&view=auto
==============================================================================
--- felix/trunk/webconsole-plugins/upnp/LICENSE (added)
+++ felix/trunk/webconsole-plugins/upnp/LICENSE Mon Mar 22 06:52:06 2010
@@ -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.

Added: felix/trunk/webconsole-plugins/upnp/NOTICE
URL: http://svn.apache.org/viewvc/felix/trunk/webconsole-plugins/upnp/NOTICE?rev=925979&view=auto
==============================================================================
--- felix/trunk/webconsole-plugins/upnp/NOTICE (added)
+++ felix/trunk/webconsole-plugins/upnp/NOTICE Mon Mar 22 06:52:06 2010
@@ -0,0 +1,28 @@
+Apache Felix OSGi Web Console UPnP Plugin
+Copyright 2007-2010 The Apache Software Foundation
+
+
+I. Included Software
+
+This product includes software developed at
+The Apache Software Foundation (http://www.apache.org/).
+Licensed under the Apache License 2.0.
+
+
+II. Used Software
+
+This product uses software developed at
+The OSGi Alliance (http://www.osgi.org/).
+Copyright (c) OSGi Alliance (2000, 2009).
+Licensed under the Apache License 2.0.
+
+This product includes software from http://bassistance.de/jquery-plugins/jquery-plugin-treeview/
+Copyright (c) 2007 Jorn Zaefferer
+Licensed under the MIT License
+
+This product is based on original source code developer by ProSyst (http://www.prosyst.com/)
+
+
+III. License Summary
+- Apache License 2.0
+- MIT License

Added: felix/trunk/webconsole-plugins/upnp/pom.xml
URL: http://svn.apache.org/viewvc/felix/trunk/webconsole-plugins/upnp/pom.xml?rev=925979&view=auto
==============================================================================
--- felix/trunk/webconsole-plugins/upnp/pom.xml (added)
+++ felix/trunk/webconsole-plugins/upnp/pom.xml Mon Mar 22 06:52:06 2010
@@ -0,0 +1,132 @@
+<!--
+    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.felix</groupId>
+        <artifactId>felix-parent</artifactId>
+        <version>1.2.0</version>
+        <relativePath>../../../pom/pom.xml</relativePath>
+    </parent>
+
+    <artifactId>org.apache.felix.webconsole.plugins.upnp</artifactId>
+    <packaging>bundle</packaging>
+    <version>1.0.0-SNAPSHOT</version>
+
+    <name>Apache Felix Web Console UPnP Plugin</name>
+    <description>
+        This is a plugin for the Apache Felix OSGi web console for displaying
+        UPnP devices.
+    </description>
+
+    <scm>
+        <connection>scm:svn:http://svn.apache.org/repos/asf/felix/trunk/webconsole-plugins/upnp</connection>
+        <developerConnection>scm:svn:https://svn.apache.org/repos/asf/felix/trunk/webconsole-plugins/upnp</developerConnection>
+        <url>http://svn.apache.org/viewvc/felix/trunk/webconsole-plugins/upnp</url>
+    </scm>
+
+    <build>
+        <plugins>
+            <!-- translate UTF-8 encoded properties files to ISO-8859-1 -->
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>native2ascii-maven-plugin</artifactId>
+                <version>1.0-alpha-1</version>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>native2ascii</goal>
+                        </goals>
+                        <configuration>
+                            <encoding>UTF-8</encoding>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <version>2.0.1</version>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <Bundle-SymbolicName>
+                            ${artifactId}
+                        </Bundle-SymbolicName>
+                        <Bundle-Vendor>
+                            The Apache Software Foundation
+                        </Bundle-Vendor>
+                        <Bundle-Activator>
+                            org.apache.felix.webconsole.plugins.upnp.internal.Activator
+                        </Bundle-Activator>
+                        <Private-Package>
+                            org.apache.felix.webconsole.plugins.upnp.*
+                        </Private-Package>
+                        <Import-Package>
+							org.osgi.framework;
+							org.osgi.service.upnp;version="1.1",
+							org.osgi.util.tracker; version="1.0.0",
+							org.apache.felix.webconsole; version="3.0.0",
+							javax.servlet; version="2.4",
+							javax.servlet.http; version="2.4",
+							org.json						
+                        </Import-Package>
+                    </instructions>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>servlet-api</artifactId>
+            <version>2.4</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.core</artifactId>
+            <version>4.0.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.compendium</artifactId>
+            <version>4.0.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.webconsole</artifactId>
+            <version>2.0.7-SNAPSHOT</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.json</groupId>
+            <artifactId>json</artifactId>
+            <version>20070829</version>
+            <scope>compile</scope>
+            <optional>true</optional>
+        </dependency>
+    </dependencies>
+</project>

Added: felix/trunk/webconsole-plugins/upnp/src/main/java/org/apache/felix/webconsole/plugins/upnp/internal/Activator.java
URL: http://svn.apache.org/viewvc/felix/trunk/webconsole-plugins/upnp/src/main/java/org/apache/felix/webconsole/plugins/upnp/internal/Activator.java?rev=925979&view=auto
==============================================================================
--- felix/trunk/webconsole-plugins/upnp/src/main/java/org/apache/felix/webconsole/plugins/upnp/internal/Activator.java (added)
+++ felix/trunk/webconsole-plugins/upnp/src/main/java/org/apache/felix/webconsole/plugins/upnp/internal/Activator.java Mon Mar 22 06:52:06 2010
@@ -0,0 +1,109 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.felix.webconsole.plugins.upnp.internal;
+
+import org.apache.felix.webconsole.SimpleWebConsolePlugin;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.upnp.UPnPDevice;
+import org.osgi.util.tracker.ServiceTracker;
+import org.osgi.util.tracker.ServiceTrackerCustomizer;
+
+/**
+ * Activator is the main starting class.
+ */
+public class Activator implements BundleActivator, ServiceTrackerCustomizer
+{
+
+    private ServiceTracker tracker;
+    private BundleContext context;
+
+    private SimpleWebConsolePlugin plugin;
+
+    /**
+     * @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext)
+     */
+    public final void start(BundleContext context) throws Exception
+    {
+        this.context = context;
+        this.tracker = new ServiceTracker(context, UPnPDevice.class.getName(), this);
+        this.tracker.open();
+    }
+
+    /**
+     * @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
+     */
+    public final void stop(BundleContext context) throws Exception
+    {
+        if (tracker != null)
+        {
+            tracker.close();
+            tracker = null;
+        }
+    }
+
+    // - begin tracker
+    /**
+     * @see org.osgi.util.tracker.ServiceTrackerCustomizer#modifiedService(org.osgi.framework.ServiceReference,
+     *      java.lang.Object)
+     */
+    public final void modifiedService(ServiceReference reference, Object service)
+    {/* unused */
+    }
+
+    /**
+     * @see org.osgi.util.tracker.ServiceTrackerCustomizer#addingService(org.osgi.framework.ServiceReference)
+     */
+    public final Object addingService(ServiceReference reference)
+    {
+        SimpleWebConsolePlugin plugin = this.plugin;
+        if (plugin == null)
+        {
+            this.plugin = plugin = new WebConsolePlugin(tracker).register(context);
+        }
+        // delegate event
+        ControlServlet controller = ((WebConsolePlugin) plugin).controller;
+        if (controller != null)
+            controller.addingService(reference);
+
+        return context.getService(reference);
+    }
+
+    /**
+     * @see org.osgi.util.tracker.ServiceTrackerCustomizer#removedService(org.osgi.framework.ServiceReference,
+     *      java.lang.Object)
+     */
+    public final void removedService(ServiceReference reference, Object service)
+    {
+        SimpleWebConsolePlugin plugin = this.plugin;
+
+        if (plugin != null)
+        {
+            ControlServlet controller = ((WebConsolePlugin) plugin).controller;
+            if (controller != null)
+                controller.removedService(reference, service);
+        }
+
+        if (tracker.getTrackingCount() == 0 && plugin != null)
+        {
+            plugin.unregister();
+            this.plugin = null;
+        }
+
+    }
+}

Added: felix/trunk/webconsole-plugins/upnp/src/main/java/org/apache/felix/webconsole/plugins/upnp/internal/ControlServlet.java
URL: http://svn.apache.org/viewvc/felix/trunk/webconsole-plugins/upnp/src/main/java/org/apache/felix/webconsole/plugins/upnp/internal/ControlServlet.java?rev=925979&view=auto
==============================================================================
--- felix/trunk/webconsole-plugins/upnp/src/main/java/org/apache/felix/webconsole/plugins/upnp/internal/ControlServlet.java (added)
+++ felix/trunk/webconsole-plugins/upnp/src/main/java/org/apache/felix/webconsole/plugins/upnp/internal/ControlServlet.java Mon Mar 22 06:52:06 2010
@@ -0,0 +1,492 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.felix.webconsole.plugins.upnp.internal;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.reflect.Constructor;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Iterator;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.upnp.UPnPAction;
+import org.osgi.service.upnp.UPnPDevice;
+import org.osgi.service.upnp.UPnPIcon;
+import org.osgi.service.upnp.UPnPLocalStateVariable;
+import org.osgi.service.upnp.UPnPService;
+import org.osgi.service.upnp.UPnPStateVariable;
+import org.osgi.util.tracker.ServiceTracker;
+import org.osgi.util.tracker.ServiceTrackerCustomizer;
+
+/**
+ * This class handles requests from the Web Interface. It is separated from
+ * the WebConsolePlugin just to improve readability. This servlet actually
+ * is not registered in HTTP service.
+ */
+public class ControlServlet extends HttpServlet implements ServiceTrackerCustomizer
+{
+
+    private static final SimpleDateFormat DATA_FORMAT = new SimpleDateFormat(
+        "EEE, d MMM yyyy HH:mm:ss Z");
+
+    final HashMap icons = new HashMap(10);
+    final HashMap sessions = new HashMap(10);
+
+    private ServiceTracker tracker;
+    private final BundleContext bc;
+
+    private static final long LAST_MODIFIED = System.currentTimeMillis();
+
+    /**
+     * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest,
+     *      javax.servlet.http.HttpServletResponse)
+     */
+    protected void doGet(HttpServletRequest request, HttpServletResponse response)
+        throws ServletException, IOException
+    {
+
+        String udn = request.getParameter("icon");
+
+        if (udn != null)
+        {
+            UPnPIcon icon = (UPnPIcon) icons.get(udn);
+            if (icon == null)
+            {
+                response.sendError(HttpServletResponse.SC_NOT_FOUND);
+            }
+            else
+            {
+                if (request.getDateHeader("If-Modified-Since") > 0)
+                {
+                    // if it is already in cache - don't bother to go further
+                    response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+                }
+                else
+                {
+                    // enable caching
+                    response.setDateHeader("Last-Modified", LAST_MODIFIED);
+
+                    String mime = icon.getMimeType();
+                    if (mime != null)
+                        response.setContentType(mime);
+                    OutputStream out = response.getOutputStream();
+
+                    int size = icon.getSize();
+                    if (size > 0)
+                        response.setContentLength(size);
+
+                    InputStream in = icon.getInputStream();
+                    // can't use buffer, because it's might block if reading byte[]
+                    int read;
+                    while (-1 != (read = in.read()))
+                        out.write(read);
+                }
+            }
+        }
+    }
+
+    /**
+     * @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest,
+     *      javax.servlet.http.HttpServletResponse)
+     */
+    protected void doPost(HttpServletRequest request, HttpServletResponse response)
+        throws ServletException, IOException
+    {
+        try
+        {
+            JSONObject json = new JSONObject();
+
+            String method = request.getParameter("action");
+
+            if ("listDevices".equals(method))
+            {
+                getSession(request).unsubscribe();
+
+                ServiceReference[] refs = tracker.getServiceReferences();
+                // add root devices only
+                for (int i = 0; refs != null && i < refs.length; i++)
+                {
+                    if (refs[i] != null
+                        && refs[i].getProperty(UPnPDevice.PARENT_UDN) == null)
+                    {
+                        json.append("devices", deviceTreeToJSON(refs[i]));
+                    }
+                }
+            }
+            else if ("serviceDetails".equals(method))
+            {
+                UPnPService service = requireService(request);
+                SessionObject session = getSession(request)//
+                .subscribe(require("udn", request), service.getId());
+
+                json = serviceToJSON(service, session);
+            }
+            else if ("invokeAction".equals(method))
+            {
+                UPnPService service = requireService(request);
+                UPnPAction action = service.getAction(require("actionID", request));
+
+                json = invoke(
+                    action, //
+                    request.getParameterValues("names"),
+                    request.getParameterValues("vals"));
+            }
+            else
+            {
+                throw new ServletException("Invalid action: " + method);
+            }
+
+            response.setContentType("application/json");
+            response.setCharacterEncoding("UTF-8");
+            response.getWriter().print(json.toString(2));
+        }
+        catch (ServletException e)
+        {
+            throw e;
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+            throw new ServletException(e.toString());
+        }
+    }
+
+    private final SessionObject getSession(HttpServletRequest request)
+    {
+        String sessionID = request.getSession().getId();
+        SessionObject ret = (SessionObject) sessions.get(sessionID);
+        if (ret == null)
+        {
+            ret = new SessionObject(bc, sessionID, sessions);
+            request.getSession().setAttribute("___upnp.session.object", ret);
+        }
+        return ret;
+    }
+
+    private static final String require(String name, HttpServletRequest request)
+        throws ServletException
+    {
+        String value = request.getParameter(name);
+        if (value == null)
+            throw new ServletException("missing parameter: " + name);
+        return value;
+    }
+
+    private final UPnPService requireService(HttpServletRequest request)
+        throws ServletException
+    {
+        String deviceUdn = require("udn", request);
+        String serviceUrn = require("urn", request);
+
+        UPnPDevice device = getDevice(deviceUdn);
+        return getService(device, serviceUrn);
+    }
+
+    private final JSONObject deviceTreeToJSON(ServiceReference ref) throws JSONException
+    {
+        UPnPDevice device = (UPnPDevice) tracker.getService(ref);
+        Object[] refs = tracker.getServiceReferences();
+
+        Object parentUdn = ref.getProperty(UPnPDevice.UDN);
+        JSONObject json = deviceToJSON(ref, device);
+
+        // add child devices
+        for (int i = 0; refs != null && i < refs.length; i++)
+        {
+            ref = (ServiceReference) refs[i];
+
+            Object parent = ref.getProperty(UPnPDevice.PARENT_UDN);
+            Object currentUDN = ref.getProperty(UPnPDevice.UDN);
+            if (parent == null)
+            { // no parent
+                continue;
+            }
+            else if (currentUDN != null && currentUDN.equals(parent))
+            { // self ?
+                continue;
+            }
+            else if (parentUdn.equals(parent))
+            {
+                device = (UPnPDevice) tracker.getService(ref);
+                json.append("children", deviceTreeToJSON(ref));
+            }
+        }
+        return json;
+    }
+
+    private static final JSONObject deviceToJSON(ServiceReference ref, UPnPDevice device)
+        throws JSONException
+    {
+        JSONObject json = new JSONObject();
+        json.put("icon", device.getIcons(null) != null);
+
+        // add properties
+        String[] props = ref.getPropertyKeys();
+        JSONObject _props = new JSONObject();
+        for (int i = 0; props != null && i < props.length; i++)
+        {
+            _props.put(props[i], ref.getProperty(props[i]));
+        }
+        json.put("props", _props);
+
+        UPnPService[] services = device.getServices();
+        for (int i = 0; services != null && i < services.length; i++)
+        {
+            json.append("services", services[i].getType());
+        }
+
+        return json;
+    }
+
+    private static final JSONObject serviceToJSON(UPnPService service,
+        SessionObject session) throws JSONException
+    {
+        JSONObject json = new JSONObject();
+
+        // add service properties
+        json.put("type", service.getType());
+        json.put("id", service.getId());
+
+        // add state variables
+        UPnPStateVariable[] vars = service.getStateVariables();
+        for (int i = 0; vars != null && i < vars.length; i++)
+        {
+            Object value = null;
+            if (vars[i] instanceof UPnPLocalStateVariable)
+            {
+                value = ((UPnPLocalStateVariable) vars[i]).getCurrentValue();
+            }
+
+            if (value == null)
+                value = session.getValue(vars[i].getName());
+            if (value == null)
+                value = "---";
+
+            json.append("variables", new JSONObject() //
+            .put("name", vars[i].getName()) //
+            .put("value", value) //
+            .put("defalt", vars[i].getDefaultValue()) //
+            .put("min", vars[i].getMinimum()) //
+            .put("max", vars[i].getMaximum()) //
+            .put("step", vars[i].getStep()) //
+            .put("allowed", vars[i].getAllowedValues()) //
+            .put("sendsEvents", vars[i].sendsEvents()) //
+            );
+        }
+
+        // add actions
+        UPnPAction[] actions = service.getActions();
+        for (int i = 0; actions != null && i < actions.length; i++)
+        {
+            json.append("actions", actionToJSON(actions[i]));
+        }
+
+        return json;
+    }
+
+    private static final JSONObject actionToJSON(UPnPAction action) throws JSONException
+    {
+        JSONObject json = new JSONObject();
+        json.put("name", action.getName());
+        String[] names = action.getInputArgumentNames();
+        for (int i = 0; names != null && i < names.length; i++)
+        {
+            UPnPStateVariable variable = action.getStateVariable(names[i]);
+            json.append("inVars", new JSONObject()//
+            .put("name", names[i])//
+            .put("type", variable.getUPnPDataType()));
+        }
+
+        return json;
+
+    }
+
+    private static final JSONObject invoke(UPnPAction action, String[] names,
+        String[] vals) throws Exception
+    {
+        JSONObject json = new JSONObject();
+
+        // check input arguments
+        Hashtable inputArgs = null;
+        if (names != null && vals != null && names.length > 0
+            && names.length == vals.length)
+        {
+            inputArgs = new Hashtable(names.length);
+            for (int i = 0; i < names.length; i++)
+            {
+                UPnPStateVariable var = action.getStateVariable(names[i]);
+                Class javaType = var.getJavaDataType();
+                Constructor constructor = javaType.getConstructor(new Class[] { String.class });
+                Object argObj = constructor.newInstance(new Object[] { vals[i] });
+
+                inputArgs.put(names[i], argObj);
+            }
+        }
+
+        // invoke
+        Dictionary out = action.invoke(inputArgs);
+
+        // prepare output arguments
+        if (out != null && out.size() > 0)
+        {
+            for (Enumeration e = out.keys(); e.hasMoreElements();)
+            {
+                String key = (String) e.nextElement();
+                UPnPStateVariable var = action.getStateVariable(key);
+
+                Object value = out.get(key);
+                if (value instanceof Date)
+                {
+                    synchronized (DATA_FORMAT)
+                    {
+                        value = DATA_FORMAT.format((Date) value);
+                    }
+                }
+                else if (value instanceof byte[])
+                {
+                    value = hex((byte[]) value);
+                }
+
+                json.append("output", new JSONObject() //
+                .put("name", key)//
+                .put("type", var.getUPnPDataType()) //
+                .put("value", value));
+            }
+        }
+        return json;
+    }
+
+    private static final String hex(byte[] data)
+    {
+        if (data == null)
+            return "null";
+        StringBuffer sb = new StringBuffer(data.length * 3);
+        synchronized (sb)
+        {
+            for (int i = 0; i < data.length; i++)
+            {
+                sb.append(Integer.toHexString(data[i] & 0xff)).append('-');
+            }
+            sb.deleteCharAt(sb.length() - 1);
+        }
+        return sb.toString();
+    }
+
+    private final UPnPDevice getDevice(String udn)
+    {
+        ServiceReference[] refs = tracker.getServiceReferences();
+        String _udn;
+        for (int i = 0; refs != null && i < refs.length; i++)
+        {
+            _udn = (String) refs[i].getProperty(UPnPDevice.UDN);
+            if (_udn != null && _udn.equals(udn))
+            {
+                return (UPnPDevice) tracker.getService(refs[i]);
+            }
+        }
+
+        throw new IllegalArgumentException("Device '" + udn + "' not found!");
+    }
+
+    private final UPnPService getService(UPnPDevice device, String urn)
+    {
+        UPnPService[] services = device.getServices();
+        for (int i = 0; services != null && i < services.length; i++)
+        {
+            if (services[i].getType().equals(urn))
+            {
+                return services[i];
+            }
+        }
+
+        throw new IllegalArgumentException("Service '" + urn + "' not found!");
+    }
+
+    /**
+     * Creates new XML-RPC handler.
+     * 
+     * @param bc the bundle context
+     * @param iconServlet the icon servlet.
+     */
+    ControlServlet(BundleContext bc, ServiceTracker tracker)
+    {
+        this.bc = bc;
+        this.tracker = tracker;
+    }
+
+    /**
+     * Cancels the scheduled timers
+     */
+    void close()
+    {
+        icons.clear();
+        for (Iterator i = sessions.values().iterator(); i.hasNext();)
+        {
+            ((SessionObject) i.next()).unsubscribe();
+        }
+        sessions.clear();
+    }
+
+    /* ---------- BEGIN SERVICE TRACKER */
+    /**
+     * @see org.osgi.util.tracker.ServiceTrackerCustomizer#modifiedService(org.osgi.framework.ServiceReference,
+     *      java.lang.Object)
+     */
+    public final void modifiedService(ServiceReference ref, Object serv)
+    {/* unused */
+    }
+
+    /**
+     * @see org.osgi.util.tracker.ServiceTrackerCustomizer#removedService(org.osgi.framework.ServiceReference,
+     *      java.lang.Object)
+     */
+    public final void removedService(ServiceReference ref, Object serv)
+    {
+        icons.remove(ref.getProperty(UPnPDevice.UDN));
+    }
+
+    /**
+     * @see org.osgi.util.tracker.ServiceTrackerCustomizer#addingService(org.osgi.framework.ServiceReference)
+     */
+    public final Object addingService(ServiceReference ref)
+    {
+        UPnPDevice device = (UPnPDevice) bc.getService(ref);
+
+        UPnPIcon[] _icons = device.getIcons(null);
+        if (_icons != null && _icons.length > 0)
+        {
+            icons.put(ref.getProperty(UPnPDevice.UDN), _icons[0]);
+        }
+
+        return device;
+    }
+
+}

Added: felix/trunk/webconsole-plugins/upnp/src/main/java/org/apache/felix/webconsole/plugins/upnp/internal/SessionObject.java
URL: http://svn.apache.org/viewvc/felix/trunk/webconsole-plugins/upnp/src/main/java/org/apache/felix/webconsole/plugins/upnp/internal/SessionObject.java?rev=925979&view=auto
==============================================================================
--- felix/trunk/webconsole-plugins/upnp/src/main/java/org/apache/felix/webconsole/plugins/upnp/internal/SessionObject.java (added)
+++ felix/trunk/webconsole-plugins/upnp/src/main/java/org/apache/felix/webconsole/plugins/upnp/internal/SessionObject.java Mon Mar 22 06:52:06 2010
@@ -0,0 +1,153 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.felix.webconsole.plugins.upnp.internal;
+
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Map;
+
+import javax.servlet.http.HttpSessionBindingEvent;
+import javax.servlet.http.HttpSessionBindingListener;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.upnp.UPnPDevice;
+import org.osgi.service.upnp.UPnPEventListener;
+import org.osgi.service.upnp.UPnPService;
+
+/**
+ * The reason for having this SessionObject is the strange event delivery in UPnP. It's not possible
+ * read a state variable value, but if register a listener, you will get notified when the value has
+ * changed.
+ */
+final class SessionObject implements HttpSessionBindingListener, UPnPEventListener
+{
+
+    static final String LISTENER_CLASS = UPnPEventListener.class.getName();
+
+    private final Map vars = new HashMap();
+    private final String sessionID;
+    private final Map store;
+    private String udn, urn;
+
+    private final BundleContext bc;
+    private final Hashtable regProps = new Hashtable(3);
+    private ServiceRegistration reg;
+
+    SessionObject(BundleContext bc, String sessionID, Map store)
+    {
+        this.bc = bc;
+        this.sessionID = sessionID;
+        this.store = store;
+    }
+
+    /**
+     * @see javax.servlet.http.HttpSessionBindingListener#valueBound(javax.servlet.http.HttpSessionBindingEvent)
+     */
+    public void valueBound(HttpSessionBindingEvent event)
+    {
+        store.put(sessionID, this);
+    }
+
+    /**
+     * @see javax.servlet.http.HttpSessionBindingListener#valueUnbound(javax.servlet.http.HttpSessionBindingEvent)
+     */
+    public final void valueUnbound(HttpSessionBindingEvent event)
+    {
+        unsubscribe();
+        store.remove(sessionID); // remove from list of sessions
+    }
+
+    /**
+     * @see org.osgi.service.upnp.UPnPEventListener#notifyUPnPEvent(java.lang.String,
+     *      java.lang.String, java.util.Dictionary)
+     */
+    public final void notifyUPnPEvent(String deviceId, String serviceId, Dictionary events)
+    {
+        if (sameDevice(deviceId, serviceId))
+        {
+            for (Enumeration e = events.keys(); e.hasMoreElements();)
+            {
+                Object key = e.nextElement();
+                vars.put(key, events.get(key));
+            }
+        }
+    }
+
+    private final boolean sameDevice(String udn, String urn)
+    {
+        String _udn = this.udn;
+        String _urn = this.urn;
+        if (_udn == null || _urn == null)
+            return false; // not subscribed
+        return _udn.equals(udn) && _urn.equals(urn);
+    }
+
+    final synchronized SessionObject subscribe(String udn, String urn)
+    {
+        if (!sameDevice(udn, urn))
+        {
+            unsubscribe();
+            this.udn = udn;
+            this.urn = urn;
+
+            try
+            {
+                regProps.put(UPnPEventListener.UPNP_FILTER, bc.createFilter(//
+                "(&(" + UPnPDevice.UDN + '=' + udn + ")(" + //
+                    UPnPService.ID + '=' + urn + "))"));
+            }
+            catch (InvalidSyntaxException e)
+            { /* will not happen */
+            }
+            reg = bc.registerService(LISTENER_CLASS, this, regProps);
+        }
+        return this;
+    }
+
+    final synchronized SessionObject unsubscribe()
+    {
+        this.udn = this.urn = null;
+        vars.clear();
+        if (reg != null)
+        {
+            reg.unregister();
+            reg = null;
+        }
+        return this;
+    }
+
+    final Object getValue(String name)
+    {
+        return vars.get(name);
+    }
+
+    /**
+     * @see java.lang.Object#toString()
+     */
+    public final String toString()
+    {
+        StringBuffer buffer = new StringBuffer();
+        buffer.append("SessionObject [sessionID=").append(sessionID).append(", udn=").append(
+            udn).append(", urn=").append(urn).append(", vars=").append(vars).append("]");
+        return buffer.toString();
+    }
+
+}

Added: felix/trunk/webconsole-plugins/upnp/src/main/java/org/apache/felix/webconsole/plugins/upnp/internal/WebConsolePlugin.java
URL: http://svn.apache.org/viewvc/felix/trunk/webconsole-plugins/upnp/src/main/java/org/apache/felix/webconsole/plugins/upnp/internal/WebConsolePlugin.java?rev=925979&view=auto
==============================================================================
--- felix/trunk/webconsole-plugins/upnp/src/main/java/org/apache/felix/webconsole/plugins/upnp/internal/WebConsolePlugin.java (added)
+++ felix/trunk/webconsole-plugins/upnp/src/main/java/org/apache/felix/webconsole/plugins/upnp/internal/WebConsolePlugin.java Mon Mar 22 06:52:06 2010
@@ -0,0 +1,127 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.felix.webconsole.plugins.upnp.internal;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.felix.webconsole.SimpleWebConsolePlugin;
+import org.osgi.framework.BundleContext;
+import org.osgi.util.tracker.ServiceTracker;
+
+/**
+ * Provides Web Console interface to UPnP Devices.
+ */
+public class WebConsolePlugin extends SimpleWebConsolePlugin
+{
+
+    private static final String LABEL = "upnp";
+    private static final String TITLE = "UPnP";
+    private static final String CSS[] = { "/" + LABEL + "/res/upnp.css", //
+            "/" + LABEL + "/res/jquery-treeview-1.4/jquery.treeview.css", //
+    };
+
+    private final ServiceTracker tracker;
+    ControlServlet controller;
+
+    //templates
+    private final String TEMPLATE;
+
+    /**
+     * Creates new plugin
+     * 
+     * @param tracker the UPnP Device tracker
+     */
+    public WebConsolePlugin(ServiceTracker tracker)
+    {
+        super(LABEL, TITLE, CSS);
+        this.tracker = tracker;
+
+        // load templates
+        TEMPLATE = readTemplateFile("/res/upnp.html");
+    }
+
+    /**
+     * @see org.apache.felix.webconsole.AbstractWebConsolePlugin#activate(org.osgi.framework.BundleContext)
+     */
+    public void activate(BundleContext bundleContext)
+    {
+        super.activate(bundleContext);
+        controller = new ControlServlet(bundleContext, tracker);
+    }
+
+    /**
+     * @see org.apache.felix.webconsole.SimpleWebConsolePlugin#deactivate()
+     */
+    public void deactivate()
+    {
+        if (controller != null)
+        {
+            controller.close();
+            controller = null;
+        }
+        super.deactivate();
+    }
+
+    /**
+     * @see org.apache.felix.webconsole.AbstractWebConsolePlugin#renderContent(javax.servlet.http.HttpServletRequest,
+     *      javax.servlet.http.HttpServletResponse)
+     */
+    protected void renderContent(HttpServletRequest req, HttpServletResponse res)
+        throws ServletException, IOException
+    {
+        res.getWriter().print(TEMPLATE);
+    }
+
+    /**
+     * @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest,
+     *      javax.servlet.http.HttpServletResponse)
+     */
+    protected void doPost(HttpServletRequest request, HttpServletResponse response)
+        throws ServletException, IOException
+    {
+        if (request.getParameter("action") != null)
+        {
+            controller.doPost(request, response);
+        }
+        else
+        {
+            super.doPost(request, response);
+        }
+    }
+
+    /**
+     * @see org.apache.felix.webconsole.AbstractWebConsolePlugin#doGet(javax.servlet.http.HttpServletRequest,
+     *      javax.servlet.http.HttpServletResponse)
+     */
+    protected void doGet(HttpServletRequest request, HttpServletResponse response)
+        throws ServletException, IOException
+    {
+        if (request.getParameter("icon") != null)
+        {
+            controller.doGet(request, response);
+        }
+        else
+        {
+            super.doGet(request, response);
+        }
+    }
+
+}

Added: felix/trunk/webconsole-plugins/upnp/src/main/native2ascii/OSGI-INF/l10n/bundle_bg.properties
URL: http://svn.apache.org/viewvc/felix/trunk/webconsole-plugins/upnp/src/main/native2ascii/OSGI-INF/l10n/bundle_bg.properties?rev=925979&view=auto
==============================================================================
--- felix/trunk/webconsole-plugins/upnp/src/main/native2ascii/OSGI-INF/l10n/bundle_bg.properties (added)
+++ felix/trunk/webconsole-plugins/upnp/src/main/native2ascii/OSGI-INF/l10n/bundle_bg.properties Mon Mar 22 06:52:06 2010
@@ -0,0 +1,26 @@
+# Convert to normal properties using: native2ascii.exe -encoding utf-8 bundle_bg.properties.utf8 bundle_bg.properties
+status.ok=UPnP работи. Вижте по-долу за наличните устройсва.
+btn.search=Търсене на устройства
+btn.reloadVars=Презареждане на променливите
+tree.searching=Търсене на устройства...
+tree.loading.title=Зареждане...
+device.title=Информация за устройството
+prop.name=Атрибут
+prop.value=Стойност
+service.title=Информация за услугата
+service.id=ID:
+service.type=Тип:
+vars.title=Променливи
+vars.name=Име
+vars.value=Стойност
+vars.events=Изпраща нотификации
+actions.title=Действия
+actions.invoke=Изпълнение
+args.name=Име на аргумента
+args.type=UPnP тип
+args.value=Стойност
+dialog.title.ok=Успех!
+no.actions=Няма асоциирани действия
+no.params.out=Няма изходни параметри
+actions.select=Действие:
+actions.invoke.title=Изпълнение на избраното действие

Added: felix/trunk/webconsole-plugins/upnp/src/main/resources/OSGI-INF/l10n/bundle.properties
URL: http://svn.apache.org/viewvc/felix/trunk/webconsole-plugins/upnp/src/main/resources/OSGI-INF/l10n/bundle.properties?rev=925979&view=auto
==============================================================================
--- felix/trunk/webconsole-plugins/upnp/src/main/resources/OSGI-INF/l10n/bundle.properties (added)
+++ felix/trunk/webconsole-plugins/upnp/src/main/resources/OSGI-INF/l10n/bundle.properties Mon Mar 22 06:52:06 2010
@@ -0,0 +1,25 @@
+status.ok=UPnP Service Available.
+btn.search=Search for UPnP Devices
+btn.reloadVars=Reload State Variables
+tree.searching=Searching for UPnP Devices ...
+tree.loading.title=Loading
+device.title=Device Information
+prop.name=Property Name
+prop.value=Value
+service.title=Service Information
+service.id=Service ID:
+service.type=Service Type:
+vars.title=State Variables
+vars.name=Name
+vars.value=Value
+vars.events=Sends Events
+actions.title=Actions
+actions.invoke=Invoke
+args.name=Argument Name
+args.type=UPnP Data Type
+args.value=Value
+dialog.title.ok=Success
+no.actions=No actions available.
+no.params.out=No output parameters.
+actions.select=Select Action:
+actions.invoke.title=Invoke the selected action

Added: felix/trunk/webconsole-plugins/upnp/src/main/resources/res/jquery-treeview-1.4/images/minus.gif
URL: http://svn.apache.org/viewvc/felix/trunk/webconsole-plugins/upnp/src/main/resources/res/jquery-treeview-1.4/images/minus.gif?rev=925979&view=auto
==============================================================================
Binary file - no diff available.

Propchange: felix/trunk/webconsole-plugins/upnp/src/main/resources/res/jquery-treeview-1.4/images/minus.gif
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Added: felix/trunk/webconsole-plugins/upnp/src/main/resources/res/jquery-treeview-1.4/images/plus.gif
URL: http://svn.apache.org/viewvc/felix/trunk/webconsole-plugins/upnp/src/main/resources/res/jquery-treeview-1.4/images/plus.gif?rev=925979&view=auto
==============================================================================
Binary file - no diff available.

Propchange: felix/trunk/webconsole-plugins/upnp/src/main/resources/res/jquery-treeview-1.4/images/plus.gif
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Added: felix/trunk/webconsole-plugins/upnp/src/main/resources/res/jquery-treeview-1.4/images/treeview-default-line.gif
URL: http://svn.apache.org/viewvc/felix/trunk/webconsole-plugins/upnp/src/main/resources/res/jquery-treeview-1.4/images/treeview-default-line.gif?rev=925979&view=auto
==============================================================================
Binary file - no diff available.

Propchange: felix/trunk/webconsole-plugins/upnp/src/main/resources/res/jquery-treeview-1.4/images/treeview-default-line.gif
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Added: felix/trunk/webconsole-plugins/upnp/src/main/resources/res/jquery-treeview-1.4/images/treeview-default.gif
URL: http://svn.apache.org/viewvc/felix/trunk/webconsole-plugins/upnp/src/main/resources/res/jquery-treeview-1.4/images/treeview-default.gif?rev=925979&view=auto
==============================================================================
Binary file - no diff available.

Propchange: felix/trunk/webconsole-plugins/upnp/src/main/resources/res/jquery-treeview-1.4/images/treeview-default.gif
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Added: felix/trunk/webconsole-plugins/upnp/src/main/resources/res/jquery-treeview-1.4/jquery.treeview.css
URL: http://svn.apache.org/viewvc/felix/trunk/webconsole-plugins/upnp/src/main/resources/res/jquery-treeview-1.4/jquery.treeview.css?rev=925979&view=auto
==============================================================================
--- felix/trunk/webconsole-plugins/upnp/src/main/resources/res/jquery-treeview-1.4/jquery.treeview.css (added)
+++ felix/trunk/webconsole-plugins/upnp/src/main/resources/res/jquery-treeview-1.4/jquery.treeview.css Mon Mar 22 06:52:06 2010
@@ -0,0 +1,56 @@
+.treeview, .treeview ul { 
+	padding: 0;
+	margin: 0;
+	list-style: none;
+}
+
+.treeview ul {
+	background-color: white;
+	margin-top: 4px;
+}
+
+.treeview .hitarea {
+	background: url(images/treeview-default.gif) -64px -25px no-repeat;
+	height: 16px;
+	width: 16px;
+	margin-left: -16px;
+	float: left;
+	cursor: pointer;
+}
+/* fix for IE6 */
+* html .hitarea {
+	display: inline;
+	float:none;
+}
+
+.treeview li { 
+	margin: 0;
+	padding: 3px 0pt 3px 16px;
+}
+
+.treeview a.selected {
+	background-color: #eee;
+}
+
+#treecontrol { margin: 1em 0; display: none; }
+
+.treeview .hover { color: red; cursor: pointer; }
+
+.treeview li { background: url(images/treeview-default-line.gif) 0 0 no-repeat; }
+.treeview li.collapsable, .treeview li.expandable { background-position: 0 -176px; }
+
+.treeview .expandable-hitarea { background-position: -80px -3px; }
+
+.treeview li.last { background-position: 0 -1766px }
+.treeview li.lastCollapsable, .treeview li.lastExpandable { background-image: url(images/treeview-default.gif); }  
+.treeview li.lastCollapsable { background-position: 0 -111px }
+.treeview li.lastExpandable { background-position: -32px -67px }
+
+.treeview div.lastCollapsable-hitarea, .treeview div.lastExpandable-hitarea { background-position: 0; }
+
+
+.filetree li { padding: 3px 0 2px 16px; }
+.filetree span.folder, .filetree span.file { padding: 1px 0 1px 16px; display: block; }
+.filetree span.folder { background: url(images/folder.gif) 0 0 no-repeat; }
+.filetree li.expandable span.folder { background: url(images/folder-closed.gif) 0 0 no-repeat; }
+.filetree span.file { background: url(images/file.gif) 0 0 no-repeat; }

Added: felix/trunk/webconsole-plugins/upnp/src/main/resources/res/jquery-treeview-1.4/jquery.treeview.pack.js
URL: http://svn.apache.org/viewvc/felix/trunk/webconsole-plugins/upnp/src/main/resources/res/jquery-treeview-1.4/jquery.treeview.pack.js?rev=925979&view=auto
==============================================================================
--- felix/trunk/webconsole-plugins/upnp/src/main/resources/res/jquery-treeview-1.4/jquery.treeview.pack.js (added)
+++ felix/trunk/webconsole-plugins/upnp/src/main/resources/res/jquery-treeview-1.4/jquery.treeview.pack.js Mon Mar 22 06:52:06 2010
@@ -0,0 +1,16 @@
+/*
+ * Treeview 1.4 - jQuery plugin to hide and show branches of a tree
+ * 
+ * http://bassistance.de/jquery-plugins/jquery-plugin-treeview/
+ * http://docs.jquery.com/Plugins/Treeview
+ *
+ * Copyright (c) 2007 Jörn Zaefferer
+ *
+ * Dual licensed under the MIT and GPL licenses:
+ *   http://www.opensource.org/licenses/mit-license.php
+ *   http://www.gnu.org/licenses/gpl.html
+ *
+ * Revision: $Id: jquery.treeview.js 4684 2008-02-07 19:08:06Z joern.zaefferer $
+ *
+ */
+eval(function(p,a,c,k,e,r){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}(';(4($){$.1l($.F,{E:4(b,c){l a=3.n(\'.\'+b);3.n(\'.\'+c).o(c).m(b);a.o(b).m(c);8 3},s:4(a,b){8 3.n(\'.\'+a).o(a).m(b).P()},1n:4(a){a=a||"1j";8 3.1j(4(){$(3).m(a)},4(){$(3).o(a)})},1h:4(b,a){b?3.1g({1e:"p"},b,a):3.x(4(){T(3)[T(3).1a(":U")?"H":"D"]();7(a)a.A(3,O)})},12:4(b,a){7(b){3.1g({1e:"D"},b,a)}1L{3.D();7(a)3.x(a)}},11:4(a){7(!a.1k){3.n(":r-1H:G(9)").m(k.r);3.n((a.1F?"":"."+k.X)+":G(."+k.W+")").6(">9").D()}8 3.n(":y(>9)")},S:4(b,c){3.n(":y(>9):G(:y(>a))").6(">1z").C(4(a){c.A($(3).19())}).w($("a",3)).1n();7(!b.1k){3.n(":y(>9:U)").m(k.q).s(k.r,k.t);3.G(":y(>9:U)").m(k.u).s(k.r,k.v);3.1r("<J 14=\\""+k.5+"\\"/>").6("J."+k.5).x(4(){l a="";$.x($(3).B().1o("14
 ").13(" "),4(){a+=3+"-5 "});$(3).m(a)})}3.6("J."+k.5).C(c)},z:4(g){g=$.1l({N:"z"},g);7(g.w){8 3.1K("w",[g.w])}7(g.p){l d=g.p;g.p=4(){8 d.A($(3).B()[0],O)}}4 1m(b,c){4 L(a){8 4(){K.A($("J."+k.5,b).n(4(){8 a?$(3).B("."+a).1i:1I}));8 1G}}$("a:10(0)",c).C(L(k.u));$("a:10(1)",c).C(L(k.q));$("a:10(2)",c).C(L())}4 K(){$(3).B().6(">.5").E(k.Z,k.Y).E(k.I,k.M).P().E(k.u,k.q).E(k.v,k.t).6(">9").1h(g.1f,g.p);7(g.1E){$(3).B().1D().6(">.5").s(k.Z,k.Y).s(k.I,k.M).P().s(k.u,k.q).s(k.v,k.t).6(">9").12(g.1f,g.p)}}4 1d(){4 1C(a){8 a?1:0}l b=[];j.x(4(i,e){b[i]=$(e).1a(":y(>9:1B)")?1:0});$.V(g.N,b.1A(""))}4 1c(){l b=$.V(g.N);7(b){l a=b.13("");j.x(4(i,e){$(e).6(">9")[1y(a[i])?"H":"D"]()})}}3.m("z");l j=3.6("Q").11(g);1x(g.1w){18"V":l h=g.p;g.p=4(){1d();7(h){h.A(3,O)}};1c();17;18"1b":l f=3.6("a").n(4(){8 3.16.15()==1b.16.15()});7(f.1i){f.m("1v").1u("9, Q").w(f.19()).H()}17}j.S(g,K);7(g.R){1m(3,g.R);$(g.R).H()}8 3.1t("w",4(a,b){$(b).1s().o(k.r).o(k.v).o(k.t).6(">.5").o(k.I).o(k.M);$(b).6("Q").1q().
 11(g).S(g,K)})}});l k=$.F.z.1J={W:"W",X:"X",q:"q",Y:"q-5",M:"t-5",u:"u",Z:"u-5",I:"v-5",v:"v",t:"t",r:"r",5:"5"};$.F.1p=$.F.z})(T);',62,110,'|||this|function|hitarea|find|if|return|ul||||||||||||var|addClass|filter|removeClass|toggle|expandable|last|replaceClass|lastExpandable|collapsable|lastCollapsable|add|each|has|treeview|apply|parent|click|hide|swapClass|fn|not|show|lastCollapsableHitarea|div|toggler|handler|lastExpandableHitarea|cookieId|arguments|end|li|control|applyClasses|jQuery|hidden|cookie|open|closed|expandableHitarea|collapsableHitarea|eq|prepareBranches|heightHide|split|class|toLowerCase|href|break|case|next|is|location|deserialize|serialize|height|animated|animate|heightToggle|length|hover|prerendered|extend|treeController|hoverClass|attr|Treeview|andSelf|prepend|prev|bind|parents|selected|persist|switch|parseInt|span|join|visible|binary|siblings|unique|collapsed|false|child|true|classes|trigger|else'.split('|'),0,{}))
\ No newline at end of file

Added: felix/trunk/webconsole-plugins/upnp/src/main/resources/res/loading.gif
URL: http://svn.apache.org/viewvc/felix/trunk/webconsole-plugins/upnp/src/main/resources/res/loading.gif?rev=925979&view=auto
==============================================================================
Binary file - no diff available.

Propchange: felix/trunk/webconsole-plugins/upnp/src/main/resources/res/loading.gif
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Added: felix/trunk/webconsole-plugins/upnp/src/main/resources/res/upnp.css
URL: http://svn.apache.org/viewvc/felix/trunk/webconsole-plugins/upnp/src/main/resources/res/upnp.css?rev=925979&view=auto
==============================================================================
--- felix/trunk/webconsole-plugins/upnp/src/main/resources/res/upnp.css (added)
+++ felix/trunk/webconsole-plugins/upnp/src/main/resources/res/upnp.css Mon Mar 22 06:52:06 2010
@@ -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.
+ */
+#detailsBox { padding-left: .5em }
+#dialog .nicetable { font-size: .5em }
+#detailsBox h3 {
+	font-weight: bold;
+	font-size: 1.5em;
+	padding-top: 1em;
+}
+#treeBox , #treeCont {
+	width: 300px !important;
+	height: 100%;
+	min-height: 300px;
+	overflow: auto;
+}
+#browser { margin: 1em; height: 100% }
+#browser li { cursor: pointer }
+#browser span.ui-icon { display: inline-block }
+img.icon { /* force image size */
+	border: none;
+	width: 16px;
+	height: 16px;
+	margin: 0;
+	padding: 0;
+}

Added: felix/trunk/webconsole-plugins/upnp/src/main/resources/res/upnp.html
URL: http://svn.apache.org/viewvc/felix/trunk/webconsole-plugins/upnp/src/main/resources/res/upnp.html?rev=925979&view=auto
==============================================================================
--- felix/trunk/webconsole-plugins/upnp/src/main/resources/res/upnp.html (added)
+++ felix/trunk/webconsole-plugins/upnp/src/main/resources/res/upnp.html Mon Mar 22 06:52:06 2010
@@ -0,0 +1,121 @@
+<script type="text/javascript" src="${pluginRoot}/res/jquery-treeview-1.4/jquery.treeview.pack.js"></script>
+<script type="text/javascript" src="${pluginRoot}/res/upnp.js"></script>
+<script type="text/javascript">
+// <![CDATA[
+// i18n
+var i18n = {
+	dl_title_ok  : '${dialog.title.ok}',
+	args_name    : '${args.name}',
+	args_type    : '${args.type}',
+	args_value   : '${args.value}',
+	no_actions   : '${no.actions}',
+	no_params_out: '${no.params.out}'
+}
+// ]]>
+</script>
+
+<p class="statline">${status.ok}</p>
+
+<form method="post" enctype="multipart/form-data" action="" onsubmit="return false">
+	<div class="ui-widget-header ui-corner-top buttonGroup">
+		<button id="reloadDevices">${btn.search}</button>
+		<button id="reloadVars" class="ui-state-disabled">${btn.reloadVars}</button>
+	</div>
+</form>
+
+<table id="plugin_table" style="width: 100%">
+	<tr>
+		<td id="treeCont" class="ui-widget-content ui-corner-bottom">
+			<div id="searching"> <!-- search for devices -->
+				${tree.searching}
+				<img src="${pluginRoot}/res/loading.gif" alt="${tree.loading.title}" />
+			</div>
+			<div id="treeBox"> <!-- here comes the tree -->
+				<ul id="browser">
+					<li>dummy</li>
+				</ul>
+			</div>
+		</td>
+		<td id="detailsBox">
+			<!-- opened when device is selected -->
+			<div id="deviceData" class="ui-helper-hidden">
+				<h3>${device.title}</h3>
+				<table id="deviceTable" class="nicetable">
+					<thead>
+						<tr>
+							<th>${prop.name}</th>
+							<th>${prop.value}</th>
+						</tr>
+					</thead>
+					<tbody>
+						<tr><td colspan="2">&nbsp;</td></tr> <!-- dynamic contents -->
+					</tbody>
+				</table>
+			</div>
+			<div id="serviceData" class="ui-helper-hidden">
+				<h3>${service.title}</h3>
+				<table class="nicetable">
+					<thead>
+						<tr>
+							<th>${prop.name}</th>
+							<th>${prop.value}</th>
+						</tr>
+					</thead>
+					<tbody>
+						<tr>
+							<td class="ui-priority-primary">${service.id}</td>
+							<td id="serviceDataInfoID">&nbsp;</td><!-- dynamic contents -->
+						</tr> 
+						<tr>
+							<td class="ui-priority-primary">${service.type}</td>
+							<td id="serviceDataInfoType">&nbsp;</td><!-- dynamic contents -->
+						</tr> 
+					</tbody>
+				</table>
+
+				<h3>${vars.title}</h3>
+				<table id="serviceDataVars" class="tablesorter nicetable">
+					<thead>
+						<tr>
+							<th class="col_Name">${vars.name}</th>
+							<th class="col_Value">${vars.value}</th>
+							<th class="col_SendEvents">${vars.events}</th>
+						</tr>
+					</thead>
+					<tbody><!-- dynamic contents -->
+						<tr><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td></tr> 
+					</tbody>
+				</table>
+
+				<div id="actionsContainer">
+					<h3>${actions.title}</h3>
+					<div class="ui-widget-header ui-corner-top buttonGroup">
+						${actions.select}
+						<select name="c">
+							<option value="---">---</option>
+						</select>
+						<button title="${actions.invoke.title}"><span class="ui-icon ui-icon-play">&nbsp;</span></button>
+					</div>
+					<table class="nicetable">
+						<thead>
+							<tr>
+								<th>${args.name}</th>
+								<th>${args.type}</th>
+								<th>${args.value}</th>
+							</tr>
+						</thead>
+						<tbody>
+							<tr><!-- template -->
+								<td>&nbsp;</td>
+								<td>&nbsp;</td>
+								<td><input value="" /></td>
+							</tr>
+						</tbody>
+					</table>
+				</div>
+			</div> <!-- serviceData -->
+			&nbsp;
+		</td>
+	</tr>
+</table>
+

Added: felix/trunk/webconsole-plugins/upnp/src/main/resources/res/upnp.js
URL: http://svn.apache.org/viewvc/felix/trunk/webconsole-plugins/upnp/src/main/resources/res/upnp.js?rev=925979&view=auto
==============================================================================
--- felix/trunk/webconsole-plugins/upnp/src/main/resources/res/upnp.js (added)
+++ felix/trunk/webconsole-plugins/upnp/src/main/resources/res/upnp.js Mon Mar 22 06:52:06 2010
@@ -0,0 +1,326 @@
+/*
+ * 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.
+ */
+// device selection
+var deviceData = false;
+var deviceTableBody = false;
+
+// service selection
+var serviceData = false;
+var serviceDataVars = false;
+var serviceDataInfoID = false;
+var serviceDataInfoType = false;
+
+// actions
+var actionsContainer = false;
+var actionsSelect    = false;
+var actionsInvoke    = false;
+var actionsTable     = false;
+var actionsTableBody = false;
+var actionsTableRow  = false;
+
+// tree browser, buttons, error dialog
+var browser = false;
+var searching = false;
+var reloadVars = false;
+
+/* BEGIN HELPERS */
+
+/* helper functions for tree node */
+function _id(dn) { return dn.replace(/[:-\\.\\$]/g,'_') }
+/* creates a node in the device tree */
+function treeNode(id, name, icon, span) {
+	var li = createElement('li', null, { 'id' : _id(id) }, [
+		createElement('span', null, null, [
+			icon ? 
+				createElement('img', 'icon', { 'src' : icon }) :
+				createElement('span', 'ui-icon ui-icon-' + span) ,
+			text(name)
+		])
+	]);
+	return $(li);
+}
+/* creates a service node in the devices tree, and associates the click action */
+function servNode(udn, urn) {
+	return treeNode(udn+urn, urn, null, 'extlink').click(function() {
+		if (selectServiceTime) {
+			clearTimeout(selectServiceTime);
+			selectServiceTime = false;
+		}
+		$.post(pluginRoot, { 
+			'action': 'serviceDetails',
+			'udn' : udn,
+			'urn' : urn
+		}, function(data) {
+			renderService(udn, urn, data)
+		}, 'json');
+		return false;
+	});
+}
+/* a helper function to format device properties values - specially 
+ * converts arrays and strings, if the last are links */
+function _val(val) {
+	var ret = '';
+	if ($.isArray(val)) {
+		for (i in val) ret += _val(val[i]) + '<br/>';
+	} else {
+		ret = (typeof val == 'string' && val.indexOf('http://') != -1) ?
+			'<a target="blank" href="' + val + '">' + val + '</a>' : val;
+	}
+	return ret;
+}
+
+
+/* BEGIN UI-ELEMENTS CREATION */
+
+/* add element to the tree, just creates the node */
+function addDevice(device) {
+	var udn  = device.props['UPnP.device.UDN'];
+	var name = device.props['UPnP.device.friendlyName'];
+	var icon = null;
+	if (device.icon) icon = pluginRoot + '?icon=' + udn;
+
+	var node = treeNode(udn, name, icon, 'lightbulb').click(function() {
+		renderDevice(device);
+	});
+
+	var ul, hasChildren;
+
+	// services
+	hasChildren = false;
+	ul = $(createElement('ul', 'ui-helper-clearfix'));
+	for(var i in device.services) {
+		hasChildren = true;
+		ul.append( servNode(udn, device.services[i]) );
+	}
+	if (hasChildren) node.append(ul);
+
+	// child devices
+	hasChildren = false;
+	ul = $(createElement('ul'));
+	for(var i in device.children) {
+		hasChildren = true;
+		addDevice(device.children[i]);
+	}
+	if (hasChildren) node.append(ul);
+
+	return node;
+}
+/* fills in the list of state variables */
+function renderVars(data) {
+	serviceDataVars.empty();
+	for(i in data.variables) {
+		var _var = data.variables[i];
+		var _tr = tr(null, null, [
+			td(null, null, [ text(_var.name) ]),
+			td(null, null, [ text(_var.value) ]),
+			td(null, null, [ text(_var.sendsEvents) ])
+		]);
+		serviceDataVars.append(_tr);
+	}
+	initStaticWidgets();
+}
+
+/* BEGIN ACTION HANDLERS */
+
+var selectedDevice = false; // the LI element of the selected device, reset on load
+function renderDevice(device) {
+	// generate content
+	var table = '';
+	for(var key in device.props) {
+		table += '<tr><td class="ui-priority-primary">' + key + '</td><td>' + _val(device.props[key]) + '</td></tr>';
+	}
+
+	// update the UI
+	deviceTableBody.html(table);
+	reloadVars.addClass('ui-state-disabled');
+	deviceData.removeClass('ui-helper-hidden');
+	serviceData.addClass('ui-helper-hidden')
+	
+	// reset selected items
+	if (selectedDevice) selectedDevice.css('font-weight', 'normal');
+	selectedDevice = $('#' + _id(device.props['UPnP.device.UDN']) + ' span').css('font-weight', 'bold');
+}
+
+var selectedUdn = false;
+var selectedUrn = false;
+var selectServiceTime = false;
+function renderService(udn, urn, data) {
+	// save selection
+	selectedUdn = udn;
+	selectedUrn = urn;
+
+	// append service info
+	serviceDataInfoID.text(data.id);
+	serviceDataInfoType.text(data.type);
+
+	// append state variables
+	renderVars(data);
+
+	// append actions
+	if (data.actions) {
+		var html = '';
+		var x = data.actions;
+		for (var a in x) html += '<option value="' + a + '">' + x[a].name + '</option>';
+		actionsSelect.html(html).unbind('change').change(function() {
+			var index = $(this).val();
+			actionSelected(udn, urn, x[index]);
+		}).trigger('change');
+		actionsContainer.removeClass('ui-helper-hidden');
+	} else {
+		actionsContainer.addClass('ui-helper-hidden');
+	}
+
+	// update UI
+	deviceData.addClass('ui-helper-hidden');
+	serviceData.removeClass('ui-helper-hidden');
+	reloadVars.removeClass('ui-state-disabled');
+	initStaticWidgets();
+
+	// refresh once - to get updates asynchronously
+	selectServiceTime = setTimeout('reloadVars.click()', 3000);
+}
+
+function actionSelected(udn, urn, action) {
+	// add input arguments
+	if (action.inVars) {
+		actionsTableBody.empty();
+		for (var i in action.inVars) {
+			var _arg = action.inVars[i];
+			var _tr = actionsTableRow.clone().appendTo(actionsTableBody);
+			_tr.find('td:eq(0)').text(_arg.name);
+			_tr.find('td:eq(1)').text(_arg.type);
+			var _el = _tr.find('input').attr('id', 'arg'+i);
+			_arg['element'] = _el;
+		}
+		actionsTable.removeClass('ui-helper-hidden');
+	} else {
+		actionsTable.addClass('ui-helper-hidden');
+	}
+	
+	actionsInvoke.unbind('click').click(function() {
+		invokeAction(udn, urn, action);
+	});
+
+	initStaticWidgets(actionsTableBody);
+}
+
+function invokeAction(udn, urn, action) {
+	// prepare arguments
+	var names = new Array();
+	var vals = new Array();
+	for (var i in action.inVars) {
+		var x = action.inVars[i];
+		names.push(x['name']);
+		vals.push(x['element'].val());
+	}
+	// invoke action
+	$.post(pluginRoot, { 
+		'udn' : udn,
+		'urn' : urn,
+		'action': 'invokeAction',
+		'actionID' : action.name,
+		'names' : names,
+		'vals'  : vals
+	}, function(data) {
+		var html = i18n.no_params_out;
+		if (data.output) {
+			html = '<table class="nicetable"><tr><th>'+i18n.args_name+'</th><th>'+i18n.args_type+'</th><th>' + i18n.args_value + '</th></tr>';
+			for(var i in data.output) {
+				var arg = data.output[i];
+				html += '<tr><td>' + arg['name'] + '</td><td>' + arg['type'] + '</td><td>' + arg['value'] + '</td></tr>';
+			}
+			html += '</table>';
+		}
+		Xalert(html, i18n.dl_title_ok);
+	}, 'json');
+}
+
+function listDevices() {
+	browser.empty().addClass('ui-helper-hidden');
+	searching.removeClass('ui-helper-hidden');
+	
+	$.post(pluginRoot, { 'action': 'listDevices' }, function(data) {
+		if (data && data.devices) {
+			$.each(data.devices, function(index) {
+				var html = addDevice(this);
+				browser.treeview( { add: html.appendTo(browser) } );
+			});
+		} else {
+			browser.append('','No devices available', '');
+		}
+
+		// update selected items
+		selectedDevice = false;
+		selectedUdn = false;
+		selectedUrn = false;
+	
+		// update IU elements
+		browser.removeClass('ui-helper-hidden');
+		searching.addClass('ui-helper-hidden');
+		deviceData.addClass('ui-helper-hidden');
+		serviceData.addClass('ui-helper-hidden');
+	}, 'json');
+
+	return false;
+}
+
+
+
+$(document).ready( function() {
+	// init elements of style
+	searching          = $('#searching');
+	deviceData         = $('#deviceData');
+	deviceTableBody    = $('#deviceTable tbody');
+
+	// services
+	serviceData        = $('#serviceData');
+	serviceDataInfoID  = $('#serviceDataInfoID');
+	serviceDataInfoType= $('#serviceDataInfoType');
+	serviceDataVars    = $('#serviceDataVars tbody');
+
+	// actions
+	actionsContainer   = $('#actionsContainer');
+	actionsSelect      = actionsContainer.find('select');
+	actionsInvoke      = actionsContainer.find('button');
+	actionsTable       = actionsContainer.find('table');
+	actionsTableBody   = actionsTable.find('tbody');
+	actionsTableRow    = actionsTableBody.find('tr').clone();
+	actionsTableBody.empty();
+
+	// init navigation tree
+	browser = $('#browser').treeview({
+		animated: 'fast',
+		collapsed: true,
+		unique: true
+	});
+	
+	// reload button
+	reloadVars = $('#reloadVars').click(function() {
+		if (selectedUdn && selectedUrn) {
+			$.post(pluginRoot, { 
+				'action': 'serviceDetails',
+				'udn' : selectedUdn,
+				'urn' : selectedUrn
+			}, renderVars, 'json');
+		}
+	})
+
+	$('#reloadDevices').click(listDevices);
+
+	listDevices();
+});
+