content: /**
   *  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.aries.jmx.framework;
  
  import static org.apache.aries.jmx.util.FrameworkUtils.getBundleDependencies;
  import static org.apache.aries.jmx.util.FrameworkUtils.getBundleExportedPackages;
  import static org.apache.aries.jmx.util.FrameworkUtils.getBundleImportedPackages;
  import static org.apache.aries.jmx.util.FrameworkUtils.getBundleState;
  import static org.apache.aries.jmx.util.FrameworkUtils.getDependentBundles;
  import static org.apache.aries.jmx.util.FrameworkUtils.getFragmentIds;
  import static org.apache.aries.jmx.util.FrameworkUtils.getHostIds;
  import static org.apache.aries.jmx.util.FrameworkUtils.getRegisteredServiceIds;
  import static org.apache.aries.jmx.util.FrameworkUtils.getServicesInUseByBundle;
  import static org.apache.aries.jmx.util.FrameworkUtils.isBundlePendingRemoval;
  import static org.apache.aries.jmx.util.FrameworkUtils.isBundleRequiredByOthers;
  import static org.apache.aries.jmx.util.FrameworkUtils.resolveBundle;
  
  import java.io.IOException;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Collection;
  import java.util.Collections;
  import java.util.Dictionary;
  import java.util.Enumeration;
  import java.util.List;
  import java.util.concurrent.ExecutorService;
  import java.util.concurrent.Executors;
  import java.util.concurrent.RejectedExecutionException;
  import java.util.concurrent.atomic.AtomicInteger;
  import java.util.concurrent.locks.Lock;
  import java.util.concurrent.locks.ReentrantLock;
  import javax.management.AttributeChangeNotification;
  import javax.management.MBeanNotificationInfo;
  import javax.management.MBeanRegistration;
  import javax.management.MBeanServer;
  import javax.management.Notification;
  import javax.management.NotificationBroadcasterSupport;
  import javax.management.ObjectName;
  import javax.management.openmbean.CompositeData;
  import javax.management.openmbean.TabularData;
  import javax.management.openmbean.TabularDataSupport;
  
  import org.apache.aries.jmx.JMXThreadFactory;
  import org.apache.aries.jmx.Logger;
  import org.apache.aries.jmx.codec.BundleData;
  import org.apache.aries.jmx.codec.BundleData.Header;
  import org.apache.aries.jmx.codec.BundleEventData;
  import org.osgi.framework.Bundle;
  import org.osgi.framework.BundleContext;
  import org.osgi.framework.BundleEvent;
  import org.osgi.framework.BundleListener;
  import org.osgi.jmx.framework.BundleStateMBean;
  import org.osgi.service.log.LogService;
  import org.osgi.service.packageadmin.PackageAdmin;
  import org.osgi.service.startlevel.StartLevel;
  
  /**
   * Implementation of <code>BundleStateMBean</code> which emits JMX <code>Notification</code> on <code>Bundle</code>
   * state changes.
   *
   * @version $Rev$ $Date$
   */
  public class BundleState extends NotificationBroadcasterSupport implements BundleStateMBean, MBeanRegistration {
  
      protected Logger logger;
      protected BundleContext bundleContext;
      protected PackageAdmin packageAdmin;
      protected StartLevel startLevel;
      protected StateConfig stateConfig;
  
      protected ExecutorService eventDispatcher;
      protected BundleListener bundleListener;
      private AtomicInteger notificationSequenceNumber = new AtomicInteger(1);
      private AtomicInteger attributeChangeNotificationSequenceNumber = new AtomicInteger(1);
      private Lock lock = new ReentrantLock();
      private AtomicInteger registrations = new AtomicInteger(0);
  
      // notification type description
      public static String BUNDLE_EVENT = "org.osgi.bundle.event";
  
      public BundleState(BundleContext bundleContext, PackageAdmin packageAdmin, StartLevel startLevel, StateConfig stateConfig, Logger logger) {
          this.bundleContext = bundleContext;
          this.packageAdmin = packageAdmin;
          this.startLevel = startLevel;
          this.stateConfig = stateConfig;
          this.logger = logger;
      }
  
      /**
       * @see org.osgi.jmx.framework.BundleStateMBean#getExportedPackages(long)
       */
      public String[] getExportedPackages(long bundleId) throws IOException, IllegalArgumentException {
          Bundle bundle = resolveBundle(bundleContext, bundleId);
          return getBundleExportedPackages(bundle, packageAdmin);
      }
  
      /**
       * @see org.osgi.jmx.framework.BundleStateMBean#getFragments(long)
       */
      public long[] getFragments(long bundleId) throws IOException, IllegalArgumentException {
          Bundle bundle = resolveBundle(bundleContext, bundleId);
          return getFragmentIds(bundle, packageAdmin);
      }
  
      /**
       * @see org.osgi.jmx.framework.BundleStateMBean#getHeaders(long)
       */
      public TabularData getHeaders(long bundleId) throws IOException, IllegalArgumentException {
          Bundle bundle = resolveBundle(bundleContext, bundleId);
          Dictionary<String, String> bundleHeaders = bundle.getHeaders();
          return getHeaders(bundleHeaders);
      }
  
      /**
       * @see org.osgi.jmx.framework.BundleStateMBean#getHeaders(long, java.lang.String)
       */
      public TabularData getHeaders(long bundleId, String locale) throws IOException, IllegalArgumentException {
          Bundle bundle = resolveBundle(bundleContext, bundleId);
          Dictionary<String, String> bundleHeaders = bundle.getHeaders(locale);
          return getHeaders(bundleHeaders);
      }
  
      private TabularData getHeaders(Dictionary<String, String> bundleHeaders) {
          List<Header> headers = new ArrayList<Header>();
          Enumeration<String> keys = bundleHeaders.keys();
          while (keys.hasMoreElements()) {
              String key = keys.nextElement();
              headers.add(new Header(key, bundleHeaders.get(key)));
          }
          TabularData headerTable = new TabularDataSupport(HEADERS_TYPE);
          for (Header header : headers) {
              headerTable.put(header.toCompositeData());
          }
          return headerTable;
      }
  
      /**
       * @see org.osgi.jmx.framework.BundleStateMBean#getHeader(long, java.lang.String)
       */
      public String getHeader(long bundleId, String key) throws IOException {
          Bundle bundle = resolveBundle(bundleContext, bundleId);
          return bundle.getHeaders().get(key);
      }
  
      /**
       * @see org.osgi.jmx.framework.BundleStateMBean#getHeader(long, java.lang.String, java.lang.String)
       */
      public String getHeader(long bundleId, String key, String locale) throws IOException {
          Bundle bundle = resolveBundle(bundleContext, bundleId);
          return bundle.getHeaders(locale).get(key);
      }
  
      /**
       * @see org.osgi.jmx.framework.BundleStateMBean#getHosts(long)
       */
      public long[] getHosts(long fragmentId) throws IOException, IllegalArgumentException {
          Bundle fragment = resolveBundle(bundleContext, fragmentId);
          return getHostIds(fragment, packageAdmin);
      }
  
      /**
       * @see org.osgi.jmx.framework.BundleStateMBean#getImportedPackages(long)
       */
      public String[] getImportedPackages(long bundleId) throws IOException, IllegalArgumentException {
          Bundle bundle = resolveBundle(bundleContext, bundleId);
          return getBundleImportedPackages(bundleContext, bundle, packageAdmin);
      }
  
      /**
       * @see org.osgi.jmx.framework.BundleStateMBean#getLastModified(long)
       */
      public long getLastModified(long bundleId) throws IOException, IllegalArgumentException {
          Bundle bundle = resolveBundle(bundleContext, bundleId);
          return bundle.getLastModified();
      }
  
      /**
       * @see org.osgi.jmx.framework.BundleStateMBean#getLocation(long)
       */
      public String getLocation(long bundleId) throws IOException, IllegalArgumentException {
          Bundle bundle = resolveBundle(bundleContext, bundleId);
          return bundle.getLocation();
      }
  
      /**
       * @see org.osgi.jmx.framework.BundleStateMBean#getRegisteredServices(long)
       */
      public long[] getRegisteredServices(long bundleId) throws IOException, IllegalArgumentException {
          Bundle bundle = resolveBundle(bundleContext, bundleId);
          return getRegisteredServiceIds(bundle);
      }
  
      /**
       * @see org.osgi.jmx.framework.BundleStateMBean#getRequiredBundles(long)
       */
      public long[] getRequiredBundles(long bundleIdentifier) throws IOException, IllegalArgumentException {
          Bundle bundle = resolveBundle(bundleContext, bundleIdentifier);
          return getBundleDependencies(bundleContext, bundle, packageAdmin);
      }
  
      /**
       * @see org.osgi.jmx.framework.BundleStateMBean#getRequiringBundles(long)
       */
      public long[] getRequiringBundles(long bundleIdentifier) throws IOException, IllegalArgumentException {
          Bundle bundle = resolveBundle(bundleContext, bundleIdentifier);
          return getDependentBundles(bundle, packageAdmin);
      }
  
      /**
       * @see org.osgi.jmx.framework.BundleStateMBean#getServicesInUse(long)
       */
      public long[] getServicesInUse(long bundleIdentifier) throws IOException, IllegalArgumentException {
          Bundle bundle = resolveBundle(bundleContext, bundleIdentifier);
          return getServicesInUseByBundle(bundle);
      }
  
      /**
       * @see org.osgi.jmx.framework.BundleStateMBean#getStartLevel(long)
       */
      public int getStartLevel(long bundleId) throws IOException, IllegalArgumentException {
          Bundle bundle = resolveBundle(bundleContext, bundleId);
          return startLevel.getBundleStartLevel(bundle);
      }
  
      /**
       * @see org.osgi.jmx.framework.BundleStateMBean#getState(long)
       */
      public String getState(long bundleId) throws IOException, IllegalArgumentException {
          Bundle bundle = resolveBundle(bundleContext, bundleId);
          return getBundleState(bundle);
      }
  
      /**
       * @see org.osgi.jmx.framework.BundleStateMBean#getSymbolicName(long)
       */
      public String getSymbolicName(long bundleId) throws IOException, IllegalArgumentException {
          Bundle bundle = resolveBundle(bundleContext, bundleId);
          return bundle.getSymbolicName();
      }
  
      /**
       * @see org.osgi.jmx.framework.BundleStateMBean#getVersion(long)
       */
      public String getVersion(long bundleId) throws IOException, IllegalArgumentException {
          Bundle bundle = resolveBundle(bundleContext, bundleId);
          return bundle.getVersion().toString();
      }
  
      /**
       * @see org.osgi.jmx.framework.BundleStateMBean#isFragment(long)
       */
      public boolean isFragment(long bundleId) throws IOException, IllegalArgumentException {
          Bundle bundle = resolveBundle(bundleContext, bundleId);
          return (PackageAdmin.BUNDLE_TYPE_FRAGMENT == packageAdmin.getBundleType(bundle));
      }
  
      /**
       * @see org.osgi.jmx.framework.BundleStateMBean#isActivationPolicyUsed(long)
       */
      public boolean isActivationPolicyUsed(long bundleId) throws IOException {
          Bundle bundle = resolveBundle(bundleContext, bundleId);
          return startLevel.isBundleActivationPolicyUsed(bundle);
      }
  
      /**
       * @see org.osgi.jmx.framework.BundleStateMBean#isPersistentlyStarted(long)
       */
      public boolean isPersistentlyStarted(long bundleId) throws IOException, IllegalArgumentException {
          Bundle bundle = resolveBundle(bundleContext, bundleId);
          return startLevel.isBundlePersistentlyStarted(bundle);
      }
  
      /**
       * @see org.osgi.jmx.framework.BundleStateMBean#isRemovalPending(long)
       */
      public boolean isRemovalPending(long bundleId) throws IOException, IllegalArgumentException {
          Bundle bundle = resolveBundle(bundleContext, bundleId);
          return isBundlePendingRemoval(bundle, packageAdmin);
      }
  
      /**
       * @see org.osgi.jmx.framework.BundleStateMBean#isRequired(long)
       */
      public boolean isRequired(long bundleId) throws IOException, IllegalArgumentException {
          Bundle bundle = resolveBundle(bundleContext, bundleId);
          return isBundleRequiredByOthers(bundle, packageAdmin);
      }
  
      public CompositeData getBundle(long id) throws IOException {
          Bundle bundle = bundleContext.getBundle(id);
          if (bundle == null)
              return null;
  
          BundleData data = new BundleData(bundleContext, bundle, packageAdmin, startLevel);
          return data.toCompositeData();
      }
  
      public long[] getBundleIds() throws IOException {
          Bundle[] bundles = bundleContext.getBundles();
          long[] ids = new long[bundles.length];
          for (int i=0; i < bundles.length; i++) {
              ids[i] = bundles[i].getBundleId();
          }
  
          // The IDs are sorted here. It's not required by the spec but it's nice
          // to have an ordered list returned.
          Arrays.sort(ids);
  
          return ids;
      }
  
      /**
       * @see org.osgi.jmx.framework.BundleStateMBean#listBundles()
       */
      public TabularData listBundles() throws IOException {
          return listBundles(BundleStateMBean.BUNDLE_TYPE.keySet());
      }
  
      public TabularData listBundles(String ... items) throws IOException {
          return listBundles(Arrays.asList(items));
      }
  
      private TabularData listBundles(Collection<String> items) throws IOException {
          Bundle[] containerBundles = bundleContext.getBundles();
          List<BundleData> bundleDatas = new ArrayList<BundleData>();
          if (containerBundles != null) {
              for (Bundle containerBundle : containerBundles) {
                  bundleDatas.add(new BundleData(bundleContext, containerBundle, packageAdmin, startLevel));
              }
          }
          TabularData bundleTable = new TabularDataSupport(BUNDLES_TYPE);
          for (BundleData bundleData : bundleDatas) {
              bundleTable.put(bundleData.toCompositeData(items));
          }
          return bundleTable;
      }
  
      /**
       * @see javax.management.NotificationBroadcasterSupport#getNotificationInfo()
       */
      public MBeanNotificationInfo[] getNotificationInfo() {
          MBeanNotificationInfo eventInfo = new MBeanNotificationInfo(
                  new String[] { BUNDLE_EVENT },
                  Notification.class.getName(),
                  "A BundleEvent issued from the Framework describing a bundle lifecycle change");
  
          MBeanNotificationInfo attributeChangeInfo = new MBeanNotificationInfo(
                  new String [] {AttributeChangeNotification.ATTRIBUTE_CHANGE },
                  AttributeChangeNotification.class.getName(),
                  "An attribute of this MBean has changed");
  
          return new MBeanNotificationInfo[] { eventInfo, attributeChangeInfo };
      }
  
      /**
       * @see javax.management.MBeanRegistration#postDeregister()
       */
      public void postDeregister() {
          if (registrations.decrementAndGet() < 1) {
              shutDownDispatcher();
          }
      }
  
      /**
       * @see javax.management.MBeanRegistration#postRegister(java.lang.Boolean)
       */
      public void postRegister(Boolean registrationDone) {
          if (registrationDone && registrations.incrementAndGet() == 1) {
              eventDispatcher = Executors.newSingleThreadExecutor(new JMXThreadFactory("JMX OSGi Bundle State Event Dispatcher"));
              bundleContext.addBundleListener(bundleListener);
          }
      }
  
      /**
       * @see javax.management.MBeanRegistration#preDeregister()
       */
      public void preDeregister() throws Exception {
          // No action
      }
  
      /**
       * @see javax.management.MBeanRegistration#preRegister(javax.management.MBeanServer, javax.management.ObjectName)
       */
      public ObjectName preRegister(MBeanServer server, ObjectName name) throws Exception {
          lock.lock();
          try {
              if (bundleListener == null) {
                  bundleListener = new BundleListener() {
                      public void bundleChanged(BundleEvent event) {
                          if (stateConfig != null && !stateConfig.isBundleChangeNotificationEnabled()) {
                              return;
                          }
                          try {
                              final Notification notification = new Notification(EVENT, OBJECTNAME,
                                      notificationSequenceNumber.getAndIncrement());
                              notification.setUserData(new BundleEventData(event).toCompositeData());
  
                              // also send notifications to the bundleIDs attribute listeners, if a bundle was added or removed
                              final AttributeChangeNotification attributeChangeNotification =
                                      getAttributeChangeNotification(event);
  
                              eventDispatcher.submit(new Runnable() {
                                  public void run() {
                                      sendNotification(notification);
                                      if (attributeChangeNotification != null)
                                          sendNotification(attributeChangeNotification);
                                  }
                              });
                          } catch (RejectedExecutionException re) {
                              logger.log(LogService.LOG_WARNING, "Task rejected for JMX Notification dispatch of event ["
                                      + event + "] - Dispatcher may have been shutdown");
                          } catch (Exception e) {
                              logger.log(LogService.LOG_WARNING,
                                      "Exception occured on JMX Notification dispatch for event [" + event + "]", e);
                          }
                      }
                  };
              }
          } finally {
              lock.unlock();
          }
          return name;
      }
  
      protected AttributeChangeNotification getAttributeChangeNotification(BundleEvent event) throws IOException {
          if (stateConfig != null && !stateConfig.isAttributeChangeNotificationEnabled()) {
              return null;
          }
  
          int eventType = event.getType();
          switch (eventType) {
          case BundleEvent.INSTALLED:
          case BundleEvent.UNINSTALLED:
              long bundleID = event.getBundle().getBundleId();
              long[] ids = getBundleIds();
  
              List<Long> without = new ArrayList<Long>();
              for (long id : ids) {
                  if (id != bundleID)
                      without.add(id);
              }
              List<Long> with = new ArrayList<Long>(without);
              with.add(bundleID);
  
              // Sorting is not mandatory, but its nice for the user, note that getBundleIds() also returns a sorted array
              Collections.sort(with);
  
              List<Long> oldList = eventType == BundleEvent.INSTALLED ? without : with;
              List<Long> newList = eventType == BundleEvent.INSTALLED ? with : without;
  
              long[] oldIDs = new long[oldList.size()];
              for (int i = 0; i < oldIDs.length; i++) {
                  oldIDs[i] = oldList.get(i);
              }
  
              long[] newIDs = new long[newList.size()];
              for (int i = 0; i < newIDs.length; i++) {
                  newIDs[i] = newList.get(i);
              }
  
              return new AttributeChangeNotification(OBJECTNAME, attributeChangeNotificationSequenceNumber.getAndIncrement(),
                      System.currentTimeMillis(), "BundleIds changed", "BundleIds", "Array of long", oldIDs, newIDs);
          default:
              return null;
          }
      }
  
      /*
       * Shuts down the notification dispatcher
       * [ARIES-259] MBeans not getting unregistered reliably
       */
      protected void shutDownDispatcher() {
          if (bundleListener != null) {
              try {
                 bundleContext.removeBundleListener(bundleListener);
              }
              catch (Exception e) {
                 // ignore
              }
          }
          if (eventDispatcher != null) {
              eventDispatcher.shutdown();
          }
      }
  
      /*
       * Returns the ExecutorService used to dispatch Notifications
       */
      protected ExecutorService getEventDispatcher() {
          return eventDispatcher;
      }
  
  }
  