/* * #%L * Wisdom-Framework * %% * Copyright (C) 2013 - 2014 Wisdom Framework * %% * 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. * #L% */ package org.wisdom.monitor.extensions.osgi; import com.google.common.collect.ImmutableMap; import org.apache.felix.ipojo.annotations.Context; import org.apache.felix.ipojo.annotations.Invalidate; import org.apache.felix.ipojo.annotations.Validate; import org.osgi.framework.*; import org.wisdom.api.DefaultController; import org.wisdom.api.annotations.*; import org.wisdom.api.http.FileItem; import org.wisdom.api.http.HttpMethod; import org.wisdom.api.http.Result; import org.wisdom.api.security.Authenticated; import org.wisdom.api.templates.Template; import org.wisdom.monitor.service.MonitorExtension; import java.util.Dictionary; import java.util.List; import java.util.concurrent.Callable; /** * Provides the OSGi bundle admin view. */ @Controller @Path("/monitor/osgi/bundle") @Authenticated("Monitor-Authenticator") public class BundleMonitorExtension extends DefaultController implements MonitorExtension { /** * The template. */ @View("monitor/bundles") Template bundles; /** * The bundle context. */ @Context BundleContext context; /** * Just a simple bundle event counter. */ private BundleEventCounter counter = new BundleEventCounter(); /** * Starts the counter. */ @Validate public void start() { counter.start(); } /** * Stops the counter. */ @Invalidate public void stop() { counter.stop(); } /** * @return the HTML page. */ @Route(method = HttpMethod.GET, uri = "") public Result bundle() { return ok(render(bundles)); } /** * @return the list of bundles and event counts. */ @Route(method = HttpMethod.GET, uri = ".json") public Result bundles() { final List<BundleModel> list = BundleModel.bundles(context); return ok(ImmutableMap.of( "bundles", list, "events", counter.get(), "active", getActiveBundleCount(list), "installed", getInstalledBundleCount(list))).json(); } private int getInstalledBundleCount(List<BundleModel> bundles) { int count = 0; for (BundleModel bundle : bundles) { if (BundleStates.INSTALLED.equals(bundle.getState())) { count++; } } return count; } private int getActiveBundleCount(List<BundleModel> bundles) { int count = 0; for (BundleModel bundle : bundles) { if (BundleStates.ACTIVE.equals(bundle.getState())) { count++; } } return count; } /** * Toggles the states of the bundle. If the bundle is active, the bundle is stopped. If the bundle is resolved or * installed, the bundle is started. * * @param id the bundle's id * @return OK if everything is fine, BAD_REQUEST if the bundle cannot be started or stopped correctly, * NOT_FOUND if there are no bundles with the given id. */ @Route(method = HttpMethod.GET, uri = "/{id}") public Result toggleBundle(@Parameter("id") long id) { Bundle bundle = context.getBundle(id); if (bundle == null) { return notFound("Bundle " + id + " not found"); } else { if (! isFragment(bundle)) { if (bundle.getState() == Bundle.ACTIVE) { try { bundle.stop(); } catch (BundleException e) { logger().error("Cannot stop bundle {}", bundle.getSymbolicName(), e); return badRequest(e); } } else if (bundle.getState() == Bundle.INSTALLED || bundle.getState() == Bundle.RESOLVED) { try { bundle.start(); } catch (BundleException e) { logger().error("Cannot start bundle {}", bundle.getSymbolicName(), e); return badRequest(e); } } } else { if (bundle.getState() == Bundle.RESOLVED) { try { bundle.stop(); } catch (BundleException e) { logger().error("Cannot stop bundle {}", bundle.getSymbolicName(), e); return badRequest(e); } } } } return ok(); } /** * Updates the given bundle. The bundle is updated from the installation url. * * @param id the bundle's id * @return OK if the bundle is updated, BAD_REQUEST if an error occurs when the bundle is updated, * NOT_FOUND if there are no bundles with the given id. */ @Route(method = HttpMethod.POST, uri = "/{id}") public Result updateBundle(@Parameter("id") long id) { final Bundle bundle = context.getBundle(id); if (bundle == null) { return notFound("Bundle " + id + " not found"); } else { return async(new Callable<Result>() { @Override public Result call() throws Exception { try { logger().info("Updating bundle {} from {}", bundle.getSymbolicName(), bundle.getLocation()); bundle.update(); return ok(); } catch (BundleException e) { logger().error("Cannot update bundle {}", bundle.getSymbolicName(), e); return badRequest(e); } } }); } } /** * Installs a new bundle. * * @param bundle the bundle file * @param startIfNeeded whether or not the bundle need to be started * @return the bundle page, with a flash message. */ @Route(method = HttpMethod.POST, uri = "") public Result installBundle(@FormParameter("bundle") final FileItem bundle, @FormParameter("start") @DefaultValue("false") final boolean startIfNeeded) { if (bundle != null) { return async(new Callable<Result>() { @Override public Result call() throws Exception { Bundle b; try { b = context.installBundle("file/temp/" + bundle.name(), bundle.stream()); logger().info("Bundle {} installed", b.getSymbolicName()); } catch (BundleException e) { flash("error", "Cannot install bundle '" + bundle.name() + "' : " + e.getMessage()); return bundle(); } if (startIfNeeded && ! isFragment(b)) { try { b.start(); flash("success", "Bundle '" + b.getSymbolicName() + "' installed and started"); return bundle(); } catch (BundleException e) { flash("error", "Bundle '" + b.getSymbolicName() + "' installed but " + "failed to start: " + e.getMessage()); return bundle(); } } else { flash("success", "Bundle '" + b.getSymbolicName() + "' installed."); return bundle(); } } }); } else { logger().error("No bundle to install"); flash("error", "Unable to install the bundle - no uploaded file"); return bundle(); } } /** * Uninstalls the given bundle. * * @param id the bundle's id * @return OK if the bundle is updated, BAD_REQUEST if an error occurs when the bundle is uninstalled, * NOT_FOUND if there are no bundles with the given id. */ @Route(method = HttpMethod.DELETE, uri = "/{id}") public Result uninstallBundle(@Parameter("id") long id) { final Bundle bundle = context.getBundle(id); if (bundle == null) { return notFound("Bundle " + id + " not found"); } else { return async(new Callable<Result>() { @Override public Result call() throws Exception { try { logger().info("Uninstalling bundle {}", bundle.getSymbolicName()); bundle.uninstall(); return ok(); } catch (BundleException e) { logger().error("Cannot uninstall bundle {}", bundle.getSymbolicName(), e); return badRequest(e); } } }); } } /** * @return "Bundles". */ @Override public String label() { return "Bundles"; } /** * @return the bundle page's url. */ @Override public String url() { return "/monitor/osgi/bundle"; } /** * @return "OSGi". */ @Override public String category() { return "osgi"; } /** * A simple class counting events. * No synchronization involved, as we can be off without be in troubles. */ private class BundleEventCounter implements BundleListener { /** * Current count. */ int counter = 0; /** * Starts counting. */ public void start() { context.addBundleListener(this); } /** * Resets the counter. */ public void reset() { counter = 0; } /** * Stops counting. */ public void stop() { context.removeBundleListener(this); } /** * Receives notification that a bundle has had a lifecycle change. * * @param event The <code>BundleEvent</code>. */ public void bundleChanged(BundleEvent event) { counter++; } /** * Gets the current counter value. * * @return the current counter value. */ public int get() { return counter; } } /** * Checks whether or not the given bundle is a fragment * @param bundle the bundle * @return {@code true} if the bundle is a fragment, {@code false} otherwise. */ public static boolean isFragment(Bundle bundle) { Dictionary<String, String> headers = bundle.getHeaders(); return headers.get(Constants.FRAGMENT_HOST) != null; } }