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); } }
