Author: cziegeler
Date: Mon Aug 19 10:46:00 2013
New Revision: 1515349

URL: http://svn.apache.org/r1515349
Log:
SLING-3021 :  Use service properties for HC meta data and improve JMX 
registration 

Modified:
    
sling/trunk/contrib/extensions/healthcheck/jmx/src/main/java/org/apache/sling/hc/jmx/impl/HealthCheckMBean.java
    
sling/trunk/contrib/extensions/healthcheck/jmx/src/main/java/org/apache/sling/hc/jmx/impl/HealthCheckMBeanCreator.java
    
sling/trunk/contrib/extensions/healthcheck/jmx/src/test/java/org/apache/sling/hc/jmx/impl/HealthCheckMBeanTest.java

Modified: 
sling/trunk/contrib/extensions/healthcheck/jmx/src/main/java/org/apache/sling/hc/jmx/impl/HealthCheckMBean.java
URL: 
http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/healthcheck/jmx/src/main/java/org/apache/sling/hc/jmx/impl/HealthCheckMBean.java?rev=1515349&r1=1515348&r2=1515349&view=diff
==============================================================================
--- 
sling/trunk/contrib/extensions/healthcheck/jmx/src/main/java/org/apache/sling/hc/jmx/impl/HealthCheckMBean.java
 (original)
+++ 
sling/trunk/contrib/extensions/healthcheck/jmx/src/main/java/org/apache/sling/hc/jmx/impl/HealthCheckMBean.java
 Mon Aug 19 10:46:00 2013
@@ -17,8 +17,8 @@
  */
 package org.apache.sling.hc.jmx.impl;
 
-import java.io.Serializable;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -46,21 +46,13 @@ import org.apache.sling.hc.api.HealthChe
 import org.apache.sling.hc.api.Result;
 import org.apache.sling.hc.api.ResultLog;
 import org.osgi.framework.ServiceReference;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 /** A {@link DynamicMBean} used to execute a {@link HealthCheck} service */
-public class HealthCheckMBean implements DynamicMBean, Serializable {
-
-    private static final long serialVersionUID = -90745301105975287L;
-    private static final Logger logger = 
LoggerFactory.getLogger(HealthCheckMBean.class);
-    private final String beanName;
-    private final String jmxTypeName;
-    private final HealthCheck healthCheck;
+public class HealthCheckMBean implements DynamicMBean {
 
     public static final String HC_OK_ATTRIBUTE_NAME = "ok";
     public static final String HC_STATUS_ATTRIBUTE_NAME = "status";
-    public static final String LOG_ATTRIBUTE_NAME = "log";
+    public static final String HC_LOG_ATTRIBUTE_NAME = "log";
 
     private static CompositeType LOG_ROW_TYPE;
     private static TabularType LOG_TABLE_TYPE;
@@ -69,149 +61,174 @@ public class HealthCheckMBean implements
     public static final String LEVEL_COLUMN = "level";
     public static final String MESSAGE_COLUMN = "message";
 
-    public static final String DEFAULT_JMX_TYPE_NAME = "HealthCheck";
+    public static final String JMX_TYPE_NAME = "HealthCheck";
+    public static final String JMX_DOMAIN = "org.apache.sling.healthcheck";
+
+    /** The health check service to call. */
+    private final HealthCheck healthCheck;
+
+    /** The mbean info. */
+    private final MBeanInfo mbeanInfo;
 
-    private final ServiceReference serviceReference;
+    /** The default attributes. */
+    private final Map<String, Object> defaultAttributes;
 
     static {
         try {
             // Define the log row and table types
             LOG_ROW_TYPE = new CompositeType(
                     "LogLine",
-                    "A line in the Rule log",
+                    "A line in the result log",
                     new String [] { INDEX_COLUMN, LEVEL_COLUMN, MESSAGE_COLUMN 
},
                     new String [] { "log line index", "log level", "log 
message"},
                     new OpenType[] { SimpleType.INTEGER, SimpleType.STRING, 
SimpleType.STRING }
                     );
             final String [] indexes = { INDEX_COLUMN };
-            LOG_TABLE_TYPE = new TabularType("LogTable", "Rule log messages", 
LOG_ROW_TYPE, indexes);
+            LOG_TABLE_TYPE = new TabularType("LogTable", "Result log 
messages", LOG_ROW_TYPE, indexes);
         } catch(Exception ignore) {
             // row or table type will be null if this happens
         }
     }
 
     public HealthCheckMBean(final ServiceReference ref, final HealthCheck hc) {
-        this.serviceReference = ref;
-        String name = (String)ref.getProperty(HealthCheck.MBEAN_NAME);
-        if(empty(name)) {
-            name = (String)ref.getProperty(HealthCheck.NAME);
-        }
-
-        if(empty(name)) {
-            name = hc.toString();
-        }
-
-        final int pos = name.indexOf('/');
-        if(pos > 0) {
-            jmxTypeName = name.substring(0, pos);
-            beanName = name.substring(pos + 1);
-        } else {
-            jmxTypeName = DEFAULT_JMX_TYPE_NAME;
-            beanName = name;
-        }
-
-        healthCheck = hc;
-    }
-
-    private static boolean empty(String str) {
-        return str == null || str.trim().length() == 0;
+        this.healthCheck = hc;
+        this.mbeanInfo = this.createMBeanInfo(ref);
+        this.defaultAttributes = this.createDefaultAttributes(ref);
     }
 
     @Override
     public Object getAttribute(final String attribute)
-            throws AttributeNotFoundException, MBeanException, 
ReflectionException {
-
-        // TODO cache the result of execution for a few seconds?
-        final Result result = healthCheck.execute();
-
-        if(HC_OK_ATTRIBUTE_NAME.equals(attribute)) {
-            return result.isOk();
-        } else if(LOG_ATTRIBUTE_NAME.equals(attribute)) {
-            return logData(result);
-        } else if(HC_STATUS_ATTRIBUTE_NAME.equals(attribute)) {
-            return result.getStatus().toString();
-        } else {
-            final Object o = this.serviceReference.getProperty(attribute);
-            if(o == null) {
-                throw new AttributeNotFoundException(attribute);
-            }
-            return o;
+    throws AttributeNotFoundException, MBeanException, ReflectionException {
+        // we should call getAttributes - and not vice versa to have the result
+        // of a single check call - and not do a check call for each attribute
+        final AttributeList result = this.getAttributes(new String[] 
{attribute});
+        if ( result.size() == 0 ) {
+            throw new AttributeNotFoundException(attribute);
         }
+        final Attribute attr = (Attribute) result.get(0);
+        return attr.getValue();
     }
 
-    private TabularData logData(Result er) {
+    private TabularData logData(final Result er) throws OpenDataException {
         final TabularDataSupport result = new 
TabularDataSupport(LOG_TABLE_TYPE);
-        int i=1;
-        for(ResultLog.Entry e : er) {
+        int i = 1;
+        for(final ResultLog.Entry e : er) {
             final Map<String, Object> data = new HashMap<String, Object>();
             data.put(INDEX_COLUMN, i++);
             data.put(LEVEL_COLUMN, e.getStatus().toString());
             data.put(MESSAGE_COLUMN, e.getMessage());
-            try {
-                result.put(new CompositeDataSupport(LOG_ROW_TYPE, data));
-            } catch(OpenDataException ode) {
-                throw new IllegalStateException("OpenDataException while 
creating log data", ode);
-            }
+
+            result.put(new CompositeDataSupport(LOG_ROW_TYPE, data));
         }
         return result;
     }
 
     @Override
-    public AttributeList getAttributes(String[] attributes) {
+    public AttributeList getAttributes(final String[] attributes) {
         final AttributeList result = new AttributeList();
-        for(String key : attributes) {
-            try {
-                result.add(new Attribute(key, getAttribute(key)));
-            } catch(Exception e) {
-                logger.error("Exception getting Attribute " + key, e);
+        if ( attributes != null ) {
+            Result hcResult = null;
+            for(final String key : attributes) {
+                final Object defaultValue = this.defaultAttributes.get(key);
+                if ( defaultValue != null ) {
+                    result.add(new Attribute(key, defaultValue));
+                } else {
+                    if ( HC_OK_ATTRIBUTE_NAME.equals(key) ) {
+                        if ( hcResult == null ) {
+                            hcResult = this.healthCheck.execute();
+                        }
+                        result.add(new Attribute(key, hcResult.isOk()));
+                    } else if ( HC_LOG_ATTRIBUTE_NAME.equals(key) ) {
+                        if ( hcResult == null ) {
+                            hcResult = this.healthCheck.execute();
+                        }
+                        try {
+                            result.add(new Attribute(key, logData(hcResult)));
+                        } catch ( final OpenDataException ignore ) {
+                            // we ignore this and simply don't add the 
attribute
+                        }
+                    } else if ( HC_STATUS_ATTRIBUTE_NAME.equals(key) ) {
+                        if ( hcResult == null ) {
+                            hcResult = this.healthCheck.execute();
+                        }
+                        result.add(new Attribute(key, 
hcResult.getStatus().toString()));
+                    }
+                }
             }
         }
+
         return result;
     }
 
-    @Override
-    public MBeanInfo getMBeanInfo() {
+    /**
+     * Create the mbean info
+     */
+    private MBeanInfo createMBeanInfo(final ServiceReference serviceReference) 
{
         final List<MBeanAttributeInfo> attrs = new 
ArrayList<MBeanAttributeInfo>();
 
         // add relevant service properties
-        final String[] serviceProps = new String[] {HealthCheck.NAME, 
HealthCheck.TAGS};
-        for(final String propName : serviceProps) {
-            if ( this.serviceReference.getProperty(propName) != null ) {
-                attrs.add(new MBeanAttributeInfo(propName, 
List.class.getName(), "Description of " + propName, true, false, false));
-            }
+        if ( serviceReference.getProperty(HealthCheck.NAME) != null ) {
+            attrs.add(new MBeanAttributeInfo(HealthCheck.NAME, 
String.class.getName(), "The name of the health check service.", true, false, 
false));
+        }
+        if ( serviceReference.getProperty(HealthCheck.TAGS) != null ) {
+            attrs.add(new MBeanAttributeInfo(HealthCheck.TAGS, 
String.class.getName(), "The tags of the health check service.", true, false, 
false));
         }
 
         // add standard attributes
-        attrs.add(new MBeanAttributeInfo(HC_OK_ATTRIBUTE_NAME, 
Boolean.class.getName(), "The HealthCheck result", true, false, false));
-        attrs.add(new OpenMBeanAttributeInfoSupport(LOG_ATTRIBUTE_NAME, "The 
rule log", LOG_TABLE_TYPE, true, false, false));
-        attrs.add(new MBeanAttributeInfo(HC_STATUS_ATTRIBUTE_NAME, 
String.class.getName(), "The HealthCheck status", true, false, false));
+        attrs.add(new MBeanAttributeInfo(HC_OK_ATTRIBUTE_NAME, 
Boolean.class.getName(), "The health check result", true, false, false));
+        attrs.add(new MBeanAttributeInfo(HC_STATUS_ATTRIBUTE_NAME, 
String.class.getName(), "The health check status", true, false, false));
+
+        attrs.add(new OpenMBeanAttributeInfoSupport(HC_LOG_ATTRIBUTE_NAME, 
"The health check result log", LOG_TABLE_TYPE, true, false, false));
+
+        return new MBeanInfo(this.getClass().getName(),
+                   "Health check",
+                   attrs.toArray(new MBeanAttributeInfo[attrs.size()]), null, 
null, null);
+    }
+
+    /**
+     * Create the default attributes.
+     */
+    private Map<String, Object> createDefaultAttributes(final ServiceReference 
serviceReference) {
+        final Map<String, Object> list = new HashMap<String, Object>();
+        if ( serviceReference.getProperty(HealthCheck.NAME) != null ) {
+            list.put(HealthCheck.NAME, 
serviceReference.getProperty(HealthCheck.NAME).toString());
+        }
+        if ( serviceReference.getProperty(HealthCheck.TAGS) != null ) {
+            final Object value = 
serviceReference.getProperty(HealthCheck.TAGS);
+            if ( value instanceof String[] ) {
+                list.put(HealthCheck.TAGS, Arrays.toString((String[])value));
+            } else {
+                list.put(HealthCheck.TAGS, value.toString());
+            }
+        }
+        return list;
+    }
 
-        return new MBeanInfo(this.getClass().getName(), beanName, 
attrs.toArray(new MBeanAttributeInfo[attrs.size()]), null, null, null);
+    @Override
+    public MBeanInfo getMBeanInfo() {
+        return this.mbeanInfo;
     }
 
     @Override
-    public Object invoke(String actionName, Object[] params, String[] 
signature)
+    public Object invoke(final String actionName, final Object[] params, final 
String[] signature)
             throws MBeanException, ReflectionException {
-        throw new UnsupportedOperationException(getClass().getSimpleName() + " 
does not support operations on Rules");
+        throw new MBeanException(new 
UnsupportedOperationException(getClass().getSimpleName() + " does not support 
operations."));
     }
 
     @Override
-    public void setAttribute(Attribute attribute)
-            throws AttributeNotFoundException, InvalidAttributeValueException,
+    public void setAttribute(final Attribute attribute)
+    throws AttributeNotFoundException, InvalidAttributeValueException,
             MBeanException, ReflectionException {
-        throw new UnsupportedOperationException(getClass().getSimpleName() + " 
does not support setting Rules attributes");
+        throw new MBeanException(new 
UnsupportedOperationException(getClass().getSimpleName() + " does not support 
setting attributes."));
     }
 
     @Override
-    public AttributeList setAttributes(AttributeList attributes) {
-        throw new UnsupportedOperationException(getClass().getSimpleName() + " 
does not support setting Rules attributes");
-    }
-
-    public String getName() {
-        return beanName;
+    public AttributeList setAttributes(final AttributeList attributes) {
+        return new AttributeList();
     }
 
-    public String getJmxTypeName() {
-        return jmxTypeName;
+    @Override
+    public String toString() {
+        return "HealthCheckMBean [healthCheck=" + healthCheck + "]";
     }
 }
\ No newline at end of file

Modified: 
sling/trunk/contrib/extensions/healthcheck/jmx/src/main/java/org/apache/sling/hc/jmx/impl/HealthCheckMBeanCreator.java
URL: 
http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/healthcheck/jmx/src/main/java/org/apache/sling/hc/jmx/impl/HealthCheckMBeanCreator.java?rev=1515349&r1=1515348&r2=1515349&view=diff
==============================================================================
--- 
sling/trunk/contrib/extensions/healthcheck/jmx/src/main/java/org/apache/sling/hc/jmx/impl/HealthCheckMBeanCreator.java
 (original)
+++ 
sling/trunk/contrib/extensions/healthcheck/jmx/src/main/java/org/apache/sling/hc/jmx/impl/HealthCheckMBeanCreator.java
 Mon Aug 19 10:46:00 2013
@@ -17,9 +17,12 @@
  */
 package org.apache.sling.hc.jmx.impl;
 
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Dictionary;
 import java.util.HashMap;
 import java.util.Hashtable;
+import java.util.List;
 import java.util.Map;
 
 import javax.management.DynamicMBean;
@@ -32,51 +35,47 @@ import org.osgi.framework.BundleContext;
 import org.osgi.framework.ServiceReference;
 import org.osgi.framework.ServiceRegistration;
 import org.osgi.util.tracker.ServiceTracker;
-import org.osgi.util.tracker.ServiceTrackerCustomizer;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /**
  * Creates an {@link HealthCheckMbean} for every {@link HealthCheckMBean} 
service
  *
- * TODO: What happens if two mbeans want to use the same object name (type and 
name).
- *       We need to handle this and maybe use service ranking to resolve the 
conflict
- * */
+ */
 @Component
 public class HealthCheckMBeanCreator {
 
-    private final Logger log = LoggerFactory.getLogger(getClass());
+    private final Logger logger = LoggerFactory.getLogger(getClass());
+
+    private final Map<ServiceReference, Registration> registeredServices = new 
HashMap<ServiceReference, Registration>();
 
-    private final Map<ServiceReference, ServiceRegistration> 
registeredServices = new HashMap<ServiceReference, ServiceRegistration>();
+    private final Map<String, List<ServiceReference>> sortedRegistrations = 
new HashMap<String, List<ServiceReference>>();
 
     private ServiceTracker hcTracker;
 
     @Activate
     protected void activate(final BundleContext btx) {
-        this.hcTracker = new ServiceTracker(btx, HealthCheck.class.getName(), 
new ServiceTrackerCustomizer() {
+        this.hcTracker = new ServiceTracker(btx, HealthCheck.class.getName(), 
null) {
 
             @Override
-            public synchronized void removedService(final ServiceReference 
reference, final Object service) {
-                btx.ungetService(reference);
-                unregisterHCMBean(reference);
+            public Object addingService(final ServiceReference reference) {
+                return registerHCMBean(btx, reference);
             }
 
             @Override
-            public synchronized void modifiedService(final ServiceReference 
reference, final Object service) {
-                unregisterHCMBean(reference);
-                registerHCMBean(btx, reference, (HealthCheck)service);
+            public void modifiedService(final ServiceReference reference,
+                    final Object service) {
+                unregisterHCMBean(btx, reference);
+                registerHCMBean(btx, reference);
             }
 
             @Override
-            public synchronized Object addingService(final ServiceReference 
reference) {
-                final HealthCheck hc = (HealthCheck) btx.getService(reference);
-
-                if ( hc != null ) {
-                    registerHCMBean(btx, reference, hc);
-                }
-                return hc;
+            public void removedService(final ServiceReference reference,
+                    final Object service) {
+                unregisterHCMBean(btx, reference);
             }
-        });
+
+        };
         this.hcTracker.open();
     }
 
@@ -88,22 +87,103 @@ public class HealthCheckMBeanCreator {
         }
     }
 
-    private void registerHCMBean(final BundleContext bundleContext, final 
ServiceReference ref, final HealthCheck hc) {
-        final HealthCheckMBean mbean = new HealthCheckMBean(ref, hc);
-
-        final Dictionary<String, String> mbeanProps = new Hashtable<String, 
String>();
-        mbeanProps.put("jmx.objectname", "org.apache.sling.healthcheck:type=" 
+ mbean.getJmxTypeName() + ",service=" + mbean.getName());
+    /**
+     * Register an mbean for a health check service.
+     * The mbean is only registered if
+     * - the service has an mbean registration property
+     * - if there is no other service with the same name but a higher service 
ranking
+     *
+     * @param bundleContext The bundle context
+     * @param reference     The service reference to the health check service
+     * @return The registered mbean or <code>null</code>
+     */
+    private synchronized Object registerHCMBean(final BundleContext 
bundleContext, final ServiceReference reference) {
+        final Registration reg = Registration.getRegistration(bundleContext, 
reference);
+        if ( reg != null ) {
+            this.registeredServices.put(reference, reg);
 
-        final ServiceRegistration reg = 
bundleContext.registerService(DynamicMBean.class.getName(), mbean, mbeanProps);
-        registeredServices.put(ref, reg);
-        log.debug("Registered {} with properties {}", mbean, mbeanProps);
+            List<ServiceReference> registered = 
this.sortedRegistrations.get(reg.name);
+            if ( registered == null ) {
+                registered = new ArrayList<ServiceReference>();
+                this.sortedRegistrations.put(reg.name, registered);
+            }
+            registered.add(reference);
+            Collections.sort(registered);
+            if ( registered.get(0).equals(reference) ) {
+                if ( registered.size() > 1 ) {
+                    final ServiceReference prevRef = registered.get(1);
+                    final Registration prevReg = 
this.registeredServices.get(prevRef);
+                    prevReg.unregister(this.logger);
+                }
+                reg.register(this.logger, bundleContext);
+            }
+        }
+        return reg;
     }
 
-    private void unregisterHCMBean(final ServiceReference ref) {
-        final ServiceRegistration reg = registeredServices.remove(ref);
+    private synchronized void unregisterHCMBean(final BundleContext 
bundleContext, final ServiceReference ref) {
+        final Registration reg = registeredServices.remove(ref);
         if ( reg != null ) {
-            reg.unregister();
-            log.debug("Ungegistered {}", ref);
+            final boolean registerFirst = reg.unregister(this.logger);
+            final List<ServiceReference> registered = 
this.sortedRegistrations.get(reg.name);
+            registered.remove(ref);
+            if ( registered.size() == 0 ) {
+                this.sortedRegistrations.remove(reg.name);
+            } else if ( registerFirst ) {
+                final ServiceReference newRef = registered.get(0);
+                final Registration newReg = 
this.registeredServices.get(newRef);
+                newReg.register(this.logger, bundleContext);
+            }
+            bundleContext.ungetService(ref);
+        }
+    }
+
+    private static final class Registration {
+        public final String name;
+        public final HealthCheckMBean mbean;
+        private ServiceRegistration registration;
+
+        public Registration(final String name, final HealthCheckMBean mbean) {
+            this.name = name;
+            this.mbean = mbean;
+        }
+
+        public static Registration getRegistration(final BundleContext 
bundleContext, final ServiceReference ref) {
+            final Object nameObj = ref.getProperty(HealthCheck.MBEAN_NAME);
+            if ( nameObj != null ) {
+                final HealthCheck service = (HealthCheck) 
bundleContext.getService(ref);
+                if ( service != null ) {
+                    final HealthCheckMBean mbean = new HealthCheckMBean(ref, 
service);
+
+                    return new Registration(nameObj.toString().replace(',', 
'.'), mbean);
+                }
+            }
+            return null;
+        }
+
+        public void register(final Logger logger, final BundleContext btx) {
+            final StringBuilder sb = new 
StringBuilder(HealthCheckMBean.JMX_DOMAIN);
+            sb.append(":type=");
+            sb.append(HealthCheckMBean.JMX_TYPE_NAME);
+            sb.append(",name=");
+            sb.append(this.name);
+            final String objectName = sb.toString();
+
+            final Dictionary<String, String> mbeanProps = new 
Hashtable<String, String>();
+            mbeanProps.put("jmx.objectname", objectName);
+            this.registration = 
btx.registerService(DynamicMBean.class.getName(), this.mbean, mbeanProps);
+
+            logger.debug("Registered health check mbean {} as {}", this.mbean, 
objectName);
+        }
+
+        public boolean unregister(final Logger logger) {
+            if ( this.registration != null ) {
+                this.registration.unregister();
+                this.registration = null;
+                logger.debug("Ungegistered health check mbean {}", this.mbean);
+                return true;
+            }
+            return false;
         }
     }
 }
\ No newline at end of file

Modified: 
sling/trunk/contrib/extensions/healthcheck/jmx/src/test/java/org/apache/sling/hc/jmx/impl/HealthCheckMBeanTest.java
URL: 
http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/healthcheck/jmx/src/test/java/org/apache/sling/hc/jmx/impl/HealthCheckMBeanTest.java?rev=1515349&r1=1515348&r2=1515349&view=diff
==============================================================================
--- 
sling/trunk/contrib/extensions/healthcheck/jmx/src/test/java/org/apache/sling/hc/jmx/impl/HealthCheckMBeanTest.java
 (original)
+++ 
sling/trunk/contrib/extensions/healthcheck/jmx/src/test/java/org/apache/sling/hc/jmx/impl/HealthCheckMBeanTest.java
 Mon Aug 19 10:46:00 2013
@@ -29,7 +29,6 @@ import org.apache.sling.hc.api.HealthChe
 import org.apache.sling.hc.api.Result;
 import org.apache.sling.hc.api.ResultLog;
 import org.apache.sling.hc.healthchecks.util.SimpleConstraintChecker;
-import org.apache.sling.hc.jmx.impl.HealthCheckMBean;
 import org.junit.Test;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.ServiceReference;
@@ -60,7 +59,7 @@ public class HealthCheckMBeanTest {
         final Object value = jmxServer.getAttribute(objectName, attributeName);
         final ResultLog resultLog = new ResultLog();
         new SimpleConstraintChecker().check(value, constraint, resultLog);
-        assertEquals("Expecting result " + expected, expected, 
resultLog.getAggregateStatus().equals(Result.Status.OK));
+        assertEquals("Expecting result " + expected + "(" + resultLog + ")", 
expected, resultLog.getAggregateStatus().equals(Result.Status.OK));
 
     }
 


Reply via email to