Java tutorial
/** * Copyright 2013 the original author or authors. * * Licensed 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 io.neba.core.selftests; import io.neba.api.annotations.SelfTest; import io.neba.core.blueprint.EventhandlingBarrier; import org.eclipse.gemini.blueprint.service.importer.ImportedOsgiServiceProxy; import org.osgi.framework.Bundle; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.MethodMetadata; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.util.ClassUtils; import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils.MethodCallback; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import static io.neba.core.util.BundleUtil.displayNameOf; import static org.springframework.beans.factory.BeanFactoryUtils.isFactoryDereference; /** * Detects beans that have methods annotated with {@link SelfTest}. <br /> * Considers all beans defined in an application context unless they are OSGi * references, i.e. from a foreign bundle. * * @author Olaf Otto */ @Service public class SelftestRegistrar { private static final long EVERY_30_SECONDS = 30 * 1000; private final Collection<SelftestReference> selftestReferences = new LinkedHashSet<SelftestReference>(); private final String selftestAnnotationName = SelfTest.class.getName(); private final Logger logger = LoggerFactory.getLogger(getClass()); public void registerSelftests(ConfigurableListableBeanFactory factory, Bundle bundle) { String[] beanNames = BeanFactoryUtils.beanNamesIncludingAncestors(factory); for (String beanName : beanNames) { if (factory.containsBeanDefinition(beanName) && !isInternal(beanName)) { findSelftests(factory, beanName, bundle); } } } public List<SelftestReference> getSelftestReferences() { return new ArrayList<SelftestReference>(this.selftestReferences); } @Scheduled(fixedRate = EVERY_30_SECONDS) public void removeInvalidReferences() { if (EventhandlingBarrier.tryBegin()) { try { this.logger.debug("Checking for references to beans from inactive bundles..."); for (Iterator<SelftestReference> it = this.selftestReferences.iterator(); it.hasNext();) { final SelftestReference reference = it.next(); if (!reference.isValid()) { this.logger.info("Reference to " + reference + " is invalid, removing."); it.remove(); } } this.logger.debug("Completed checking for references to beans from inactive bundles."); } finally { EventhandlingBarrier.end(); } } } private void findSelftests(final ConfigurableListableBeanFactory factory, String beanName, Bundle bundle) { BeanDefinition definition = factory.getBeanDefinition(beanName); if (isOsgiServiceReference(factory, beanName)) { this.logger.info("Skipping bean " + beanName + " from bundle " + displayNameOf(bundle) + ", it is an osgi service reference."); } else if (definition instanceof AnnotatedBeanDefinition) { findSelftestUsingBeanDefinition(factory, beanName, bundle, definition); } else { findSelftestUsingReflection(factory, beanName, bundle); } } /** * A bean may be the representation of an OSGi service provided by a * different bundle - in this case we must not check it for selftests, as * this is done in the service's source bundle. */ private boolean isOsgiServiceReference(BeanFactory factory, String beanName) { Class<?> beanType = factory.getType(beanName); return beanType != null && ImportedOsgiServiceProxy.class.isAssignableFrom(beanType); } /** * If a bean was detected by classpath scanning, i.e. is annotated (e.g. * with {@link org.springframework.stereotype.Component}), the bean * definition already contains metadata for all bean annotations. It is thus * more efficient to use this metadata than using reflection. */ private void findSelftestUsingBeanDefinition(ConfigurableListableBeanFactory factory, String beanName, Bundle bundle, BeanDefinition definition) { AnnotatedBeanDefinition annotatedBeanDefinition = (AnnotatedBeanDefinition) definition; AnnotationMetadata metadata = annotatedBeanDefinition.getMetadata(); if (isSelftestingBean(metadata)) { for (MethodMetadata selftestMethodMetadata : getSelfTestMethods(metadata)) { this.selftestReferences .add(new SelftestReference(factory, beanName, selftestMethodMetadata, bundle)); } } } /** * In case no annotation metadata exists, find selftests by checking all * methods for the {@link SelfTest} annotation. */ private void findSelftestUsingReflection(final ConfigurableListableBeanFactory factory, final String beanName, final Bundle bundle) { Class<?> beanType = factory.getType(beanName); if (beanType != null) { beanType = unproxy(beanType); ReflectionUtils.doWithMethods(beanType, new MethodCallback() { @Override public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException { SelfTest selfTest = AnnotationUtils.findAnnotation(method, SelfTest.class); if (selfTest != null) { String methodName = method.getName(); selftestReferences .add(new SelftestReference(factory, beanName, selfTest, methodName, bundle)); } } }); } } /** * Certain prefixes mark bean definitions as "internal", i.e. definitions of * factory-internal service beans or automatically generated infrastructure * bean definitions. */ private boolean isInternal(String beanName) { return isFactoryDereference(beanName) || beanName.startsWith("scopedTarget."); } /** * Since proxies may implement a type's signature but not include a type's * annotations, we need to unproxy types before scanning for annotations. */ private Class<?> unproxy(Class<?> beanType) { Class<?> unproxiedType = beanType; if (ClassUtils.isCglibProxyClass(beanType)) { // It is a dynamic subclass re-implementing the same methods. unproxiedType = beanType.getSuperclass(); } return unproxiedType; } private Set<MethodMetadata> getSelfTestMethods(AnnotationMetadata metadata) { return metadata.getAnnotatedMethods(this.selftestAnnotationName); } private boolean isSelftestingBean(AnnotationMetadata metadata) { return metadata.hasAnnotatedMethods(this.selftestAnnotationName); } public void unregister(Bundle bundle) { removeSelftests(bundle); } private synchronized void removeSelftests(Bundle bundle) { this.logger.info("Removing bundle " + displayNameOf(bundle) + " from the selftest registry..."); Iterator<SelftestReference> i = this.selftestReferences.iterator(); while (i.hasNext()) { if (i.next().getBundleId() == bundle.getBundleId()) { i.remove(); } } this.logger.info("Bundle " + displayNameOf(bundle) + " was removed from the selftest registry."); } }