FELIX-2236 Improvement and fixes:
  * Ensure bundle can be started without Config Admin, Metatype and Web Console on IBM J9 VM
  * Support basic configuration with framework properties
  * Prevent NullPointerException if no configuration is available or config is deleted

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@927746 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/webconsole-plugins/memoryusage/src/main/java/org/apache/felix/webconsole/plugins/memoryusage/internal/Activator.java b/webconsole-plugins/memoryusage/src/main/java/org/apache/felix/webconsole/plugins/memoryusage/internal/Activator.java
index 18d0fc1..4b18d68 100644
--- a/webconsole-plugins/memoryusage/src/main/java/org/apache/felix/webconsole/plugins/memoryusage/internal/Activator.java
+++ b/webconsole-plugins/memoryusage/src/main/java/org/apache/felix/webconsole/plugins/memoryusage/internal/Activator.java
@@ -1,18 +1,18 @@
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
+ * or more contributor license agreements. See the NOTICE file
  * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
+ * 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
+ * with the License. You may obtain a copy of the License at
  *
- *   http://www.apache.org/licenses/LICENSE-2.0
+ * 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
+ * KIND, either express or implied. See the License for the
  * specific language governing permissions and limitations
  * under the License.
  */
@@ -49,14 +49,12 @@
         // install Web Console plugin
         try
         {
-            MemoryUsagePanel tdp = new MemoryUsagePanel(support);
-            tdp.activate(bundleContext);
-
             Dictionary<String, Object> properties = new Hashtable<String, Object>();
-            properties.put("felix.webconsole.label", tdp.getLabel());
+            properties.put("felix.webconsole.label", MemoryUsageConstants.LABEL);
 
             register(bundleContext, new String[]
-                { "javax.servlet.Servlet", "org.apache.felix.webconsole.ConfigurationPrinter" }, tdp, properties);
+                { "javax.servlet.Servlet", "org.apache.felix.webconsole.ConfigurationPrinter" }, new MemoryUsagePanel(
+                bundleContext, support), properties);
         }
         catch (Throwable t)
         {
@@ -66,15 +64,15 @@
         // register for configuration
         try
         {
-            MemoryUsageConfigurator tdp = new MemoryUsageConfigurator(support);
             Dictionary<String, Object> properties = new Hashtable<String, Object>();
-            properties.put(Constants.SERVICE_PID, MemoryUsageConfigurator.NAME);
+            properties.put(Constants.SERVICE_PID, MemoryUsageConstants.PID);
             register(bundleContext, new String[]
-                { "org.osgi.service.cm.ManagedService" }, tdp, properties);
+                { "org.osgi.service.cm.ManagedService" }, new MemoryUsageConfigurator(support), properties);
         }
         catch (Throwable t)
         {
-            // Configuration Admin and Metatype Service API might not be available, don't care
+            // Configuration Admin and Metatype Service API might not be
+            // available, don't care
         }
     }
 
@@ -87,7 +85,7 @@
         }
     }
 
-    private void register(BundleContext context, String[] serviceNames, Object service,
+    static void register(BundleContext context, String[] serviceNames, Object service,
         Dictionary<String, Object> properties)
     {
 
diff --git a/webconsole-plugins/memoryusage/src/main/java/org/apache/felix/webconsole/plugins/memoryusage/internal/MemoryUsageCommand.java b/webconsole-plugins/memoryusage/src/main/java/org/apache/felix/webconsole/plugins/memoryusage/internal/MemoryUsageCommand.java
index 9a8a45d..9499a33 100644
--- a/webconsole-plugins/memoryusage/src/main/java/org/apache/felix/webconsole/plugins/memoryusage/internal/MemoryUsageCommand.java
+++ b/webconsole-plugins/memoryusage/src/main/java/org/apache/felix/webconsole/plugins/memoryusage/internal/MemoryUsageCommand.java
@@ -25,7 +25,7 @@
 
 import org.apache.felix.shell.Command;
 
-public class MemoryUsageCommand implements Command
+class MemoryUsageCommand implements Command
 {
 
     private static final String HELP_CMD = "help";
@@ -42,7 +42,7 @@
 
     private final MemoryUsageSupport support;
 
-    public MemoryUsageCommand(final MemoryUsageSupport support)
+    MemoryUsageCommand(final MemoryUsageSupport support)
     {
         this.support = support;
     }
diff --git a/webconsole-plugins/memoryusage/src/main/java/org/apache/felix/webconsole/plugins/memoryusage/internal/MemoryUsageConfigurator.java b/webconsole-plugins/memoryusage/src/main/java/org/apache/felix/webconsole/plugins/memoryusage/internal/MemoryUsageConfigurator.java
index 8aad994..7fbe40a 100644
--- a/webconsole-plugins/memoryusage/src/main/java/org/apache/felix/webconsole/plugins/memoryusage/internal/MemoryUsageConfigurator.java
+++ b/webconsole-plugins/memoryusage/src/main/java/org/apache/felix/webconsole/plugins/memoryusage/internal/MemoryUsageConfigurator.java
@@ -1,18 +1,18 @@
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
+ * or more contributor license agreements. See the NOTICE file
  * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
+ * 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
+ * with the License. You may obtain a copy of the License at
  *
- *   http://www.apache.org/licenses/LICENSE-2.0
+ * 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
+ * KIND, either express or implied. See the License for the
  * specific language governing permissions and limitations
  * under the License.
  */
@@ -21,21 +21,17 @@
 import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.Dictionary;
+import java.util.Hashtable;
+
 import org.osgi.service.cm.ConfigurationException;
 import org.osgi.service.cm.ManagedService;
 import org.osgi.service.metatype.AttributeDefinition;
 import org.osgi.service.metatype.MetaTypeProvider;
 import org.osgi.service.metatype.ObjectClassDefinition;
 
-public class MemoryUsageConfigurator implements ManagedService, MetaTypeProvider
+class MemoryUsageConfigurator implements ManagedService, MetaTypeProvider
 {
 
-    static final String NAME = "org.apache.felix.webconsole.plugins.memoryusage.internal.MemoryUsageConfigurator";
-
-    private static final String PROP_DUMP_THRESHOLD = "dump.threshold";
-
-    private static final String PROP_DUMP_LOCATION = "dump.location";
-
     private final MemoryUsageSupport support;
 
     private ObjectClassDefinition ocd;
@@ -48,7 +44,13 @@
     @SuppressWarnings("unchecked")
     public void updated(Dictionary properties) throws ConfigurationException
     {
-        final Object thresholdValue = properties.get(PROP_DUMP_THRESHOLD);
+        // ensure default values if there is no config or config is deleted
+        if (properties == null)
+        {
+            properties = new Hashtable();
+        }
+
+        final Object thresholdValue = properties.get(MemoryUsageConstants.PROP_DUMP_THRESHOLD);
         if (thresholdValue != null)
         {
             final int threshold;
@@ -78,12 +80,20 @@
                 throw failure(iae.getMessage());
             }
         }
+        else
+        {
+            support.setThreshold(-1);
+        }
 
-        final Object locationValue = properties.get(PROP_DUMP_LOCATION);
+        final Object locationValue = properties.get(MemoryUsageConstants.PROP_DUMP_LOCATION);
         if (locationValue instanceof String)
         {
             support.setDumpLocation((String) locationValue);
         }
+        else
+        {
+            support.setDumpLocation(null);
+        }
     }
 
     public String[] getLocales()
@@ -93,7 +103,7 @@
 
     public ObjectClassDefinition getObjectClassDefinition(String id, String locale)
     {
-        if (!NAME.equals(id))
+        if (!MemoryUsageConstants.PID.equals(id))
         {
             return null;
         }
@@ -102,11 +112,11 @@
         {
 
             final ArrayList<AttributeDefinition> adList = new ArrayList<AttributeDefinition>();
-            adList.add(new AttributeDefinitionImpl(PROP_DUMP_THRESHOLD, "Dump Threshold",
+            adList.add(new AttributeDefinitionImpl(MemoryUsageConstants.PROP_DUMP_THRESHOLD, "Dump Threshold",
                 "Threshold at which to automatically create a memory dump as a percentage in the range "
-                    + MemoryUsageSupport.MIN_DUMP_THRESHOLD + " to " + MemoryUsageSupport.MAX_DUMP_THRESHOLD
+                    + MemoryUsageConstants.MIN_DUMP_THRESHOLD + " to " + MemoryUsageConstants.MAX_DUMP_THRESHOLD
                     + " or zero to disable automatic dump creation.", AttributeDefinition.INTEGER, new String[]
-                    { String.valueOf(MemoryUsageSupport.DEFAULT_DUMP_THRESHOLD) }, 0, null, null)
+                    { String.valueOf(MemoryUsageConstants.DEFAULT_DUMP_THRESHOLD) }, 0, null, null)
             {
                 @Override
                 public String validate(String value)
@@ -114,11 +124,10 @@
                     try
                     {
                         int threshold = Integer.parseInt(value);
-                        if (threshold != 0
-                            && (threshold < MemoryUsageSupport.MIN_DUMP_THRESHOLD || threshold > MemoryUsageSupport.MAX_DUMP_THRESHOLD))
+                        if (!MemoryUsageConstants.isThresholdValid(threshold))
                         {
-                            return "Threshold must in the range " + MemoryUsageSupport.MIN_DUMP_THRESHOLD + " to "
-                                + MemoryUsageSupport.MAX_DUMP_THRESHOLD + " or zero";
+                            return "Threshold must in the range " + MemoryUsageConstants.MIN_DUMP_THRESHOLD + " to "
+                                + MemoryUsageConstants.MAX_DUMP_THRESHOLD + " or zero";
                         }
                         return ""; // everything ok
                     }
@@ -128,7 +137,7 @@
                     }
                 }
             });
-            adList.add(new AttributeDefinitionImpl(PROP_DUMP_LOCATION, "Dumpe Location",
+            adList.add(new AttributeDefinitionImpl(MemoryUsageConstants.PROP_DUMP_LOCATION, "Dumpe Location",
                 "The filesystem location where heap dumps are stored. If this is null or empty (the default) the dumps are stored in "
                     + support.getDefaultDumpLocation(), ""));
 
@@ -149,7 +158,7 @@
 
                 public String getID()
                 {
-                    return NAME;
+                    return MemoryUsageConstants.PID;
                 }
 
                 public String getDescription()
@@ -169,9 +178,9 @@
 
     private ConfigurationException failure(final Object invalidValue)
     {
-        return new ConfigurationException(PROP_DUMP_THRESHOLD, "Invalid Dump Threshold value '" + invalidValue
-            + "': Must be an integer number in the range " + MemoryUsageSupport.MIN_DUMP_THRESHOLD + " to "
-            + MemoryUsageSupport.MAX_DUMP_THRESHOLD + " or zero to disable");
+        return new ConfigurationException(MemoryUsageConstants.PROP_DUMP_THRESHOLD, "Invalid Dump Threshold value '"
+            + invalidValue + "': Must be an integer number in the range " + MemoryUsageConstants.MIN_DUMP_THRESHOLD
+            + " to " + MemoryUsageConstants.MAX_DUMP_THRESHOLD + " or zero to disable");
     }
 
     private static class AttributeDefinitionImpl implements AttributeDefinition
diff --git a/webconsole-plugins/memoryusage/src/main/java/org/apache/felix/webconsole/plugins/memoryusage/internal/MemoryUsageConstants.java b/webconsole-plugins/memoryusage/src/main/java/org/apache/felix/webconsole/plugins/memoryusage/internal/MemoryUsageConstants.java
new file mode 100644
index 0000000..a89ff98
--- /dev/null
+++ b/webconsole-plugins/memoryusage/src/main/java/org/apache/felix/webconsole/plugins/memoryusage/internal/MemoryUsageConstants.java
@@ -0,0 +1,92 @@
+/*
+ * 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.memoryusage.internal;
+
+/**
+ * The <code>MemoryUsageConstants</code> provides some basic constants for
+ * the MemoryUsage support bundle
+ */
+final class MemoryUsageConstants
+{
+
+    /**
+     * Service PID for the Configuration Admin configuration object to be
+     * provided to configure the memory usage support.
+     */
+    static final String PID = "org.apache.felix.webconsole.plugins.memoryusage.internal.MemoryUsageConfigurator";
+
+    /**
+     * The label (or address) under which the Memory Usage Web Console Plugin
+     * is accessible.
+     */
+    static final String LABEL = "memoryusage";
+
+    /**
+     * The name of the property providing the filesystem location where the
+     * memory dumps should be placed. If this location is relative it is located
+     * inside the bundle private data area.
+     * <p>
+     * This property may be set as a framework property or as a property of
+     * configuration provided by the Configuration Admin Service for the service
+     * {@link #PID}.
+     */
+    static final String PROP_DUMP_LOCATION = "felix.memoryusage.dump.location";
+
+    /**
+     * The name of the property providing threshold as a percentage of the
+     * maximum available memory at which an automatic memory dumps should
+     * be created.
+     * <p>
+     * This property may be set as a framework property or as a property of
+     * configuration provided by the Configuration Admin Service for the service
+     * {@link #PID}.
+     * <p>
+     * The property must be an integer value or be parseable to an integer
+     * value. The value must be zero to disable automatic dump generation or in
+     * the range [{@link #MIN_DUMP_THRESHOLD}..{@link #MAX_DUMP_THRESHOLD}].
+     */
+    static final String PROP_DUMP_THRESHOLD = "felix.memoryusage.dump.threshold";
+
+    /**
+     * The default automatic heap dump threshold if none has been configured
+     * (or no configuration has yet been provided).
+     */
+    static final int DEFAULT_DUMP_THRESHOLD = 95;
+    /**
+     * The maximum allowed automatic heap dump threshold.
+     */
+    static final int MAX_DUMP_THRESHOLD = 99;
+    /**
+     * The minimum allowed automatic heap dump threshold.
+     */
+    static final int MIN_DUMP_THRESHOLD = 50;
+
+    /**
+     * Returns <code>true</code> if the given <code>threshold</code>value is
+     * valid; that is if the vaue is either zero or in the range [
+     * {@link #MIN_DUMP_THRESHOLD}..{@link #MAX_DUMP_THRESHOLD}].
+     *
+     * @param threshold The threshold value (percentage) to validate
+     * @return <code>true</code> if the value is valid
+     */
+    static boolean isThresholdValid(final int threshold)
+    {
+        return threshold == 0 || (threshold >= MIN_DUMP_THRESHOLD && threshold <= MAX_DUMP_THRESHOLD);
+    }
+}
diff --git a/webconsole-plugins/memoryusage/src/main/java/org/apache/felix/webconsole/plugins/memoryusage/internal/MemoryUsagePanel.java b/webconsole-plugins/memoryusage/src/main/java/org/apache/felix/webconsole/plugins/memoryusage/internal/MemoryUsagePanel.java
index a20341f..3e54f43 100644
--- a/webconsole-plugins/memoryusage/src/main/java/org/apache/felix/webconsole/plugins/memoryusage/internal/MemoryUsagePanel.java
+++ b/webconsole-plugins/memoryusage/src/main/java/org/apache/felix/webconsole/plugins/memoryusage/internal/MemoryUsagePanel.java
@@ -40,11 +40,12 @@
 import org.apache.felix.webconsole.ConfigurationPrinter;
 import org.apache.felix.webconsole.DefaultVariableResolver;
 import org.apache.felix.webconsole.WebConsoleUtil;
+import org.osgi.framework.BundleContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 @SuppressWarnings("serial")
-public class MemoryUsagePanel extends AbstractWebConsolePlugin implements ConfigurationPrinter, AttachmentProvider
+class MemoryUsagePanel extends AbstractWebConsolePlugin implements ConfigurationPrinter, AttachmentProvider
 {
 
     /** default log */
@@ -52,9 +53,10 @@
 
     private final MemoryUsageSupport support;
 
-    public MemoryUsagePanel(final MemoryUsageSupport support)
+    MemoryUsagePanel(final BundleContext bundleContext, final MemoryUsageSupport support)
     {
         this.support = support;
+        activate(bundleContext);
     }
 
     // ---------- AbstractWebConsolePlugin
@@ -62,7 +64,7 @@
     @Override
     public String getLabel()
     {
-        return "memoryusage";
+        return MemoryUsageConstants.LABEL;
     }
 
     @Override
diff --git a/webconsole-plugins/memoryusage/src/main/java/org/apache/felix/webconsole/plugins/memoryusage/internal/MemoryUsageSupport.java b/webconsole-plugins/memoryusage/src/main/java/org/apache/felix/webconsole/plugins/memoryusage/internal/MemoryUsageSupport.java
index be79755..705c419 100644
--- a/webconsole-plugins/memoryusage/src/main/java/org/apache/felix/webconsole/plugins/memoryusage/internal/MemoryUsageSupport.java
+++ b/webconsole-plugins/memoryusage/src/main/java/org/apache/felix/webconsole/plugins/memoryusage/internal/MemoryUsageSupport.java
@@ -31,6 +31,7 @@
 import java.util.Date;
 import java.util.List;
 import java.util.NoSuchElementException;
+import java.util.TreeSet;
 
 import javax.management.ListenerNotFoundException;
 import javax.management.MBeanServer;
@@ -46,22 +47,6 @@
 final class MemoryUsageSupport implements NotificationListener
 {
 
-    /**
-     * The minimum allowed automatic heap dump threshold (value is 50).
-     */
-    static final int MIN_DUMP_THRESHOLD = 50;
-
-    /**
-     * The maximum allowed automatic heap dump threshold (value is 99).
-     */
-    static final int MAX_DUMP_THRESHOLD = 99;
-
-    /**
-     * The default automatic heap dump threshold if none has been configured
-     * (or no configuration has yet been provided) (value is 95).
-     */
-    static final int DEFAULT_DUMP_THRESHOLD = 95;
-
     // This is the name of the HotSpot Diagnostic MBean
     private static final String HOTSPOT_BEAN_NAME = "com.sun.management:type=HotSpotDiagnostic";
 
@@ -71,6 +56,9 @@
     // or the current working directory
     private final File defaultDumpLocation;
 
+    // the default threshold value
+    private final int defaultThreshold;
+
     // the configured dump location
     private File dumpLocation;
 
@@ -79,13 +67,25 @@
 
     MemoryUsageSupport(final BundleContext context)
     {
-        // get the dump location
-        File dumps = context.getDataFile("dumps");
-        if (dumps == null)
+
+        // the default dump location
+        String propDumps = context.getProperty(MemoryUsageConstants.PROP_DUMP_LOCATION);
+        if (propDumps == null)
         {
-            dumps = new File("dumps");
+            propDumps = "dumps";
         }
-        defaultDumpLocation = dumps.getAbsoluteFile();
+
+        // ensure dump location is an absolute path/location
+        File dumps = new File(propDumps);
+        if (!dumps.isAbsolute())
+        {
+            File bundleDumps = context.getDataFile(propDumps);
+            if (bundleDumps != null)
+            {
+                dumps = bundleDumps;
+            }
+        }
+        this.defaultDumpLocation = dumps.getAbsoluteFile();
 
         // prepare the dump location
         setDumpLocation(null);
@@ -95,7 +95,37 @@
         memEmitter.addNotificationListener(this, null, null);
 
         // set the initial automatic dump threshold
-        setThreshold(DEFAULT_DUMP_THRESHOLD);
+        int defaultThreshold;
+        String propThreshold = context.getProperty(MemoryUsageConstants.PROP_DUMP_THRESHOLD);
+        if (propThreshold != null)
+        {
+            try
+            {
+                defaultThreshold = Integer.parseInt(propThreshold);
+                setThreshold(defaultThreshold);
+            }
+            catch (Exception e)
+            {
+                // NumberFormatException - if propTreshold cannot be parsed to
+                // int
+                // IllegalArgumentException - if threshold is invalid
+                defaultThreshold = -1;
+            }
+        }
+        else
+        {
+            defaultThreshold = -1;
+        }
+
+        // default threshold has not been configured (correctly), assume fixed
+        // default
+        if (defaultThreshold < 0)
+        {
+            defaultThreshold = MemoryUsageConstants.DEFAULT_DUMP_THRESHOLD;
+            setThreshold(defaultThreshold);
+        }
+
+        this.defaultThreshold = defaultThreshold;
     }
 
     void dispose()
@@ -117,15 +147,23 @@
      * @param percentage The threshold as a percentage of memory consumption.
      *            This value may be 0 (zero) to switch off automatic heap dumps
      *            or in the range {@link #MIN_DUMP_THRESHOLD} to
-     *            {@link #MAX_DUMP_THRESHOLD}.
+     *            {@link #MAX_DUMP_THRESHOLD}. If set to a negative value,
+     *            the default threshold is assumed.
      * @throws IllegalArgumentException if the percentage value is outside of
      *             the valid range of thresholds. The message is the percentage
      *             value which is not accepted.
      */
-    final void setThreshold(final int percentage)
+    final void setThreshold(int percentage)
     {
-        if (threshold == 0 || (threshold >= MIN_DUMP_THRESHOLD && threshold <= MAX_DUMP_THRESHOLD))
+        if (percentage < 0)
         {
+            percentage = defaultThreshold;
+        }
+
+        if (MemoryUsageConstants.isThresholdValid(percentage))
+        {
+            TreeSet<String> thresholdPools = new TreeSet<String>();
+            TreeSet<String> noThresholdPools = new TreeSet<String>();
             List<MemoryPoolMXBean> pools = getMemoryPools();
             for (MemoryPoolMXBean pool : pools)
             {
@@ -133,13 +171,21 @@
                 {
                     long threshold = pool.getUsage().getMax() * percentage / 100;
                     pool.setUsageThreshold(threshold);
+                    thresholdPools.add(pool.getName());
+                }
+                else
+                {
+                    noThresholdPools.add(pool.getName());
                 }
             }
             this.threshold = percentage;
+
+            log.info("Setting Automatic Memory Dump Threshold to {}% for pools {}", threshold, thresholdPools);
+            log.info("Automatic Memory Dump cannot be set for pools {}", noThresholdPools);
         }
         else
         {
-            throw new IllegalArgumentException(String.valueOf(threshold));
+            throw new IllegalArgumentException(String.valueOf(percentage));
         }
     }
 
@@ -304,6 +350,8 @@
         {
             this.dumpLocation = new File(dumpLocation).getAbsoluteFile();
         }
+
+        log.info("Storing Memory Dumps in {}", this.dumpLocation);
     }
 
     final File getDumpLocation()