Repository: camel
Updated Branches:
  refs/heads/master 477c9d794 -> dd8ea2f68


CAMEL-8147: Camel JMX when using custom @ManagedResource should mixin the 
standard mbean operations/attributes so you get both worlds.


Project: http://git-wip-us.apache.org/repos/asf/camel/repo
Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/aa1fba8f
Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/aa1fba8f
Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/aa1fba8f

Branch: refs/heads/master
Commit: aa1fba8f4dc2ecea9569792ac3d7b56a23b969c1
Parents: 477c9d7
Author: Claus Ibsen <[email protected]>
Authored: Tue Aug 11 12:45:21 2015 +0200
Committer: Claus Ibsen <[email protected]>
Committed: Tue Aug 11 15:30:39 2015 +0200

----------------------------------------------------------------------
 .../management/CustomRequiredModelMBean.java    |  76 ++++++++++++
 .../DefaultManagementMBeanAssembler.java        |  35 ++++--
 .../management/MaskRequiredModelMBean.java      |   1 +
 .../management/MixinRequiredModelMBean.java     | 117 +++++++++++++++++++
 .../management/ManagedCustomProcessorTest.java  |  16 ++-
 .../camel/management/ManagedResourceTest.java   |  15 +--
 6 files changed, 242 insertions(+), 18 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/camel/blob/aa1fba8f/camel-core/src/main/java/org/apache/camel/management/CustomRequiredModelMBean.java
----------------------------------------------------------------------
diff --git 
a/camel-core/src/main/java/org/apache/camel/management/CustomRequiredModelMBean.java
 
b/camel-core/src/main/java/org/apache/camel/management/CustomRequiredModelMBean.java
new file mode 100644
index 0000000..c8efe97
--- /dev/null
+++ 
b/camel-core/src/main/java/org/apache/camel/management/CustomRequiredModelMBean.java
@@ -0,0 +1,76 @@
+/**
+ * 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.camel.management;
+
+import javax.management.DynamicMBean;
+import javax.management.MBeanException;
+import javax.management.MBeanOperationInfo;
+import javax.management.ReflectionException;
+import javax.management.RuntimeOperationsException;
+import javax.management.modelmbean.ModelMBeanInfo;
+import javax.management.modelmbean.RequiredModelMBean;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A {@link javax.management.modelmbean.RequiredModelMBean} which allows us to 
intercept invoking operations on the MBean.
+ * <p/>
+ * This allows us to intercept calls to custom mbeans where allows us to 
mix-in the standard set of mbean attributes
+ * and operations that Camel provides out of the box.
+ */
+@Deprecated
+public class CustomRequiredModelMBean extends RequiredModelMBean {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(CustomRequiredModelMBean.class);
+    private ModelMBeanInfo defaultMbi;
+    private DynamicMBean defaultObject;
+
+    public CustomRequiredModelMBean() throws MBeanException, 
RuntimeOperationsException {
+        // must have default no-arg constructor
+    }
+
+    public CustomRequiredModelMBean(ModelMBeanInfo mbi, ModelMBeanInfo 
defaultMbi, DynamicMBean defaultObject) throws MBeanException, 
RuntimeOperationsException {
+        super(mbi);
+        this.defaultMbi = defaultMbi;
+        this.defaultObject = defaultObject;
+    }
+
+    @Override
+    public Object invoke(String opName, Object[] opArgs, String[] sig) throws 
MBeanException, ReflectionException {
+        Object answer;
+        if (isDefaultOperation(opName)) {
+            answer = defaultObject.invoke(opName, opArgs, sig);
+        } else {
+            answer = super.invoke(opName, opArgs, sig);
+        }
+        return answer;
+    }
+
+    protected boolean isDefaultOperation(String opName) {
+        if (defaultMbi == null || defaultObject == null) {
+            return false;
+        }
+        for (MBeanOperationInfo info : defaultMbi.getOperations()) {
+            if (info.getName().equals(opName)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/aa1fba8f/camel-core/src/main/java/org/apache/camel/management/DefaultManagementMBeanAssembler.java
----------------------------------------------------------------------
diff --git 
a/camel-core/src/main/java/org/apache/camel/management/DefaultManagementMBeanAssembler.java
 
b/camel-core/src/main/java/org/apache/camel/management/DefaultManagementMBeanAssembler.java
index abc8d44..a28bcbb 100644
--- 
a/camel-core/src/main/java/org/apache/camel/management/DefaultManagementMBeanAssembler.java
+++ 
b/camel-core/src/main/java/org/apache/camel/management/DefaultManagementMBeanAssembler.java
@@ -52,17 +52,18 @@ public class DefaultManagementMBeanAssembler implements 
ManagementMBeanAssembler
 
     public ModelMBean assemble(MBeanServer mBeanServer, Object obj, ObjectName 
name) throws JMException {
         ModelMBeanInfo mbi = null;
+        ModelMBeanInfo standardMbi = null;
+        Object custom = null;
 
         // prefer to use the managed instance if it has been annotated with 
JMX annotations
         if (obj instanceof ManagedInstance) {
             // there may be a custom embedded instance which have additional 
methods
-            Object custom = ((ManagedInstance) obj).getInstance();
+            custom = ((ManagedInstance) obj).getInstance();
             if (custom != null && 
ObjectHelper.hasAnnotation(custom.getClass().getAnnotations(), 
ManagedResource.class)) {
                 LOG.trace("Assembling MBeanInfo for: {} from custom 
@ManagedResource object: {}", name, custom);
-                // get the mbean info from the custom managed object
-                mbi = assembler.getMBeanInfo(null, custom, name.toString());
-                // and let the custom object be registered in JMX
-                obj = custom;
+                // get the mbean info into different groups (mbi = both, 
standard = standard out of the box mbi)
+                mbi = assembler.getMBeanInfo(obj, custom, name.toString());
+                standardMbi = assembler.getMBeanInfo(obj, null, 
name.toString());
             }
         }
 
@@ -77,14 +78,28 @@ public class DefaultManagementMBeanAssembler implements 
ManagementMBeanAssembler
         }
 
         RequiredModelMBean mbean;
+        RequiredModelMBean mixinMBean = null;
+
         boolean sanitize = 
camelContext.getManagementStrategy().getManagementAgent().getMask() != null && 
camelContext.getManagementStrategy().getManagementAgent().getMask();
-        if (sanitize) {
-            mbean = new MaskRequiredModelMBean(mbi, sanitize);
-        } else {
-            mbean = (RequiredModelMBean) 
mBeanServer.instantiate(RequiredModelMBean.class.getName());
-            mbean.setModelMBeanInfo(mbi);
+
+        // if we have a custom mbean then create a mixin mbean for the 
standard mbean which we would
+        // otherwise have created that contains the out of the box attributes 
and operations
+        // as we want a combined mbean that has both the custom and the 
standard
+        if (standardMbi != null) {
+            mixinMBean = (RequiredModelMBean) 
mBeanServer.instantiate(RequiredModelMBean.class.getName());
+            mixinMBean.setModelMBeanInfo(standardMbi);
+            try {
+                mixinMBean.setManagedResource(obj, "ObjectReference");
+            } catch (InvalidTargetObjectTypeException e) {
+                throw new JMException(e.getMessage());
+            }
+            // use custom as the object to call
+            obj = custom;
         }
 
+        // use a mixin mbean model to combine the custom and standard (custom 
is optional)
+        mbean = new MixinRequiredModelMBean(mbi, sanitize, standardMbi, 
mixinMBean);
+
         try {
             mbean.setManagedResource(obj, "ObjectReference");
         } catch (InvalidTargetObjectTypeException e) {

http://git-wip-us.apache.org/repos/asf/camel/blob/aa1fba8f/camel-core/src/main/java/org/apache/camel/management/MaskRequiredModelMBean.java
----------------------------------------------------------------------
diff --git 
a/camel-core/src/main/java/org/apache/camel/management/MaskRequiredModelMBean.java
 
b/camel-core/src/main/java/org/apache/camel/management/MaskRequiredModelMBean.java
index 697ed3e..50966e3 100644
--- 
a/camel-core/src/main/java/org/apache/camel/management/MaskRequiredModelMBean.java
+++ 
b/camel-core/src/main/java/org/apache/camel/management/MaskRequiredModelMBean.java
@@ -35,6 +35,7 @@ import org.slf4j.LoggerFactory;
  * For example if mask has been enabled on JMX, then we use this implementation
  * to hide sensitive information from the returned JMX attributes / operations.
  */
+@Deprecated
 public class MaskRequiredModelMBean extends RequiredModelMBean {
 
     private static final Logger LOG = 
LoggerFactory.getLogger(MaskRequiredModelMBean.class);

http://git-wip-us.apache.org/repos/asf/camel/blob/aa1fba8f/camel-core/src/main/java/org/apache/camel/management/MixinRequiredModelMBean.java
----------------------------------------------------------------------
diff --git 
a/camel-core/src/main/java/org/apache/camel/management/MixinRequiredModelMBean.java
 
b/camel-core/src/main/java/org/apache/camel/management/MixinRequiredModelMBean.java
new file mode 100644
index 0000000..b949d0e
--- /dev/null
+++ 
b/camel-core/src/main/java/org/apache/camel/management/MixinRequiredModelMBean.java
@@ -0,0 +1,117 @@
+/**
+ * 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.camel.management;
+
+import javax.management.Descriptor;
+import javax.management.DynamicMBean;
+import javax.management.MBeanException;
+import javax.management.MBeanOperationInfo;
+import javax.management.ReflectionException;
+import javax.management.RuntimeOperationsException;
+import javax.management.modelmbean.ModelMBeanInfo;
+import javax.management.modelmbean.RequiredModelMBean;
+
+import org.apache.camel.util.ObjectHelper;
+import org.apache.camel.util.URISupport;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A {@link javax.management.modelmbean.RequiredModelMBean} which allows us to 
intercept invoking operations on the MBean.
+ * <p/>
+ * This allows us to intercept calls to custom mbeans where allows us to 
mix-in the standard set of mbean attributes
+ * and operations that Camel provides out of the box.
+ * <p/>
+ * For example if mask has been enabled on JMX, then we use this implementation
+ * to hide sensitive information from the returned JMX attributes / operations.
+ */
+public class MixinRequiredModelMBean extends RequiredModelMBean {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(MixinRequiredModelMBean.class);
+    private boolean mask;
+    private ModelMBeanInfo defaultMbi;
+    private DynamicMBean defaultObject;
+
+    public MixinRequiredModelMBean() throws MBeanException, 
RuntimeOperationsException {
+        // must have default no-arg constructor
+    }
+
+    public MixinRequiredModelMBean(ModelMBeanInfo mbi, boolean mask, 
ModelMBeanInfo defaultMbi, DynamicMBean defaultObject) throws MBeanException, 
RuntimeOperationsException {
+        super(mbi);
+        this.mask = mask;
+        this.defaultMbi = defaultMbi;
+        this.defaultObject = defaultObject;
+    }
+
+    public boolean isMask() {
+        return mask;
+    }
+
+    @Override
+    public Object invoke(String opName, Object[] opArgs, String[] sig) throws 
MBeanException, ReflectionException {
+        Object answer;
+        if (defaultMbi != null && defaultObject != null && 
isDefaultOperation(opName)) {
+            answer = defaultObject.invoke(opName, opArgs, sig);
+        } else {
+            answer = super.invoke(opName, opArgs, sig);
+        }
+        // mask the answer if enabled and it was a String type (we cannot mask 
other types)
+        if (mask && answer instanceof String && 
ObjectHelper.isNotEmpty(answer) && isMaskOperation(opName)) {
+            answer = mask(opName, (String) answer);
+        }
+        return answer;
+    }
+
+    protected boolean isDefaultOperation(String opName) {
+        for (MBeanOperationInfo info : defaultMbi.getOperations()) {
+            if (info.getName().equals(opName)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    protected boolean isMaskOperation(String opName) {
+        for (MBeanOperationInfo info : getMBeanInfo().getOperations()) {
+            if (info.getName().equals(opName)) {
+                Descriptor desc = info.getDescriptor();
+                if (desc != null) {
+                    Object val = desc.getFieldValue("mask");
+                    return val != null && "true".equals(val);
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Masks the returned value from invoking the operation
+     *
+     * @param opName  the operation name invoked
+     * @param value   the current value
+     * @return the masked value
+     */
+    protected String mask(String opName, String value) {
+        // use sanitize uri which will mask sensitive information
+        String answer = URISupport.sanitizeUri(value);
+        if (LOG.isTraceEnabled()) {
+            LOG.trace("Masking JMX operation: {}.{} value: {} -> {}",
+                    new Object[]{getMBeanInfo().getClassName(), opName, value, 
answer});
+        }
+        return answer;
+    }
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/aa1fba8f/camel-core/src/test/java/org/apache/camel/management/ManagedCustomProcessorTest.java
----------------------------------------------------------------------
diff --git 
a/camel-core/src/test/java/org/apache/camel/management/ManagedCustomProcessorTest.java
 
b/camel-core/src/test/java/org/apache/camel/management/ManagedCustomProcessorTest.java
index 4d6138e..18e5209 100644
--- 
a/camel-core/src/test/java/org/apache/camel/management/ManagedCustomProcessorTest.java
+++ 
b/camel-core/src/test/java/org/apache/camel/management/ManagedCustomProcessorTest.java
@@ -25,6 +25,7 @@ import org.apache.camel.Processor;
 import org.apache.camel.api.management.ManagedAttribute;
 import org.apache.camel.api.management.ManagedResource;
 import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.support.ServiceSupport;
 
 /**
  * @version 
@@ -57,6 +58,9 @@ public class ManagedCustomProcessorTest extends 
ManagementTestSupport {
         getMockEndpoint("mock:result").expectedHeaderReceived("foo", 
"changed");
         template.sendBody("direct:start", "Bye World");
         assertMockEndpointsSatisfied();
+
+        String state = (String) mbeanServer.getAttribute(on, "State");
+        assertEquals("Started", state);
     }
 
     @Override
@@ -73,7 +77,7 @@ public class ManagedCustomProcessorTest extends 
ManagementTestSupport {
 
     // START SNIPPET: e1
     @ManagedResource(description = "My Managed Component")
-    public static class MyCustomProcessor implements Processor {
+    public static class MyCustomProcessor extends ServiceSupport implements 
Processor {
         private String foo = "hey";
 
         @ManagedAttribute
@@ -89,6 +93,16 @@ public class ManagedCustomProcessorTest extends 
ManagementTestSupport {
         public void process(Exchange exchange) throws Exception {
             exchange.getIn().setHeader("foo", getFoo());
         }
+
+        @Override
+        protected void doStart() throws Exception {
+            // noop
+        }
+
+        @Override
+        protected void doStop() throws Exception {
+            // noop
+        }
     }
     // END SNIPPET: e1
 

http://git-wip-us.apache.org/repos/asf/camel/blob/aa1fba8f/camel-core/src/test/java/org/apache/camel/management/ManagedResourceTest.java
----------------------------------------------------------------------
diff --git 
a/camel-core/src/test/java/org/apache/camel/management/ManagedResourceTest.java 
b/camel-core/src/test/java/org/apache/camel/management/ManagedResourceTest.java
index ce4de16..41e40fe 100644
--- 
a/camel-core/src/test/java/org/apache/camel/management/ManagedResourceTest.java
+++ 
b/camel-core/src/test/java/org/apache/camel/management/ManagedResourceTest.java
@@ -16,7 +16,6 @@
  */
 package org.apache.camel.management;
 
-import javax.management.AttributeNotFoundException;
 import javax.management.MBeanServer;
 import javax.management.ObjectName;
 
@@ -87,11 +86,13 @@ public class ManagedResourceTest extends 
ManagementTestSupport {
         camelsSeenCount = (Integer) mBeanServer.getAttribute(onManagedBean, 
"CamelsSeenCount");
         TestCase.assertEquals(0, camelsSeenCount);
 
-        try {
-            mBeanServer.getAttribute(onManagedBean, "CamelId");
-            fail("The CamelId attribute should not exist");
-        } catch (AttributeNotFoundException e) {
-            // expected
-        }
+        String camelId = (String) mBeanServer.getAttribute(onManagedBean, 
"CamelId");
+        assertEquals(context.getName(), camelId);
+
+        String state = (String) mBeanServer.getAttribute(onManagedBean, 
"State");
+        assertEquals("Started", state);
+
+        String fqn = (String) mBeanServer.getAttribute(onManagedBean, 
"BeanClassName");
+        assertEquals(MyManagedBean.class.getCanonicalName(), fqn);
     }
 }

Reply via email to