001/* 002 * Copyright 2002-2019 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.jmx.export.annotation; 018 019import java.lang.annotation.Annotation; 020import java.lang.reflect.AnnotatedElement; 021import java.lang.reflect.Array; 022import java.lang.reflect.Method; 023import java.lang.reflect.Modifier; 024import java.util.ArrayList; 025import java.util.List; 026import java.util.Map; 027import java.util.stream.Collectors; 028 029import org.springframework.beans.BeanUtils; 030import org.springframework.beans.BeanWrapper; 031import org.springframework.beans.MutablePropertyValues; 032import org.springframework.beans.PropertyAccessorFactory; 033import org.springframework.beans.PropertyValue; 034import org.springframework.beans.factory.BeanFactory; 035import org.springframework.beans.factory.BeanFactoryAware; 036import org.springframework.beans.factory.config.ConfigurableBeanFactory; 037import org.springframework.beans.factory.config.EmbeddedValueResolver; 038import org.springframework.core.annotation.MergedAnnotation; 039import org.springframework.core.annotation.MergedAnnotationPredicates; 040import org.springframework.core.annotation.MergedAnnotations; 041import org.springframework.core.annotation.MergedAnnotations.SearchStrategy; 042import org.springframework.core.annotation.RepeatableContainers; 043import org.springframework.jmx.export.metadata.InvalidMetadataException; 044import org.springframework.jmx.export.metadata.JmxAttributeSource; 045import org.springframework.lang.Nullable; 046import org.springframework.util.StringValueResolver; 047 048/** 049 * Implementation of the {@code JmxAttributeSource} interface that 050 * reads annotations and exposes the corresponding attributes. 051 * 052 * @author Rob Harrop 053 * @author Juergen Hoeller 054 * @author Jennifer Hickey 055 * @author Stephane Nicoll 056 * @since 1.2 057 * @see ManagedResource 058 * @see ManagedAttribute 059 * @see ManagedOperation 060 */ 061public class AnnotationJmxAttributeSource implements JmxAttributeSource, BeanFactoryAware { 062 063 @Nullable 064 private StringValueResolver embeddedValueResolver; 065 066 067 @Override 068 public void setBeanFactory(BeanFactory beanFactory) { 069 if (beanFactory instanceof ConfigurableBeanFactory) { 070 this.embeddedValueResolver = new EmbeddedValueResolver((ConfigurableBeanFactory) beanFactory); 071 } 072 } 073 074 075 @Override 076 @Nullable 077 public org.springframework.jmx.export.metadata.ManagedResource getManagedResource(Class<?> beanClass) throws InvalidMetadataException { 078 MergedAnnotation<ManagedResource> ann = MergedAnnotations.from(beanClass, SearchStrategy.TYPE_HIERARCHY) 079 .get(ManagedResource.class).withNonMergedAttributes(); 080 if (!ann.isPresent()) { 081 return null; 082 } 083 Class<?> declaringClass = (Class<?>) ann.getSource(); 084 Class<?> target = (declaringClass != null && !declaringClass.isInterface() ? declaringClass : beanClass); 085 if (!Modifier.isPublic(target.getModifiers())) { 086 throw new InvalidMetadataException("@ManagedResource class '" + target.getName() + "' must be public"); 087 } 088 089 org.springframework.jmx.export.metadata.ManagedResource bean = new org.springframework.jmx.export.metadata.ManagedResource(); 090 Map<String, Object> map = ann.asMap(); 091 List<PropertyValue> list = new ArrayList<>(map.size()); 092 map.forEach((attrName, attrValue) -> { 093 if (!"value".equals(attrName)) { 094 Object value = attrValue; 095 if (this.embeddedValueResolver != null && value instanceof String) { 096 value = this.embeddedValueResolver.resolveStringValue((String) value); 097 } 098 list.add(new PropertyValue(attrName, value)); 099 } 100 }); 101 PropertyAccessorFactory.forBeanPropertyAccess(bean).setPropertyValues(new MutablePropertyValues(list)); 102 return bean; 103 } 104 105 @Override 106 @Nullable 107 public org.springframework.jmx.export.metadata.ManagedAttribute getManagedAttribute(Method method) throws InvalidMetadataException { 108 MergedAnnotation<ManagedAttribute> ann = MergedAnnotations.from(method, SearchStrategy.TYPE_HIERARCHY) 109 .get(ManagedAttribute.class).withNonMergedAttributes(); 110 if (!ann.isPresent()) { 111 return null; 112 } 113 114 org.springframework.jmx.export.metadata.ManagedAttribute bean = new org.springframework.jmx.export.metadata.ManagedAttribute(); 115 Map<String, Object> map = ann.asMap(); 116 MutablePropertyValues pvs = new MutablePropertyValues(map); 117 pvs.removePropertyValue("defaultValue"); 118 PropertyAccessorFactory.forBeanPropertyAccess(bean).setPropertyValues(pvs); 119 String defaultValue = (String) map.get("defaultValue"); 120 if (defaultValue.length() > 0) { 121 bean.setDefaultValue(defaultValue); 122 } 123 return bean; 124 } 125 126 @Override 127 @Nullable 128 public org.springframework.jmx.export.metadata.ManagedMetric getManagedMetric(Method method) throws InvalidMetadataException { 129 MergedAnnotation<ManagedMetric> ann = MergedAnnotations.from(method, SearchStrategy.TYPE_HIERARCHY) 130 .get(ManagedMetric.class).withNonMergedAttributes(); 131 132 return copyPropertiesToBean(ann, org.springframework.jmx.export.metadata.ManagedMetric.class); 133 } 134 135 @Override 136 @Nullable 137 public org.springframework.jmx.export.metadata.ManagedOperation getManagedOperation(Method method) throws InvalidMetadataException { 138 MergedAnnotation<ManagedOperation> ann = MergedAnnotations.from(method, SearchStrategy.TYPE_HIERARCHY) 139 .get(ManagedOperation.class).withNonMergedAttributes(); 140 141 return copyPropertiesToBean(ann, org.springframework.jmx.export.metadata.ManagedOperation.class); 142 } 143 144 @Override 145 public org.springframework.jmx.export.metadata.ManagedOperationParameter[] getManagedOperationParameters(Method method) 146 throws InvalidMetadataException { 147 148 List<MergedAnnotation<? extends Annotation>> anns = getRepeatableAnnotations( 149 method, ManagedOperationParameter.class, ManagedOperationParameters.class); 150 151 return copyPropertiesToBeanArray(anns, org.springframework.jmx.export.metadata.ManagedOperationParameter.class); 152 } 153 154 @Override 155 public org.springframework.jmx.export.metadata.ManagedNotification[] getManagedNotifications(Class<?> clazz) 156 throws InvalidMetadataException { 157 158 List<MergedAnnotation<? extends Annotation>> anns = getRepeatableAnnotations( 159 clazz, ManagedNotification.class, ManagedNotifications.class); 160 161 return copyPropertiesToBeanArray(anns, org.springframework.jmx.export.metadata.ManagedNotification.class); 162 } 163 164 165 private static List<MergedAnnotation<? extends Annotation>> getRepeatableAnnotations( 166 AnnotatedElement annotatedElement, Class<? extends Annotation> annotationType, 167 Class<? extends Annotation> containerAnnotationType) { 168 169 return MergedAnnotations.from(annotatedElement, SearchStrategy.TYPE_HIERARCHY, 170 RepeatableContainers.of(annotationType, containerAnnotationType)) 171 .stream(annotationType) 172 .filter(MergedAnnotationPredicates.firstRunOf(MergedAnnotation::getAggregateIndex)) 173 .map(MergedAnnotation::withNonMergedAttributes) 174 .collect(Collectors.toList()); 175 } 176 177 @SuppressWarnings("unchecked") 178 private static <T> T[] copyPropertiesToBeanArray( 179 List<MergedAnnotation<? extends Annotation>> anns, Class<T> beanClass) { 180 181 T[] beans = (T[]) Array.newInstance(beanClass, anns.size()); 182 int i = 0; 183 for (MergedAnnotation<? extends Annotation> ann : anns) { 184 beans[i++] = copyPropertiesToBean(ann, beanClass); 185 } 186 return beans; 187 } 188 189 @Nullable 190 private static <T> T copyPropertiesToBean(MergedAnnotation<? extends Annotation> ann, Class<T> beanClass) { 191 if (!ann.isPresent()) { 192 return null; 193 } 194 T bean = BeanUtils.instantiateClass(beanClass); 195 BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(bean); 196 bw.setPropertyValues(new MutablePropertyValues(ann.asMap())); 197 return bean; 198 } 199 200}