FELIX-3111 : Separate OBR Plugin
FELIX-3107 : Separate Shell Plugin
FELIX-3099 : Separate Deployment Admin plugin
FELIX-3100 : Separate SCR plugin

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1169777 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/webconsole-plugins/obr/src/main/java/org/apache/felix/webconsole/plugins/obr/internal/AbstractBundleRepositoryRenderHelper.java b/webconsole-plugins/obr/src/main/java/org/apache/felix/webconsole/plugins/obr/internal/AbstractBundleRepositoryRenderHelper.java
new file mode 100644
index 0000000..9e0b5f2
--- /dev/null
+++ b/webconsole-plugins/obr/src/main/java/org/apache/felix/webconsole/plugins/obr/internal/AbstractBundleRepositoryRenderHelper.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.felix.webconsole.plugins.obr.internal;

+

+

+import java.io.IOException;

+

+import javax.servlet.ServletException;

+import org.apache.felix.webconsole.AbstractWebConsolePlugin;

+import org.osgi.framework.Bundle;

+import org.osgi.framework.BundleContext;

+import org.osgi.util.tracker.ServiceTracker;

+

+

+abstract class AbstractBundleRepositoryRenderHelper

+{

+

+    protected final AbstractWebConsolePlugin logger;

+

+    private final ServiceTracker repositoryAdmin;

+

+

+    protected AbstractBundleRepositoryRenderHelper( final AbstractWebConsolePlugin logger,

+        final BundleContext bundleContext, final String serviceName )

+    {

+        this.logger = logger;

+        this.repositoryAdmin = new ServiceTracker( bundleContext, serviceName, null );

+        this.repositoryAdmin.open();

+    }

+

+

+    void dispose()

+    {

+        repositoryAdmin.close();

+    }

+

+

+    boolean hasRepositoryAdmin()

+    {

+        return getRepositoryAdmin() != null;

+    }

+

+

+    protected final Object getRepositoryAdmin()

+    {

+        return repositoryAdmin.getService();

+    }

+

+

+    abstract void doDeploy( String[] bundles, boolean start, boolean optional );

+

+

+    abstract void doAction( String action, String urlParam ) throws IOException, ServletException;

+

+

+    abstract String getData( final String filter, final boolean details, final Bundle[] bundles );

+

+}
\ No newline at end of file
diff --git a/webconsole-plugins/obr/src/main/java/org/apache/felix/webconsole/plugins/obr/internal/Activator.java b/webconsole-plugins/obr/src/main/java/org/apache/felix/webconsole/plugins/obr/internal/Activator.java
new file mode 100644
index 0000000..0a53380
--- /dev/null
+++ b/webconsole-plugins/obr/src/main/java/org/apache/felix/webconsole/plugins/obr/internal/Activator.java
@@ -0,0 +1,103 @@
+/*

+ * 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.obr.internal;

+

+import org.apache.felix.webconsole.SimpleWebConsolePlugin;

+import org.osgi.framework.BundleActivator;

+import org.osgi.framework.BundleContext;

+import org.osgi.framework.Constants;

+import org.osgi.framework.Filter;

+import org.osgi.framework.ServiceReference;

+import org.osgi.util.tracker.ServiceTracker;

+import org.osgi.util.tracker.ServiceTrackerCustomizer;

+

+/**

+ * Activator is the main starting class.

+ */

+public class Activator implements BundleActivator, ServiceTrackerCustomizer, Constants

+{

+

+    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;

+        Filter filter = context.createFilter("(|" //$NON-NLS-1$

+            + '(' + OBJECTCLASS + "=org.osgi.service.obr.RepositoryAdmin)" //$NON-NLS-1$

+            + '(' + OBJECTCLASS + "=org.apache.felix.bundlerepository.RepositoryAdmin)" //$NON-NLS-1$

+            + ')');

+        this.tracker = new ServiceTracker(context, filter, 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().register(context);

+        }

+

+        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 (tracker.getTrackingCount() == 0 && plugin != null)

+        {

+            plugin.unregister();

+            this.plugin = null;

+        }

+

+    }

+}

diff --git a/webconsole-plugins/obr/src/main/java/org/apache/felix/webconsole/plugins/obr/internal/FelixBundleRepositoryRenderHelper.java b/webconsole-plugins/obr/src/main/java/org/apache/felix/webconsole/plugins/obr/internal/FelixBundleRepositoryRenderHelper.java
new file mode 100644
index 0000000..9858577
--- /dev/null
+++ b/webconsole-plugins/obr/src/main/java/org/apache/felix/webconsole/plugins/obr/internal/FelixBundleRepositoryRenderHelper.java
@@ -0,0 +1,276 @@
+/*

+ * 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.obr.internal;

+

+

+import java.io.IOException;

+import javax.servlet.ServletException;

+import org.apache.felix.bundlerepository.Capability;

+import org.apache.felix.bundlerepository.Property;

+import org.apache.felix.bundlerepository.Reason;

+import org.apache.felix.bundlerepository.Repository;

+import org.apache.felix.bundlerepository.RepositoryAdmin;

+import org.apache.felix.bundlerepository.Requirement;

+import org.apache.felix.bundlerepository.Resolver;

+import org.apache.felix.bundlerepository.Resource;

+import org.apache.felix.webconsole.AbstractWebConsolePlugin;

+import org.json.JSONException;

+import org.json.JSONObject;

+import org.osgi.framework.Bundle;

+import org.osgi.framework.BundleContext;

+import org.osgi.framework.Constants;

+import org.osgi.framework.InvalidSyntaxException;

+

+

+/**

+ * This class provides a plugin for rendering the available OSGi Bundle Repositories

+ * and the resources they provide.

+ */

+class FelixBundleRepositoryRenderHelper extends AbstractBundleRepositoryRenderHelper

+{

+

+    FelixBundleRepositoryRenderHelper( AbstractWebConsolePlugin logger, BundleContext bundleContext )

+    {

+        super( logger, bundleContext, RepositoryAdmin.class.getName() );

+    }

+

+

+    String getData( final String filter, final boolean details, Bundle[] bundles )

+    {

+        RepositoryAdmin admin = ( RepositoryAdmin ) getRepositoryAdmin();

+        if ( admin != null )

+        {

+            JSONObject json = new JSONObject();

+            try

+            {

+                json.put( "status", true ); //$NON-NLS-1$

+                json.put( "details", details ); //$NON-NLS-1$

+

+                final Repository repositories[] = admin.listRepositories();

+                for ( int i = 0; repositories != null && i < repositories.length; i++ )

+                {

+                    json.append( "repositories", new JSONObject() //$NON-NLS-1$

+                        .put( "lastModified", repositories[i].getLastModified() ) //$NON-NLS-1$

+                        .put( "name", repositories[i].getName() ) //$NON-NLS-1$

+                        .put( "url", repositories[i].getURI() ) ); //$NON-NLS-1$

+                }

+

+                Resource[] resources = admin.discoverResources( filter );

+                for ( int i = 0; resources != null && i < resources.length; i++ )

+                {

+                    json.append( "resources", toJSON( resources[i], bundles, details ) ); //$NON-NLS-1$

+                }

+

+            }

+            catch ( JSONException e )

+            {

+                logger.log( "Failed to serialize repository to JSON object.", e );

+            }

+            catch ( Exception e )

+            {

+                logger.log( "Failed to parse filter '" + filter + "'", e );

+                try

+                {

+                    String reason = "filter=" + filter;

+                    if ( e.getMessage() != null )

+                    {

+                        reason = e.getMessage() + "(" + reason + ")";

+                    }

+                    json.put( "error", reason ); //$NON-NLS-1$

+                }

+                catch ( JSONException je )

+                {

+                    // ignore

+                }

+            }

+            return json.toString();

+        }

+

+        // fall back to no data

+        return "{}"; //$NON-NLS-1$

+    }

+

+

+    final void doAction( String action, String urlParam ) throws IOException, ServletException

+    {

+        RepositoryAdmin admin = ( RepositoryAdmin ) getRepositoryAdmin();

+        Repository[] repos = admin.listRepositories();

+        Repository repo = getRepository( repos, urlParam );

+

+        String uri = repo != null ? repo.getURI() : urlParam;

+

+        if ( "delete".equals( action ) ) //$NON-NLS-1$

+        {

+            if ( !admin.removeRepository( uri ) )

+            {

+                throw new ServletException( "Failed to remove repository with URL " + uri );

+            }

+        }

+        else if ( "add".equals( action ) || "refresh".equals( action ) ) //$NON-NLS-1$ //$NON-NLS-2$

+        {

+            try

+            {

+                admin.addRepository( uri );

+            }

+            catch ( IOException e )

+            {

+                throw e;

+            }

+            catch ( Exception e )

+            {

+                throw new ServletException( "Failed to " + action + " repository " + uri + ": " + e.toString() );

+            }

+

+        }

+    }

+

+

+    final void doDeploy( String[] bundles, boolean start, boolean optional )

+    {

+        try

+        {

+            // check whether we have to do something

+            if ( bundles == null || bundles.length == 0 )

+            {

+                logger.log( "No resources to deploy" );

+                return;

+            }

+

+            RepositoryAdmin repoAdmin = ( RepositoryAdmin ) getRepositoryAdmin();

+            Resolver resolver = repoAdmin.resolver();

+

+            // prepare the deployment

+            for ( int i = 0; i < bundles.length; i++ )

+            {

+                String bundle = bundles[i];

+                if ( bundle == null || bundle.equals( "-" ) ) //$NON-NLS-1$

+                {

+                    continue;

+                }

+

+                String filter = "(id=" + bundle + ")";

+                Resource[] resources = repoAdmin.discoverResources( filter );

+                if ( resources != null && resources.length > 0 )

+                {

+                    resolver.add( resources[0] );

+                }

+            }

+

+            FelixDeployer.deploy( resolver, logger, start, optional );

+        }

+        catch ( InvalidSyntaxException e )

+        {

+            throw new IllegalStateException( e );

+        }

+    }

+

+

+    private final Repository getRepository( Repository[] repos, String repositoryUrl )

+    {

+        if ( repositoryUrl == null || repositoryUrl.length() == 0 )

+        {

+            return null;

+        }

+

+        for ( int i = 0; i < repos.length; i++ )

+        {

+            if ( repositoryUrl.equals( repos[i].getURI() ) )

+            {

+                return repos[i];

+            }

+        }

+

+        return null;

+    }

+

+

+    private final JSONObject toJSON( Resource resource, Bundle[] bundles, boolean details ) throws JSONException

+    {

+        final String symbolicName = resource.getSymbolicName();

+        final String version = resource.getVersion().toString();

+        boolean installed = false;

+        for ( int i = 0; symbolicName != null && !installed && bundles != null && i < bundles.length; i++ )

+        {

+            final String ver = ( String ) bundles[i].getHeaders( "" ).get( Constants.BUNDLE_VERSION ); //$NON-NLS-1$

+            installed = symbolicName.equals( bundles[i].getSymbolicName() ) && version.equals( ver );

+        }

+        JSONObject json = new JSONObject( resource.getProperties() ) //

+            .put( "id", resource.getId() ) // //$NON-NLS-1$

+            .put( "presentationname", resource.getPresentationName() ) // //$NON-NLS-1$

+            .put( "symbolicname", symbolicName ) // //$NON-NLS-1$

+            .put( "url", resource.getURI() ) // //$NON-NLS-1$

+            .put( "version", version ) // //$NON-NLS-1$

+            .put( "categories", resource.getCategories() ) // //$NON-NLS-1$

+            .put( "installed", installed ); //$NON-NLS-1$

+

+        if ( details )

+        {

+            Capability[] caps = resource.getCapabilities();

+            for ( int i = 0; caps != null && i < caps.length; i++ )

+            {

+                json.append( "capabilities", new JSONObject() //$NON-NLS-1$

+                    .put( "name", caps[i].getName() ) //$NON-NLS-1$

+                    .put( "properties", toJSON( caps[i].getProperties() ) ) ); //$NON-NLS-1$

+            }

+            Requirement[] reqs = resource.getRequirements();

+            for ( int i = 0; reqs != null && i < reqs.length; i++ )

+            {

+                json.append( "requirements", new JSONObject() //$NON-NLS-1$

+                    .put( "name", reqs[i].getName() ) //$NON-NLS-1$

+                    .put( "filter", reqs[i].getFilter() ) //$NON-NLS-1$

+                    .put( "optional", reqs[i].isOptional() ) ); //$NON-NLS-1$

+            }

+

+            final RepositoryAdmin admin = ( RepositoryAdmin ) getRepositoryAdmin();

+            Resolver resolver = admin.resolver();

+            resolver.add( resource );

+            resolver.resolve( Resolver.NO_OPTIONAL_RESOURCES );

+            Resource[] required = resolver.getRequiredResources();

+            for ( int i = 0; required != null && i < required.length; i++ )

+            {

+                json.append( "required", toJSON( required[i], bundles, false ) ); //$NON-NLS-1$

+            }

+            Resource[] optional = resolver.getOptionalResources();

+            for ( int i = 0; optional != null && i < optional.length; i++ )

+            {

+                json.append( "optional", toJSON( optional[i], bundles, false ) ); //$NON-NLS-1$

+            }

+            Reason[] unsatisfied = resolver.getUnsatisfiedRequirements();

+            for ( int i = 0; unsatisfied != null && i < unsatisfied.length; i++ )

+            {

+                json.append( "unsatisfied", new JSONObject() //$NON-NLS-1$

+                    .put( "name", unsatisfied[i].getRequirement().getName() ) //$NON-NLS-1$

+                    .put( "filter", unsatisfied[i].getRequirement().getFilter() ) //$NON-NLS-1$

+                    .put( "optional", unsatisfied[i].getRequirement().isOptional() ) ); //$NON-NLS-1$

+            }

+        }

+        return json;

+    }

+

+

+    private JSONObject toJSON( final Property[] props ) throws JSONException

+    {

+        JSONObject json = new JSONObject();

+        for ( int i = 0; props != null && i < props.length; i++ )

+        {

+            json.put( props[i].getName(), props[i].getValue() );

+        }

+        return json;

+    }

+}

diff --git a/webconsole-plugins/obr/src/main/java/org/apache/felix/webconsole/plugins/obr/internal/FelixDeployer.java b/webconsole-plugins/obr/src/main/java/org/apache/felix/webconsole/plugins/obr/internal/FelixDeployer.java
new file mode 100644
index 0000000..bfed872
--- /dev/null
+++ b/webconsole-plugins/obr/src/main/java/org/apache/felix/webconsole/plugins/obr/internal/FelixDeployer.java
@@ -0,0 +1,114 @@
+/*

+ * 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.obr.internal;

+

+

+import org.apache.felix.bundlerepository.Reason;

+import org.apache.felix.bundlerepository.Resolver;

+import org.apache.felix.bundlerepository.Resource;

+import org.apache.felix.webconsole.AbstractWebConsolePlugin;

+import org.osgi.service.log.LogService;

+

+

+class FelixDeployer implements Runnable

+{

+

+    private final Resolver obrResolver;

+

+    private final AbstractWebConsolePlugin logger;

+

+    private final boolean startBundles;

+

+    private final boolean optionalDependencies;

+

+    static void deploy(Resolver obrResolver, AbstractWebConsolePlugin logger, boolean startBundles,

+        boolean optionalDependencies)

+    {

+        final FelixDeployer d = new FelixDeployer(obrResolver, logger, startBundles, optionalDependencies);

+        final Thread t = new Thread(d, "OBR Bundle Deployer (Apache Felix API)");

+        t.start();

+    }

+

+    private FelixDeployer(Resolver obrResolver, AbstractWebConsolePlugin logger, boolean startBundles,

+        boolean optionalDependencies)

+    {

+        this.obrResolver = obrResolver;

+        this.logger = logger;

+        this.startBundles = startBundles;

+        this.optionalDependencies = optionalDependencies;

+    }

+

+    public void run()

+    {

+        int flags = 0;

+        flags += (startBundles ? Resolver.START : 0);

+        flags += (optionalDependencies ? 0 : Resolver.NO_OPTIONAL_RESOURCES);

+        try

+        {

+            if ( obrResolver.resolve( flags ) )

+            {

+

+                logResource( "Installing Requested Resources", obrResolver.getAddedResources() );

+                logResource( "Installing Required Resources", obrResolver.getRequiredResources() );

+                logResource( "Installing Optional Resources", obrResolver.getOptionalResources() );

+

+                obrResolver.deploy( flags );

+            }

+            else

+            {

+                logRequirements( "Cannot Install requested bundles due to unsatisfied requirements",

+                    obrResolver.getUnsatisfiedRequirements() );

+            }

+        }

+        catch ( Exception ie )

+        {

+            logger.log( LogService.LOG_ERROR, "Cannot install bundles", ie );

+        }

+    }

+

+

+    private void logResource( String message, Resource[] res )

+    {

+        if ( res != null && res.length > 0 )

+        {

+            logger.log( LogService.LOG_INFO, message );

+            for ( int i = 0; i < res.length; i++ )

+            {

+                logger.log( LogService.LOG_INFO, "  " + i + ": " + res[i].getSymbolicName() + ", "

+                    + res[i].getVersion() );

+            }

+        }

+    }

+

+

+    private void logRequirements( String message, Reason[] reasons )

+    {

+        logger.log( LogService.LOG_ERROR, message );

+        for ( int i = 0; reasons != null && i < reasons.length; i++ )

+        {

+            String moreInfo = reasons[i].getRequirement().getComment();

+            if ( moreInfo == null )

+            {

+                moreInfo = reasons[i].getRequirement().getFilter().toString();

+            }

+            logger.log( LogService.LOG_ERROR, "  " + i + ": " + reasons[i].getRequirement().getName() + " (" + moreInfo + ")" );

+        }

+    }

+

+}

diff --git a/webconsole-plugins/obr/src/main/java/org/apache/felix/webconsole/plugins/obr/internal/OsgiBundleRepositoryRenderHelper.java b/webconsole-plugins/obr/src/main/java/org/apache/felix/webconsole/plugins/obr/internal/OsgiBundleRepositoryRenderHelper.java
new file mode 100644
index 0000000..fb22b5b
--- /dev/null
+++ b/webconsole-plugins/obr/src/main/java/org/apache/felix/webconsole/plugins/obr/internal/OsgiBundleRepositoryRenderHelper.java
@@ -0,0 +1,256 @@
+/*

+ * 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.obr.internal;

+

+

+import java.io.IOException;

+import java.net.URL;

+import javax.servlet.ServletException;

+import org.apache.felix.webconsole.AbstractWebConsolePlugin;

+import org.json.JSONException;

+import org.json.JSONObject;

+import org.osgi.framework.Bundle;

+import org.osgi.framework.BundleContext;

+import org.osgi.framework.Constants;

+import org.osgi.service.obr.Capability;

+import org.osgi.service.obr.Repository;

+import org.osgi.service.obr.RepositoryAdmin;

+import org.osgi.service.obr.Requirement;

+import org.osgi.service.obr.Resolver;

+import org.osgi.service.obr.Resource;

+

+

+/**

+ * This class provides a plugin for rendering the available OSGi Bundle Repositories

+ * and the resources they provide.

+ */

+class OsgiBundleRepositoryRenderHelper extends AbstractBundleRepositoryRenderHelper

+{

+

+    OsgiBundleRepositoryRenderHelper( final AbstractWebConsolePlugin logger, final BundleContext bundleContext )

+    {

+        super( logger, bundleContext, RepositoryAdmin.class.getName() );

+    }

+

+

+    String getData( final String filter, final boolean details, Bundle[] bundles )

+    {

+        RepositoryAdmin admin = ( RepositoryAdmin ) getRepositoryAdmin();

+        if ( admin != null )

+        {

+            JSONObject json = new JSONObject();

+            try

+            {

+                json.put( "status", true ); //$NON-NLS-1$

+                json.put( "details", details ); //$NON-NLS-1$

+

+                final Repository repositories[] = admin.listRepositories();

+                for ( int i = 0; repositories != null && i < repositories.length; i++ )

+                {

+                    json.append( "repositories", new JSONObject() //$NON-NLS-1$

+                        .put( "lastModified", repositories[i].getLastModified() ) //$NON-NLS-1$

+                        .put( "name", repositories[i].getName() ) //$NON-NLS-1$

+                        .put( "url", repositories[i].getURL() ) ); //$NON-NLS-1$

+                }

+

+                Resource[] resources = admin.discoverResources( filter );

+                for ( int i = 0; resources != null && i < resources.length; i++ )

+                {

+                    json.append( "resources", toJSON( resources[i], bundles, details ) ); //$NON-NLS-1$

+                }

+            }

+            catch ( JSONException e )

+            {

+                logger.log( "Failed to serialize repository to JSON object.", e );

+            }

+            catch ( Exception e )

+            {

+                logger.log( "Failed to parse filter '" + filter + "'", e );

+                try

+                {

+                    String reason = "filter=" + filter;

+                    if ( e.getMessage() != null )

+                    {

+                        reason = e.getMessage() + "(" + reason + ")";

+                    }

+                    json.put( "error", reason ); //$NON-NLS-1$

+                }

+                catch ( JSONException je )

+                {

+                    // ignore

+                }

+            }

+

+            return json.toString();

+        }

+

+        // fall back to no data

+        return "{}"; //$NON-NLS-1$

+    }

+

+    void doAction( String action, String urlParam ) throws IOException, ServletException

+    {

+        RepositoryAdmin admin = ( RepositoryAdmin ) getRepositoryAdmin();

+        Repository[] repos = admin.listRepositories();

+        Repository repo = getRepository( repos, urlParam );

+

+        URL uri = repo != null ? repo.getURL() : new URL( urlParam );

+

+        if ( "delete".equals( action ) ) //$NON-NLS-1$

+        {

+            if ( !admin.removeRepository( uri ) )

+            {

+                throw new ServletException( "Failed to remove repository with URL " + uri );

+            }

+        }

+        else if ( "add".equals( action ) || "refresh".equals( action ) ) //$NON-NLS-1$ //$NON-NLS-2$

+        {

+            try

+            {

+                admin.addRepository( uri );

+            }

+            catch ( IOException e )

+            {

+                throw e;

+            }

+            catch ( Exception e )

+            {

+                throw new ServletException( "Failed to " + action + " repository " + uri + ": " + e.toString() );

+            }

+

+        }

+    }

+

+

+    final void doDeploy( String[] bundles, boolean start, boolean optional )

+    {

+        // check whether we have to do something

+        if ( bundles == null || bundles.length == 0 )

+        {

+            logger.log( "No resources to deploy" );

+            return;

+        }

+

+        RepositoryAdmin repoAdmin = ( RepositoryAdmin ) getRepositoryAdmin();

+        Resolver resolver = repoAdmin.resolver();

+

+        // prepare the deployment

+        for ( int i = 0; i < bundles.length; i++ )

+        {

+            String bundle = bundles[i];

+            if ( bundle == null || bundle.equals( "-" ) )

+            {

+                continue;

+            }

+

+            String filter = "(id=" + bundle + ")";

+            Resource[] resources = repoAdmin.discoverResources( filter );

+            if ( resources != null && resources.length > 0 )

+            {

+                resolver.add( resources[0] );

+            }

+        }

+

+        OsgiDeployer.deploy( resolver, logger, start );

+    }

+

+

+    private final Repository getRepository( Repository[] repos, String repositoryUrl )

+    {

+        if ( repositoryUrl == null || repositoryUrl.length() == 0 )

+        {

+            return null;

+        }

+

+        for ( int i = 0; i < repos.length; i++ )

+        {

+            if ( repositoryUrl.equals( repos[i].getURL().toString() ) )

+            {

+                return repos[i];

+            }

+        }

+

+        return null;

+    }

+

+

+    private final JSONObject toJSON( Resource resource, Bundle[] bundles, boolean details ) throws JSONException

+    {

+        final String symbolicName = resource.getSymbolicName();

+        final String version = resource.getVersion().toString();

+        boolean installed = false;

+        for ( int i = 0; symbolicName != null && !installed && bundles != null && i < bundles.length; i++ )

+        {

+            final String ver = ( String ) bundles[i].getHeaders( "" ).get( Constants.BUNDLE_VERSION ); //$NON-NLS-1$

+            installed = symbolicName.equals( bundles[i].getSymbolicName() ) && version.equals( ver );

+        }

+        JSONObject json = new JSONObject( resource.getProperties() ) //

+            .put( "id", resource.getId() ) // //$NON-NLS-1$

+            .put( "presentationname", resource.getPresentationName() ) // //$NON-NLS-1$

+            .put( "symbolicname", symbolicName ) // //$NON-NLS-1$

+            .put( "url", resource.getURL() ) // //$NON-NLS-1$

+            .put( "version", version ) // //$NON-NLS-1$

+            .put( "categories", resource.getCategories() ) // //$NON-NLS-1$

+            .put( "installed", installed ); //$NON-NLS-1$

+

+        if ( details )

+        {

+            Capability[] caps = resource.getCapabilities();

+            for ( int i = 0; caps != null && i < caps.length; i++ )

+            {

+                json.append( "capabilities", new JSONObject() //$NON-NLS-1$

+                    .put( "name", caps[i].getName() ) //$NON-NLS-1$

+                    .put( "properties", new JSONObject( caps[i].getProperties() ) ) ); //$NON-NLS-1$

+            }

+            Requirement[] reqs = resource.getRequirements();

+            for ( int i = 0; reqs != null && i < reqs.length; i++ )

+            {

+                json.append( "requirements", new JSONObject() //$NON-NLS-1$

+                    .put( "name", reqs[i].getName() ) //$NON-NLS-1$

+                    .put( "filter", reqs[i].getFilter() ) //$NON-NLS-1$

+                    .put( "optional", reqs[i].isOptional() ) ); //$NON-NLS-1$

+            }

+

+            final RepositoryAdmin admin = ( RepositoryAdmin ) getRepositoryAdmin();

+            Resolver resolver = admin.resolver();

+            resolver.add( resource );

+            resolver.resolve(); // (Resolver.NO_OPTIONAL_RESOURCES);

+            Resource[] required = resolver.getRequiredResources();

+            for ( int i = 0; required != null && i < required.length; i++ )

+            {

+                json.append( "required", toJSON( required[i], bundles, false ) ); //$NON-NLS-1$

+            }

+            Resource[] optional = resolver.getOptionalResources();

+            for ( int i = 0; optional != null && i < optional.length; i++ )

+            {

+                json.append( "optional", toJSON( optional[i], bundles, false ) ); //$NON-NLS-1$

+            }

+            Requirement/*Reason*/[] unsatisfied = resolver.getUnsatisfiedRequirements();

+            for ( int i = 0; unsatisfied != null && i < unsatisfied.length; i++ )

+            {

+                json.append( "unsatisfied", new JSONObject() //$NON-NLS-1$

+                    .put( "name", unsatisfied[i].getName() ) //$NON-NLS-1$

+                    .put( "filter", unsatisfied[i].getFilter() ) //$NON-NLS-1$

+                    .put( "optional", unsatisfied[i].isOptional() ) ); //$NON-NLS-1$

+            }

+        }

+        return json;

+    }

+

+}

diff --git a/webconsole-plugins/obr/src/main/java/org/apache/felix/webconsole/plugins/obr/internal/OsgiDeployer.java b/webconsole-plugins/obr/src/main/java/org/apache/felix/webconsole/plugins/obr/internal/OsgiDeployer.java
new file mode 100644
index 0000000..e20da6f
--- /dev/null
+++ b/webconsole-plugins/obr/src/main/java/org/apache/felix/webconsole/plugins/obr/internal/OsgiDeployer.java
@@ -0,0 +1,110 @@
+/*
+ * 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.obr.internal;
+
+
+import org.apache.felix.webconsole.AbstractWebConsolePlugin;
+import org.osgi.service.log.LogService;
+import org.osgi.service.obr.Requirement;
+import org.osgi.service.obr.Resolver;
+import org.osgi.service.obr.Resource;
+
+
+class OsgiDeployer implements Runnable
+{
+
+    private final Resolver obrResolver;
+
+    private final AbstractWebConsolePlugin logger;
+
+    private final boolean startBundles;
+
+
+    OsgiDeployer( Resolver obrResolver, AbstractWebConsolePlugin logger, boolean startBundles )
+    {
+        this.obrResolver = obrResolver;
+        this.logger = logger;
+        this.startBundles = startBundles;
+    }
+
+    static void deploy( Resolver obrResolver, AbstractWebConsolePlugin logger, boolean startBundles )
+    {
+        final OsgiDeployer d = new OsgiDeployer( obrResolver, logger, startBundles );
+        final Thread t = new Thread( d, "OBR Bundle Deployer (OSGi API)" );
+        t.start();
+    }
+
+    /**
+     * @see java.lang.Runnable#run()
+     */
+    public void run()
+    {
+        try
+        {
+            if ( obrResolver.resolve() )
+            {
+
+                logResource( logger, "Installing Requested Resources", obrResolver.getAddedResources() );
+                logResource( logger, "Installing Required Resources", obrResolver.getRequiredResources() );
+                logResource( logger, "Installing Optional Resources", obrResolver.getOptionalResources() );
+
+                obrResolver.deploy( startBundles );
+            }
+            else
+            {
+                logRequirements( logger, "Cannot Install requested bundles due to unsatisfied requirements",
+                    obrResolver.getUnsatisfiedRequirements() );
+            }
+        }
+        catch ( Exception ie )
+        {
+            logger.log( LogService.LOG_ERROR, "Cannot install bundles", ie );
+        }
+    }
+
+
+    public static void logResource( AbstractWebConsolePlugin logger, String message, Resource[] res )
+    {
+        if ( res != null && res.length > 0 )
+        {
+            logger.log( LogService.LOG_INFO, message );
+            for ( int i = 0; i < res.length; i++ )
+            {
+                logger.log( LogService.LOG_INFO, "  " + i + ": " + res[i].getSymbolicName() + ", "
+                    + res[i].getVersion() );
+            }
+        }
+    }
+
+
+    public static void logRequirements( AbstractWebConsolePlugin logger, String message, Requirement[] reasons )
+    {
+        logger.log( LogService.LOG_ERROR, message );
+        for ( int i = 0; reasons != null && i < reasons.length; i++ )
+        {
+            String moreInfo = reasons[i].getComment();
+            if ( moreInfo == null )
+            {
+                moreInfo = reasons[i].getFilter().toString();
+            }
+            logger.log( LogService.LOG_ERROR, "  " + i + ": " + reasons[i].getName() + " (" + moreInfo + ")" );
+        }
+    }
+
+}
diff --git a/webconsole-plugins/obr/src/main/java/org/apache/felix/webconsole/plugins/obr/internal/WebConsolePlugin.java b/webconsole-plugins/obr/src/main/java/org/apache/felix/webconsole/plugins/obr/internal/WebConsolePlugin.java
new file mode 100644
index 0000000..c80a88c
--- /dev/null
+++ b/webconsole-plugins/obr/src/main/java/org/apache/felix/webconsole/plugins/obr/internal/WebConsolePlugin.java
@@ -0,0 +1,323 @@
+/*

+ * 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.obr.internal;

+

+

+import java.io.IOException;

+import java.util.Enumeration;

+

+import javax.servlet.ServletException;

+import javax.servlet.http.HttpServletRequest;

+import javax.servlet.http.HttpServletResponse;

+

+import org.apache.felix.webconsole.DefaultVariableResolver;

+import org.apache.felix.webconsole.SimpleWebConsolePlugin;

+import org.apache.felix.webconsole.WebConsoleUtil;

+

+

+/**

+ * This class provides a plugin for rendering the available OSGi Bundle Repositories

+ * and the resources they provide.

+ */

+class WebConsolePlugin extends SimpleWebConsolePlugin 

+{

+    private static final String LABEL = "obr"; //$NON-NLS-1$

+    private static final String TITLE = "%obr.pluginTitle"; //$NON-NLS-1$

+    private static final String CSS[] = { "/" + LABEL + "/res/plugin.css" }; //$NON-NLS-1$ //$NON-NLS-2$

+

+    // templates

+    private final String TEMPLATE;

+

+    private AbstractBundleRepositoryRenderHelper helper;

+

+

+    /**

+     *

+     */

+    public WebConsolePlugin()

+    {

+        super( LABEL, TITLE, CSS );

+

+        // load templates

+        TEMPLATE = readTemplateFile("/res/plugin.html"); //$NON-NLS-1$

+    }

+

+

+    /**

+     * @see org.apache.felix.webconsole.SimpleWebConsolePlugin#deactivate()

+     */

+    public void deactivate()

+    {

+        if ( helper != null )

+        {

+            helper.dispose();

+            helper = null;

+        }

+

+        super.deactivate();

+    }

+

+

+    /**

+     * @see org.apache.felix.webconsole.AbstractWebConsolePlugin#renderContent(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)

+     */

+    protected void renderContent( HttpServletRequest request, HttpServletResponse response ) throws IOException

+    {

+        // prepare variables

+        DefaultVariableResolver vars = ( ( DefaultVariableResolver ) WebConsoleUtil.getVariableResolver( request ) );

+        vars.put( "__data__", getData( request ) ); //$NON-NLS-1$

+

+        response.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 ( !hasRepositoryAdmin() )

+        {

+            response.sendError( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "RepositoryAdmin service is missing" );

+            return;

+        }

+

+        final String action = request.getParameter( "action" ); //$NON-NLS-1$

+        final String deploy = request.getParameter( "deploy" ); //$NON-NLS-1$

+        final String deploystart = request.getParameter( "deploystart" ); //$NON-NLS-1$

+        final String optional = request.getParameter( "optional" ); //$NON-NLS-1$

+

+        if ( action != null )

+        {

+            doAction( action, request.getParameter( "url" ) ); //$NON-NLS-1$

+            response.getWriter().print( getData( request ) );

+            return;

+        }

+

+        if ( deploy != null || deploystart != null )

+        {

+            doDeploy( request.getParameterValues( "bundle" ), deploystart != null, optional != null ); //$NON-NLS-1$

+            doGet( request, response );

+            return;

+        }

+

+        super.doPost( request, response );

+    }

+

+

+    AbstractBundleRepositoryRenderHelper getHelper()

+    {

+        if ( helper == null )

+        {

+            try

+            {

+                helper = new FelixBundleRepositoryRenderHelper( this, getBundleContext() );

+            }

+            catch ( Throwable felixt )

+            {

+                // ClassNotFoundException, ClassDefNotFoundError

+

+                try

+                {

+                    helper = new OsgiBundleRepositoryRenderHelper( this, getBundleContext() );

+                }

+                catch ( Throwable osgit )

+                {

+                    // ClassNotFoundException, ClassDefNotFoundError

+                }

+            }

+        }

+

+        return helper;

+    }

+

+

+    private boolean hasRepositoryAdmin()

+    {

+        AbstractBundleRepositoryRenderHelper helper = getHelper();

+        return helper != null && helper.hasRepositoryAdmin();

+    }

+

+

+    private String getData( final HttpServletRequest request )

+    {

+        AbstractBundleRepositoryRenderHelper helper = getHelper();

+        if ( helper == null || !helper.hasRepositoryAdmin() )

+        {

+            return "{}"; //$NON-NLS-1$

+        }

+

+        RequestInfo info = new RequestInfo( request );

+

+        final String filter;

+        String list = info.getList();

+        if ( list != null )

+        {

+            if ( "-".equals( list ) ) //$NON-NLS-1$

+            {

+                StringBuffer sb = new StringBuffer( "(!(|" ); //$NON-NLS-1$

+                for ( int c = 0; c < 26; c++ )

+                {

+                    sb.append( "(presentationname=" ).append( ( char ) ( 'a' + c ) ) //$NON-NLS-1$

+                      .append( "*)(presentationname=" ).append( ( char ) ( 'A' + c ) ) //$NON-NLS-1$

+                      .append( "*)" ); //$NON-NLS-1$

+                }

+                sb.append( "))" ); //$NON-NLS-1$

+                filter = sb.toString();

+            }

+            else

+            {

+                filter = "(|(presentationname=" + list.toLowerCase() //$NON-NLS-1$

+                    + "*)(presentationname=" + list.toUpperCase() + "*))"; //$NON-NLS-1$ //$NON-NLS-2$

+            }

+        }

+        else

+        {

+            String query = info.getQuery();

+            if ( query != null )

+            {

+                if ( query.indexOf( '=' ) > 0 )

+                {

+                    if ( query.startsWith( "(" ) ) //$NON-NLS-1$

+                    {

+                        filter = query;

+                    }

+                    else

+                    {

+                        filter = "(" + query + ")"; //$NON-NLS-1$ //$NON-NLS-2$

+                    }

+                }

+                else

+                {

+                    filter = "(|(presentationame=*" + query + "*)(symbolicname=*" + query + "*))"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$

+                }

+            }

+            else

+            {

+                StringBuffer sb = new StringBuffer( "(&" ); //$NON-NLS-1$

+                for ( Enumeration e = request.getParameterNames(); e.hasMoreElements(); )

+                {

+                    String k = ( String ) e.nextElement();

+                    String v = request.getParameter( k );

+                    if ( v != null && v.length() > 0 

+                        && !"details".equals( k )  //$NON-NLS-1$

+                        && !"deploy".equals( k ) //$NON-NLS-1$

+                        && !"deploystart".equals( k )  //$NON-NLS-1$

+                        && !"bundle".equals( k )  //$NON-NLS-1$

+                        && !"optional".equals( k ) ) //$NON-NLS-1$

+                    {

+                        sb.append( '(' ).append( k ).append( '=' ).append( v ).append( ')' );

+                    }

+                }

+                sb.append( ')' );

+                filter = (sb.length() > 3) ? sb.toString() : null;

+            }

+        }

+

+        return helper.getData( filter, info.showDetails(), getBundleContext().getBundles() );

+    }

+

+

+    private void doAction( String action, String urlParam ) throws IOException, ServletException

+    {

+        AbstractBundleRepositoryRenderHelper helper = getHelper();

+        if ( helper != null )

+        {

+            helper.doAction( action, urlParam );

+        }

+    }

+

+

+    private void doDeploy( String[] bundles, boolean start, boolean optional )

+    {

+        AbstractBundleRepositoryRenderHelper helper = getHelper();

+        if ( helper != null )

+        {

+            helper.doDeploy( bundles, start, optional );

+        }

+    }

+

+    private static class RequestInfo {

+

+        private final HttpServletRequest request;

+

+        private boolean details;

+        private String query;

+        private String list;

+

+

+        RequestInfo( final HttpServletRequest request )

+        {

+            this.request = request;

+        }

+

+

+        boolean showDetails()

+        {

+            getQuery();

+            return details;

+        }

+

+

+        String getQuery()

+        {

+            if ( query == null )

+            {

+                String query = WebConsoleUtil.urlDecode( request.getParameter( "query" ) ); //$NON-NLS-1$

+                boolean details = false;

+                if ( query == null && request.getPathInfo().length() > 5 )

+                {

+                    // cut off "/obr/" prefix (might want to use getTitle ??)

+                    String path = request.getPathInfo().substring( 5 );

+                    int slash = path.indexOf( '/' );

+                    if ( slash < 0 )

+                    {

+                        // symbolic name only, version ??

+                        query = "(symbolicname=" + path + ")"; //$NON-NLS-1$ //$NON-NLS-2$

+                    }

+                    else

+                    {

+                        query = "(&(symbolicname=" + path.substring( 0, slash )  //$NON-NLS-1$

+                            + ")(version=" + path.substring( slash + 1 ) + "))"; //$NON-NLS-1$ //$NON-NLS-2$

+                        details = true;

+                    }

+                }

+

+                this.query = query;

+                this.details = details;

+            }

+            return query;

+        }

+

+

+        String getList()

+        {

+            if ( list == null )

+            {

+                list = WebConsoleUtil.urlDecode( request.getParameter( "list" ) ); //$NON-NLS-1$

+                if ( list == null && !request.getParameterNames().hasMoreElements() && getQuery() == null )

+                {

+                    list = "a"; //$NON-NLS-1$

+                }

+            }

+            return list;

+        }

+    }

+}

diff --git a/webconsole-plugins/obr/src/main/native2ascii/OSGI-INF/l10n/bundle_bg.properties b/webconsole-plugins/obr/src/main/native2ascii/OSGI-INF/l10n/bundle_bg.properties
new file mode 100644
index 0000000..89b6aae
--- /dev/null
+++ b/webconsole-plugins/obr/src/main/native2ascii/OSGI-INF/l10n/bundle_bg.properties
@@ -0,0 +1,49 @@
+#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.

+

+#

+# Web Console strings for reference all strings here are commented.

+# This file may be used to produce a translation of the strings

+#

+# Note that properties files are ISO-8859-1 encoded. To provide translations

+# for languages requiring different character encodings, you may use the

+# native2ascii Maven Plugin from http://mojo.codehaus.org/native2ascii-maven-plugin/

+# to translate the natively encoded files to ISO-8859-1 during bundle build

+#

+# Translations requiring non-ISO-8859-1 encoding are placed in the

+# src/main/native2ascii/OSGI-INF/l10n folder and are converted using said

+# plugin while building the bundle

+# native2ascii -encoding utf-8 bundle_bg.raw_properties bundle_bg.properties

+

+# OBR Plugin

+obr.pluginTitle=OSGi Хранилище

+obr.status.ok=Apache Bundle Repository услъгата е достъпна.

+obr.status.no=Нуждаете се от Apache Bundle Repository услугата за да се възползвате от тази функционалност!

+obr.version.select=Изберете версия...

+obr.repo.title=Бъндъл хранилище

+obr.action.add=Добавяне

+obr.action.search=Търсене

+obr.action.search.description=Въведе част от презентацията на бъндъла (име, описание, версия ...), \

+ или валиден OSGi филтър

+obr.action.deploy=Инсталиране на избраните

+obr.action.deploystart=Инсталиране и стартиране на избраните

+obr.repo.name=Име

+obr.repo.url=Адрес

+obr.repo.lastModified=Последна промяна

+obr.repo.actions=Действия

+obr.res.title=Достъпни ресурси

+obr.res.name=Име на ресурса

+obr.res.installedVer=Инсталирана версия

+obr.error=Грешка при филтриране на ресурсите

diff --git a/webconsole-plugins/obr/src/main/native2ascii/OSGI-INF/l10n/bundle_de.properties b/webconsole-plugins/obr/src/main/native2ascii/OSGI-INF/l10n/bundle_de.properties
new file mode 100644
index 0000000..e99dfb5
--- /dev/null
+++ b/webconsole-plugins/obr/src/main/native2ascii/OSGI-INF/l10n/bundle_de.properties
@@ -0,0 +1,50 @@
+#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.

+

+#

+# Web Console strings for reference all strings here are commented.

+# This file may be used to produce a translation of the strings

+#

+# Note that properties files are ISO-8859-1 encoded. To provide translations

+# for languages requiring different character encodings, you may use the

+# native2ascii Maven Plugin from http://mojo.codehaus.org/native2ascii-maven-plugin/

+# to translate the natively encoded files to ISO-8859-1 during bundle build

+#

+# Translations requiring non-ISO-8859-1 encoding are placed in the

+# src/main/native2ascii/OSGI-INF/l10n folder and are converted using said

+# plugin while building the bundle

+#

+

+# OBR Plugin

+obr.pluginTitle=OSGi Repository

+obr.status.ok=Der Apache Bundle Repository Service ist aktiv und bereit.

+obr.status.no=Der Apache Bundle Repository Service ist nicht aktiv!

+obr.version.select=Wähle Version...

+obr.repo.title=Bundle Repositories

+obr.action.add=Hinzufügen

+obr.action.search=Suchen

+obr.action.search.description=Geben Sie einen Teil des Präsentations- oder \

+ symoblischen Namens der gesuchten Bundles oder einen gültigen OSGi \

+ Filter Ausdruck

+obr.action.deploy=Ausgewählte Installieren

+obr.action.deploystart=Ausgewählte Installieren und Starten

+obr.repo.name=Name

+obr.repo.url=URL

+obr.repo.lastModified=Letzte Änderung

+obr.repo.actions=Aktionen

+obr.res.title=Verfügbare Resourcen

+obr.res.name=Resource Name

+obr.res.installedVer=Installierte Version

+obr.error=Fehler bei der Auswahl der Resourcen

diff --git a/webconsole-plugins/obr/src/main/native2ascii/OSGI-INF/l10n/bundle_ru.properties b/webconsole-plugins/obr/src/main/native2ascii/OSGI-INF/l10n/bundle_ru.properties
new file mode 100644
index 0000000..f3cebef
--- /dev/null
+++ b/webconsole-plugins/obr/src/main/native2ascii/OSGI-INF/l10n/bundle_ru.properties
@@ -0,0 +1,49 @@
+#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.
+
+#
+# Web Console strings for reference all strings here are commented.
+# This file may be used to produce a translation of the strings
+#
+# Note that properties files are ISO-8859-1 encoded. To provide translations
+# for languages requiring different character encodings, you may use the
+# native2ascii Maven Plugin from http://mojo.codehaus.org/native2ascii-maven-plugin/
+# to translate the natively encoded files to ISO-8859-1 during bundle build
+#
+# Translations requiring non-ISO-8859-1 encoding are placed in the
+# src/main/native2ascii/OSGI-INF/l10n folder and are converted using said
+# plugin while building the bundle
+#
+
+# OBR Plugin
+obr.pluginTitle=Хранилище OSGi
+obr.status.ok=Сервис Apache Bundle Repository запущен и работает.
+obr.status.no=Сервис Apache Bundle Repository не найден!
+obr.version.select=Выберите версию...
+obr.repo.title=Хранилище модулей
+obr.action.add=Добавить
+obr.action.search=Поиск
+obr.action.search.description=Введите слово, содержащееся в описании модуля (или в имени, версии и т. д.) \
+ или правильный OSGi фильтр
+obr.action.deploy=Установить выбранное
+obr.action.deploystart=Установить и запустить выбранное
+obr.repo.name=Имя
+obr.repo.url=Адрес
+obr.repo.lastModified=Последнее изменение
+obr.repo.actions=Действия
+obr.res.title=Доступные ресурсы
+obr.res.name=Имя ресурса
+obr.res.installedVer=Установленная версия
+obr.error=Ошибка фильтрации ресурсов
diff --git a/webconsole-plugins/obr/src/main/resources/OSGI-INF/l10n/bundle.properties b/webconsole-plugins/obr/src/main/resources/OSGI-INF/l10n/bundle.properties
new file mode 100644
index 0000000..78812a5
--- /dev/null
+++ b/webconsole-plugins/obr/src/main/resources/OSGI-INF/l10n/bundle.properties
@@ -0,0 +1,49 @@
+#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.

+

+#

+# Web Console strings for reference all strings here are commented.

+# This file may be used to produce a translation of the strings

+#

+# Note that properties files are ISO-8859-1 encoded. To provide translations

+# for languages requiring different character encodings, you may use the

+# native2ascii Maven Plugin from http://mojo.codehaus.org/native2ascii-maven-plugin/

+# to translate the natively encoded files to ISO-8859-1 during bundle build

+#

+# Translations requiring non-ISO-8859-1 encoding are placed in the

+# src/main/native2ascii/OSGI-INF/l10n folder and are converted using said

+# plugin while building the bundle

+#

+

+# OBR Plugin

+obr.pluginTitle=OSGi Repository

+obr.status.ok=The Apache Bundle Repository service is up and running.

+obr.status.no=The Apache Bundle Repository service is not available!

+obr.version.select=Select Version...

+obr.repo.title=Bundle Repositories

+obr.action.add=Add

+obr.action.search=Search

+obr.action.search.description=Enter word contained in bundle presentation \

+ and/or symbolic name or a valid OSGi Filter Expression

+obr.action.deploy=Deploy Selected

+obr.action.deploystart=Deploy and Start Selected

+obr.repo.name=Name

+obr.repo.url=URL

+obr.repo.lastModified=Last Modified

+obr.repo.actions=Actions

+obr.res.title=Available Resources

+obr.res.name=Resource Name

+obr.res.installedVer=Installed Version

+obr.error=Error Filtering Resources

diff --git a/webconsole-plugins/obr/src/main/resources/res/plugin.css b/webconsole-plugins/obr/src/main/resources/res/plugin.css
new file mode 100644
index 0000000..a6ef84c
--- /dev/null
+++ b/webconsole-plugins/obr/src/main/resources/res/plugin.css
@@ -0,0 +1,6 @@
+label {
+    font-family: Verdana, Arial, sans-serif;
+    font-size: 1em;
+    font-style: normal;
+    font-weight: normal;
+}
\ No newline at end of file
diff --git a/webconsole-plugins/obr/src/main/resources/res/plugin.html b/webconsole-plugins/obr/src/main/resources/res/plugin.html
new file mode 100644
index 0000000..4037a24
--- /dev/null
+++ b/webconsole-plugins/obr/src/main/resources/res/plugin.html
@@ -0,0 +1,102 @@
+<script type="text/javascript" src="${pluginRoot}/res/plugin.js"></script>

+<script type="text/javascript">

+var i18n = {

+	status_ok : '${obr.status.ok}',

+	status_no : '${obr.status.no}',

+	selectVersion : '${obr.version.select}',

+	error : '${obr.error}'

+}

+var obrData = ${__data__};

+</script>

+

+<p class="statline">${obr.status.ok}</p>

+

+<div id="ifStatusOK" class="ui-helper-hidden">

+

+<div class="ui-widget-header ui-corner-top buttonGroup">

+	<span style="float: left; margin-left: 1em">${obr.repo.title}</span>

+	<input style="width: 50%" type="text" id="addRepoUri" />

+	<button id="addRepoBtn">${obr.action.add}</button>

+</div>

+

+<table id="repoTable" class="nicetable">

+	<thead>

+		<tr>

+			<th class="col_Name">${obr.repo.name}</th>

+			<th class="col_URL">${obr.repo.url}</th>

+			<th class="col_lastMod">${obr.repo.lastModified}</th>

+			<th class="col_Actions">${obr.repo.actions}</th>

+		</tr>

+	</thead>

+	<tbody> <!-- template: will be replaced dynamically by JS -->

+		<tr>

+			<td>name</td>

+			<td>url</td>

+			<td>date</td>

+			<td>

+				<ul class="icons ui-widget">

+					<li class="dynhover" title="${refresh}"><span class="ui-icon ui-icon-refresh">&nbsp;</span></li>

+					<li class="dynhover" title="${delete}"><span class="ui-icon ui-icon-trash">&nbsp;</span></li>

+				</ul>

+			</td>

+		</tr>

+	</tbody>

+</table>

+

+<br/>

+

+<div class="ui-widget-header ui-corner-top buttonGroup">

+    <span style="float: left; margin-left: 1em">${obr.res.title}</span>

+    <span>

+        <a href="${pluginRoot}?list=a">A</a>

+        <a href="${pluginRoot}?list=b">B</a>

+        <a href="${pluginRoot}?list=c">C</a>

+        <a href="${pluginRoot}?list=d">D</a>

+        <a href="${pluginRoot}?list=e">E</a>

+        <a href="${pluginRoot}?list=f">F</a>

+        <a href="${pluginRoot}?list=g">G</a>

+        <a href="${pluginRoot}?list=h">H</a>

+        <a href="${pluginRoot}?list=i">I</a>

+        <a href="${pluginRoot}?list=j">J</a>

+        <a href="${pluginRoot}?list=k">K</a>

+        <a href="${pluginRoot}?list=l">L</a>

+        <a href="${pluginRoot}?list=m">M</a>

+        <a href="${pluginRoot}?list=n">N</a>

+        <a href="${pluginRoot}?list=o">O</a>

+        <a href="${pluginRoot}?list=p">P</a>

+        <a href="${pluginRoot}?list=q">Q</a>

+        <a href="${pluginRoot}?list=r">R</a>

+        <a href="${pluginRoot}?list=s">S</a>

+        <a href="${pluginRoot}?list=t">T</a>

+        <a href="${pluginRoot}?list=u">U</a>

+        <a href="${pluginRoot}?list=v">V</a>

+        <a href="${pluginRoot}?list=w">W</a>

+        <a href="${pluginRoot}?list=x">X</a>

+        <a href="${pluginRoot}?list=y">Y</a>

+        <a href="${pluginRoot}?list=z">Z</a>

+        <a href="${pluginRoot}?list=-">?</a>

+    </span>

+    <input type="text" id="searchField" title="${obr.action.search.description}"/>

+    <button id="searchBtn">${obr.action.search}</button>

+</div>

+

+<table id="resTable" class="nicetable">

+    <thead>

+        <tr>

+            <th class="col_ResName">${obr.res.name}</th>

+            <th class="col_VersionInst">${obr.res.installedVer}</th>

+        </tr>

+    </thead>

+    <tbody>

+        <tr><td colspan="2">dummy</td></tr>

+    </tbody>

+</table>

+

+<table id="detailsTable" class="nicetable ui-helper-hidden">

+    <tbody id="detailsTableBody">

+    </tbody>

+</table>

+

+<br/>

+

+</div> <!-- ifStatusOK -->
\ No newline at end of file
diff --git a/webconsole-plugins/obr/src/main/resources/res/plugin.js b/webconsole-plugins/obr/src/main/resources/res/plugin.js
new file mode 100644
index 0000000..159ccfa
--- /dev/null
+++ b/webconsole-plugins/obr/src/main/resources/res/plugin.js
@@ -0,0 +1,510 @@
+/*

+ * 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.

+ */

+ 

+var repoTable = false;

+var repoTableTemplate = false;

+var addRepoUri = false;

+var resTable = false;

+var searchField = false;

+var ifStatusOK = false;

+

+//This prototype is provided by the Mozilla foundation and

+//is distributed under the MIT license.

+//http://www.ibiblio.org/pub/Linux/LICENSES/mit.license

+if (!Array.prototype.map)

+{

+  Array.prototype.map = function(fun /*, thisp*/)

+  {

+    var len = this.length;

+    if (typeof fun != "function")

+      throw new TypeError();

+

+    var res = new Array(len);

+    var thisp = arguments[1];

+    for (var i = 0; i < len; i++)

+    {

+      if (i in this)

+        res[i] = fun.call(thisp, this[i], i, this);

+    }

+

+    return res;

+  };

+}

+

+var uid = 0;

+function guid() {

+   uid = uid + 1;

+   return (0x10000 + uid).toString(16).substring(1) + (((1+Math.random())*0x10000)|0).toString(16).substring(1);

+}

+

+/* displays a date in the user's local timezone */

+function localTm(time) {

+    return (time ? new Date(time) : new Date()).toLocaleString();

+}

+

+function doRepoAction(action, url) {

+    if ( !url ) {

+        Xalert('Invalid URI: ' + url, 'Error');

+    } else {

+        $.post(pluginRoot, {

+            'action' : action,

+            'url'    : url

+        }, renderData, 'json');

+    }

+}

+

+function showDetails( symbolicname, version ) {

+    window.location.href = pluginRoot + '/' + symbolicname + '/' + version;

+}

+

+function showVersions( symbolicname ) {

+    var _id = symbolicname.replace(/\./g, '_');

+    $("#block" + _id).append("<div id='pluginInlineVersions" + _id + "' style='margin-left: 4em'><ul/></div>");

+    $("#img" + _id).each(function() {

+        $(this).

+            removeClass('ui-icon-triangle-1-e').//right

+            addClass('ui-icon-triangle-1-s');//down

+    });

+    $("#entry" + _id).each(function() {

+        $(this).

+            unbind('click').

+            click(function() {hideVersions(symbolicname)}).

+            attr("title", "Hide Versions");

+    });

+    var versions = [];

+    for (var i in obrData.resources ) {

+        if (obrData.resources[i].symbolicname == symbolicname) {

+            versions.push(obrData.resources[i].version);

+        }

+    }

+    versions.sort();

+    for (var i in versions) {

+        var txt = "<li><a href='javascript: showDetails(\"" + symbolicname + "\",\"" + versions[i] + "\")'>" + versions[i] + "</a></li>";

+        $("#pluginInlineVersions" + _id + " > ul").append(txt);

+    }

+}

+

+function hideVersions( symbolicname ) {

+    var _id = symbolicname.replace(/\./g, '_');

+    $("#img" + _id).each(function() {

+        $(this).

+            removeClass('ui-icon-triangle-1-s').//down

+            addClass('ui-icon-triangle-1-e');//right

+    });

+    $("#pluginInlineVersions" + _id).each(function() {

+        $(this).

+            remove();

+    });

+    $("#entry" + _id).each(function() {

+        $(this).

+            unbind('click').

+            click(function() {showVersions(symbolicname)}).

+            attr("title", "Show Versions");

+    });

+}

+

+function renderResource(res) {

+    // proceed with resource

+    var _id = res.symbolicname.replace(/\./g, '_');

+    var _tr = resTable.find('#row' + _id);

+

+    if (_tr.length == 0) { // not created yet, create it

+        var blockElement = createElement('span', '', {

+            id: 'block' + _id

+        });

+        var titleElement = createElement('span', '', {

+            id: 'entry' + _id,

+            title: "Show Versions"

+        });

+        var inputElement = createElement('span', 'ui-icon ui-icon-triangle-1-e', {

+            id: 'img' + _id,

+            style: {display: "inline-block"}

+        });

+        blockElement.appendChild(titleElement);

+        titleElement.appendChild(inputElement);

+        titleElement.appendChild(text(" "));

+        if (res.presentationname) {

+	    titleElement.appendChild(text(res.presentationname));

+	    titleElement.appendChild(text(" ("));

+	    titleElement.appendChild(text(res.symbolicname));

+	    titleElement.appendChild(text(")"));

+	} else {

+	    titleElement.appendChild(text(res.symbolicname));

+	}

+        $(titleElement).click(function() {showVersions(res.symbolicname)});

+

+        _tr = tr( null, { 'id' : 'row' + _id } , [

+            td( null, null, [ blockElement ] ),

+            td( null, null, [ text(res.installed ? res.version : '') ] )

+        ]);

+        resTable.append( _tr );

+    }

+}

+

+function getCapabilitiesByName(res, name) {

+    var caps = [];

+    for (var v in res.capabilities) {

+        if (res.capabilities[v].name == name) {

+            caps.push(res.capabilities[v]);

+        }

+    }

+    return caps;

+}

+

+function getRequirementsByName(res, name) {

+    var caps = [];

+    for (var v in res.requirements) {

+        if (res.requirements[v].name == name) {

+            caps.push(res.requirements[v]);

+        }

+    }

+    return caps;

+}

+

+function createDetailedTable(enclosing, name, headers, rows, callback) {

+    if (rows && rows.length > 0) {

+        var uuid = guid();

+        var title = createElement('span', null, null, [

+                                createElement('span', 'ui-icon ui-icon-triangle-1-e', { id: "img"+uuid, style: {display: "inline-block"} }),

+                                text(" "),

+                                text(name)

+                             ]);

+        enclosing.append(tr(null, null, [

+            td(null, null, [ title ]),

+            td(null, null, [ createElement('table', 'nicetable ui-widget ui-helper-hidden', { id: "alt1"+uuid }, [

+                                createElement('thead', null, null, [

+                                    tr(null, null, headers.map(function(x) {

+                                        return th('ui-widget-header', null, [text(x)]);

+                                    }))

+                                ]),

+                                createElement('tbody', null, null,

+                                    rows.map(function(x) {

+                                        var values = callback(x);

+                                        var tds = values.map(function(x) {

+                                            return td(null, null, [x]);

+                                        });

+                                        return tr(null, null, tds);

+                                    })

+                                )

+                             ]),

+                             createElement('span', null, { id: "alt2"+uuid }, [

+                                text(rows.length)

+                             ])

+            ])

+        ]));

+        $(title).

+                unbind('click').

+                click(function(event) {

+                    event.preventDefault();

+                    $("#img"+uuid).toggleClass('ui-icon-triangle-1-s').//down

+                                   toggleClass('ui-icon-triangle-1-e');//right

+                    $("#alt1"+uuid).toggle();

+                    $("#alt2"+uuid).toggle();

+                });

+    }

+}

+

+function trim(stringToTrim) {

+    return stringToTrim.replace(/^\s+|\s+$/g,"");

+}

+

+function parseSimpleFilter(filter) {

+    filter = filter.substring(1, filter.length-1);

+    var start = 0;

+    var pos = 0;

+    var c = filter.charAt(pos);

+    while (c != '~' && c != '<' && c != '>' && c != '=' && c != '(' && c != ')') {

+        if (c == '<' && filterChars[pos+1] == '*') {

+            break;

+        }

+        if (c == '*' && filterChars[pos+1] == '>') {

+            break;

+        }

+        pos++;

+        c = filter.charAt(pos);

+    }

+    if (pos == start) {

+        throw ("Missing attr: " + filter.substring(pos));

+    }

+

+    var attr = trim(filter.substring(start, pos));

+    var oper = filter.substring(pos, pos+2);

+    var value;

+    if (oper == '*>' || oper == '~=' || oper == '>=' || oper == '<=' || oper == '<*') {

+        value = trim(filter.substring(pos+2));

+        if (value == '') {

+            throw ("Missing value: " + filter.substring(pos));

+        }

+

+        return { operator: oper, operands: [ attr, value ]};

+    } else {

+        if (c != '=') {

+            throw ("Invalid operator: " + filter.substring(pos));

+        }

+        oper = '=';

+        value = filter.substring(pos+1);

+        if (value == '*' ) {

+            return { operator: '=*', operands: [ attr ]};

+        }

+        return { operator: '=', operands: [ attr, value ]};

+    }

+}

+

+function parseFilter(filter) {

+    if (filter.charAt(0) != "(" || filter.charAt(filter.length-1) != ")") {

+        throw "Wrong parenthesis: " + filter;

+    }

+    if (filter.charAt(1) == "!") {

+        return { operator: filter.charAt(1), operands: [ parseFilter(filter.substring(2, filter.length-1)) ] };

+    }

+    if (filter.charAt(1) == "|" || filter.charAt(1) == "&") {

+        var inner = filter.substring(2, filter.length-1);

+        var flts = inner.match(/\([^\(\)]*(\([^\(\)]*(\([^\(\)]*(\([^\(\)]*\))*[^\(\)]*\))*[^\(\)]*\))*[^\(\)]*\)/g);

+        return { operator: filter.charAt(1), operands: flts.map(function(x) { return parseFilter(x); }) };

+    }

+    return parseSimpleFilter(filter);

+}

+

+function simplify(filter) {

+    if (filter.operator == '&' || filter.operator == '|') {

+        filter.operands = filter.operands.map(function(x) { return simplify(x); });

+    } else if (filter.operator == '!') {

+        if (filter.operands[0].operator == '<=') {

+            filter.operator = '>';

+            filter.operands = filter.operands[0].operands;

+        } else if (filter.operands[0].operator == '>=') {

+            filter.operator = '<';

+            filter.operands = filter.operands[0].operands;

+        }

+    }

+    return filter;

+}

+

+function addRow(tbody, key, value) {

+    if (value) {

+        tbody.append( tr(null, null, [

+            td(null, null, [ text(key) ]),

+            td(null, null, [ text(value) ])

+        ]));

+    }

+}

+

+function renderDetailedResource(res) {

+    var tbody = $('#detailsTableBody');

+

+    tbody.append( tr(null, null, [

+        th('ui-widget-header', null, [

+            text("Resource")

+        ]),

+        th('ui-widget-header', null, [

+            createElement('form', 'button-group', { method: "post"}, [

+                createElement('input', null, { type: "hidden", name: "bundle", value: res.id}),

+                createElement('input', 'ui-state-default ui-corner-all', { type: "submit", name: "deploy", value: "Deploy" }, [ text("dummy")]),

+                createElement('input', 'ui-state-default ui-corner-all', { type: "submit", name: "deploystart", value: "Deploy and Start" }, [ text("dummy")]),

+                text(" "),

+                createElement('input', 'ui-state-default ui-corner-all', { id: "optional", type: "checkbox", name: "optional" }),

+                text(" "),

+                createElement('label', 'ui-widget', { 'for': "optional" }, [ text("deploy optional dependencies") ])

+            ])

+        ])

+    ]));

+

+    addRow(tbody, "Name", res.presentationname);

+    addRow(tbody, "Description", res.description);

+    addRow(tbody, "Symbolic name", res.symbolicname);

+    addRow(tbody, "Version", res.version);

+    addRow(tbody, "URI", res.uri);

+    addRow(tbody, "Documentation", res.documentation);

+    addRow(tbody, "Javadoc", res.javadoc);

+    addRow(tbody, "Source", res.source);

+    addRow(tbody, "License", res.license);

+    addRow(tbody, "Copyright", res.copyright);

+    addRow(tbody, "Size", res.size);

+

+    // Exported packages

+    createDetailedTable(tbody, "Exported packages", ["Package", "Version"],

+                        getCapabilitiesByName(res, "package").sort(function(a,b) {

+                            var pa = a.properties['package'], pb = b.properties['package']; return pa == pb ? 0 : pa < pb ? -1 : +1;

+                        }),

+                        function(p) {

+                            return [ text(p.properties['package']), text(p.properties['version']) ];

+                        });

+    // Exported services

+    createDetailedTable(tbody, "Exported services", ["Service"], getCapabilitiesByName(res, "service"), function(p) {

+                            return [ text(p.properties['service']) ];

+                        });

+    // Imported packages

+    createDetailedTable(tbody, "Imported packages", ["Package", "Version", "Optional"], getRequirementsByName(res, "package"), function(p) {

+                            var f = parseFilter(p.filter);

+                            simplify(f);

+                            var n, vmin = "[0.0.0", vmax = "infinity)";

+                            if (f.operator == '&') {

+                                for (var i in f.operands) {

+                                    var fi = f.operands[i];

+                                    if (fi.operands[0] == 'package' && fi.operator == '=') {

+                                        n = fi.operands[1];

+                                    }

+                                    if (fi.operands[0] == 'version') {

+                                        if (fi.operator == '>=') {

+                                            vmin = '[' + fi.operands[1];

+                                        }

+                                        if (fi.operator == '>') {

+                                            vmin = '(' + fi.operands[1];

+                                        }

+                                        if (fi.operator == '<=') {

+                                            vmax = fi.operands[1] + "]";

+                                        }

+                                        if (fi.operator == '<') {

+                                            vmax = fi.operands[1] + ")";

+                                        }

+                                    }

+                                }

+                            }

+                            return [ text(n ? n : p.filter), text(vmin + ", " + vmax), text(p.optional) ];

+                        });

+    // Imported bundles

+    createDetailedTable(tbody, "Imported bundles", ["Bundle", "Version", "Optional"], getRequirementsByName(res, "bundle"), function(p) {

+                            return [ text(p.filter), text(""), text(p.optional) ];

+                        });

+    // Imported services

+    createDetailedTable(tbody, "Imported services", ["Service", "Optional"], getRequirementsByName(res, "service"), function(p) {

+                            return [ text(p.filter), text(p.optional) ];

+                        });

+    // Required dependencies

+    createDetailedTable(tbody, "Dependencies", ["Name", "Version"], res.required, function(p) {

+                            var a = createElement('a', null, { href: (pluginRoot + '/' + p.symbolicname + '/' + p.version) });

+                            a.appendChild(text(p.presentationname ? p.presentationname : p.symbolicname));

+                            return [ a, text(p.version) ];

+                        });

+    // Optional dependencies

+    createDetailedTable(tbody, "Optional Dependencies", ["Name", "Version"], res.optional, function(p) {

+                            var a = createElement('a', null, { href: (pluginRoot + '/' + p.symbolicname + '/' + p.version) });

+                            a.appendChild(text(p.presentationname ? p.presentationname : p.symbolicname));

+                            return [ a, text(p.version) ];

+                        });

+    // Unsatisfied requirements

+    createDetailedTable(tbody, "Unsatisfied Requirements", ["Requirement", "Optional"], res.unsatisfied, function(p) {

+                            return [ text(p.filter), text(p.optional) ];

+                        });

+

+//    $('#detailsTableBody').append( tr(null, null, [ th('ui-widget-header', { colspan: 2 }, [ text("Resource") ]) ]) );

+//    $('#detailsTableBody').append( tbody );

+}

+

+function renderRepository(repo) {

+    var _tr = repoTableTemplate.clone();

+    _tr.find('td:eq(0)').text( repo.name );

+    _tr.find('td:eq(1)').text( repo.url );

+    _tr.find('td:eq(2)').text( localTm(repo.lastModified) );

+    _tr.find('li:eq(0)').click(function() {

+        doRepoAction('refresh', repo.url);

+    });

+    _tr.find('li:eq(1)').click(function() {

+        doRepoAction('delete', repo.url);

+    });

+    repoTable.append(_tr);

+}

+

+function renderData() {

+    repoTable.empty();

+    resTable.empty();

+    if ( obrData.status ) {

+        $('.statline').html(i18n.status_ok);

+        ifStatusOK.removeClass('ui-helper-hidden');

+        for (var i in obrData.repositories ) {

+            renderRepository( obrData.repositories[i] );

+        }

+        if (obrData.details) {

+            $('#resTable').addClass('ui-helper-hidden');

+            $('#detailsTable').removeClass('ui-helper-hidden');

+            for (var i in obrData.resources ) {

+                renderDetailedResource( obrData.resources[i] );

+            }

+        } else if (obrData.resources) {

+            for (var i in obrData.resources ) {

+                renderResource( obrData.resources[i] );

+            }

+        } else if (obrData.error) {

+            _tr = tr( "ui-state-error", null , [

+                      td( "ui-state-error-text", { 'colspan': '2' }, 

+                	  [ text(i18n.error + ": " + obrData.error) ] )

+                  ]);

+            resTable.append( _tr );

+        }

+    } else {

+        $('.statline').html(i18n.status_no);

+        ifStatusOK.addClass('ui-helper-hidden');

+    }

+}

+

+

+$.extend({

+  getUrlVars: function(){

+    var vars = [], hash;

+    var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&');

+    for(var i = 0; i < hashes.length; i++)

+    {

+      var j = hashes[i].indexOf('=');

+      if (j > 0) {

+        var k = hashes[i].slice(0, j);

+        var v = hashes[i].slice(j + 1);

+        vars.push(k);

+        vars[k] = v;

+      } else {

+        vars.push(hashes[i]);

+        vars[hashes[i]] = true;

+      }

+    }

+    return vars;

+  },

+  getUrlVar: function(name){

+    return $.getUrlVars()[name];

+  }

+});

+

+$(document).ready( function() {

+    repoTable = $('#repoTable tbody');

+    repoTableTemplate = repoTable.find('tr').clone();

+    addRepoUri = $('#addRepoUri');

+    resTable = $('#resTable tbody').empty();

+    searchField = $('#searchField');

+    ifStatusOK = $('#ifStatusOK');

+    

+    var query = $.getUrlVar('query');

+    if (query) {

+        searchField.val(decodeURIComponent(query));

+    }

+

+    $('#addRepoBtn').click(function(event) {

+        event.preventDefault();

+        doRepoAction('add', addRepoUri.val());

+    });

+    $('#searchBtn').click(function(event) {

+        event.preventDefault();

+        window.location.href = pluginRoot + '?query=' + encodeURIComponent(searchField.val());

+    });

+    searchField.keypress(function(event) {

+        if (event.keyCode == 13) {

+            event.preventDefault();

+            $('#searchBtn').click();

+        }

+    });

+

+    renderData();

+    initStaticWidgets();

+});

+