/** * AnalyzerBeans * Copyright (C) 2014 Neopost - Customer Information Management * * This copyrighted material is made available to anyone wishing to use, modify, * copy, or redistribute it subject to the terms and conditions of the GNU * Lesser General Public License, as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this distribution; if not, write to: * Free Software Foundation, Inc. * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ package org.eobjects.analyzer.descriptors; import java.io.Closeable; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Set; import java.util.TreeSet; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.inject.Inject; import org.apache.commons.lang.ArrayUtils; import org.eobjects.analyzer.beans.api.Categorized; import org.eobjects.analyzer.beans.api.Close; import org.eobjects.analyzer.beans.api.ComponentCategory; import org.eobjects.analyzer.beans.api.Configured; import org.eobjects.analyzer.beans.api.Description; import org.eobjects.analyzer.beans.api.Initialize; import org.eobjects.analyzer.beans.api.Provided; import org.eobjects.analyzer.beans.api.Validate; import org.eobjects.analyzer.util.ReflectionUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * A descriptor for simple components. Simple components covers reference data * types (Dictionary, SynonymCatalog, StringPattern) as well as custom * configuration components. * * Simple components support the @Configured, @Validate, @Initialize and @Close * annotations as well as the Closeable interface. * * @see Initialize * @see Validate * @see Close * @see Configured * @see Closeable * * */ class SimpleComponentDescriptor<B> extends AbstractDescriptor<B> implements ComponentDescriptor<B> { private static final long serialVersionUID = 1L; private static final Logger logger = LoggerFactory.getLogger(SimpleComponentDescriptor.class); protected final Set<ConfiguredPropertyDescriptor> _configuredProperties;; protected final Set<ProvidedPropertyDescriptor> _providedProperties; protected final Set<InitializeMethodDescriptor> _initializeMethods; protected final Set<ValidateMethodDescriptor> _validateMethods; protected final Set<CloseMethodDescriptor> _closeMethods; /** * Constructor for inheriting from SimpleComponentDescriptor * * @param beanClass */ public SimpleComponentDescriptor(Class<B> beanClass) { this(beanClass, false); } public SimpleComponentDescriptor(final Class<B> beanClass, final boolean initialize) { super(beanClass); _configuredProperties = new TreeSet<ConfiguredPropertyDescriptor>(); _providedProperties = new TreeSet<ProvidedPropertyDescriptor>(); _validateMethods = new HashSet<ValidateMethodDescriptor>(); _initializeMethods = new HashSet<InitializeMethodDescriptor>(); _closeMethods = new HashSet<CloseMethodDescriptor>(); if (initialize) { visitClass(); } } @Override public String getDisplayName() { return getComponentClass().getSimpleName(); } @Override public String getDescription() { Description description = getAnnotation(Description.class); if (description == null) { return null; } return description.value(); } @Override public <A extends Annotation> A getAnnotation(Class<A> annotationClass) { return ReflectionUtils.getAnnotation(getComponentClass(), annotationClass); } @Override public Set<Annotation> getAnnotations() { Annotation[] annotations = getComponentClass().getAnnotations(); return new HashSet<Annotation>(Arrays.asList(annotations)); } @Override public Set<ComponentCategory> getComponentCategories() { Categorized categorized = getAnnotation(Categorized.class); if (categorized == null) { return Collections.emptySet(); } Class<? extends ComponentCategory>[] value = categorized.value(); if (value == null || value.length == 0) { return Collections.emptySet(); } Set<ComponentCategory> result = new HashSet<ComponentCategory>(); for (Class<? extends ComponentCategory> categoryClass : value) { ComponentCategory category = ReflectionUtils.newInstance(categoryClass); result.add(category); } return result; } @Override public B newInstance() { try { return getComponentClass().newInstance(); } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new IllegalStateException("Could not construct new instance of " + getComponentClass(), e); } } @Override protected void visitClass() { super.visitClass(); if (ReflectionUtils.isCloseable(getComponentClass())) { try { Method method = getComponentClass().getMethod("close", new Class<?>[0]); CloseMethodDescriptorImpl cmd = new CloseMethodDescriptorImpl(method, this); _closeMethods.add(cmd); } catch (Exception e) { // This should be impossible since all closeable's have a no-arg // close() method logger.error("Unexpected exception while getting close() method from Closeable", e); assert false; } } } @Override protected void visitField(Field field) { final boolean isInject = ReflectionUtils.isAnnotationPresent(field, Inject.class); final boolean isConfigured = ReflectionUtils.isAnnotationPresent(field, Configured.class); final boolean isProvided = ReflectionUtils.isAnnotationPresent(field, Provided.class); if (isConfigured && isProvided) { throw new DescriptorException("The field " + field + " is annotated with both @Configured and @Provided, which are mutually exclusive."); } if (!isConfigured && (isInject || isProvided)) { // provided properties = @Inject or @Provided, and NOT @Configured _providedProperties.add(new ProvidedPropertyDescriptorImpl(field, this)); } else if (isConfigured) { if (!isInject) { logger.debug("No @Inject annotation found for @Configured field: {}", field); } ConfiguredPropertyDescriptor cpd = new ConfiguredPropertyDescriptorImpl(field, this); _configuredProperties.add(cpd); } } @Override protected void visitMethod(Method method) { final boolean isInitialize; { final boolean isInitializeAnnotationPresent = ReflectionUtils.isAnnotationPresent(method, Initialize.class); final boolean isPostConstructAnnotationPresent = ReflectionUtils.isAnnotationPresent(method, PostConstruct.class); // @PostConstruct is a valid substitution for @Initialize isInitialize = isInitializeAnnotationPresent || isPostConstructAnnotationPresent; } final boolean isClose; { final boolean isPreDestroyAnnotationPresent = ReflectionUtils.isAnnotationPresent(method, PreDestroy.class); final boolean isCloseAnnotationPresent = ReflectionUtils.isAnnotationPresent(method, Close.class); // @PreDestroy is a valid substitution for @Close isClose = isCloseAnnotationPresent || isPreDestroyAnnotationPresent; } final boolean isValidate = ReflectionUtils.isAnnotationPresent(method, Validate.class); if (isInitialize) { _initializeMethods.add(new InitializeMethodDescriptorImpl(method, this)); } if (isValidate) { _validateMethods.add(new ValidateMethodDescriptorImpl(method, this)); } if (isClose) { _closeMethods.add(new CloseMethodDescriptorImpl(method, this)); } } public Set<InitializeMethodDescriptor> getInitializeMethods() { return Collections.unmodifiableSet(_initializeMethods); } public Set<ConfiguredPropertyDescriptor> getConfiguredProperties() { return Collections.unmodifiableSet(_configuredProperties); } public Set<CloseMethodDescriptor> getCloseMethods() { return Collections.unmodifiableSet(_closeMethods); } @Override public Set<ValidateMethodDescriptor> getValidateMethods() { return Collections.unmodifiableSet(_validateMethods); } @Override public Set<ProvidedPropertyDescriptor> getProvidedProperties() { return Collections.unmodifiableSet(_providedProperties); } @Override public Set<ProvidedPropertyDescriptor> getProvidedPropertiesByType(Class<?> cls) { Set<ProvidedPropertyDescriptor> result = new HashSet<ProvidedPropertyDescriptor>(); for (ProvidedPropertyDescriptor descriptor : _providedProperties) { if (ReflectionUtils.is(descriptor.getType(), cls)) { result.add(descriptor); } } return result; } @Override public ConfiguredPropertyDescriptor getConfiguredProperty(String configuredName) { for (ConfiguredPropertyDescriptor configuredDescriptor : _configuredProperties) { if (configuredName.equals(configuredDescriptor.getName())) { return configuredDescriptor; } } for (ConfiguredPropertyDescriptor configuredDescriptor : _configuredProperties) { String[] aliases = configuredDescriptor.getAliases(); if (ArrayUtils.contains(aliases, configuredName)) { return configuredDescriptor; } } return null; } @Override public Set<ConfiguredPropertyDescriptor> getConfiguredPropertiesByAnnotation( Class<? extends Annotation> annotationClass) { Set<ConfiguredPropertyDescriptor> set = new TreeSet<ConfiguredPropertyDescriptor>(); for (ConfiguredPropertyDescriptor configuredDescriptor : _configuredProperties) { Annotation annotation = configuredDescriptor.getAnnotation(annotationClass); if (annotation != null) { set.add(configuredDescriptor); } } return set; } @Override public Set<ConfiguredPropertyDescriptor> getConfiguredPropertiesByType(Class<?> type, boolean includeArrays) { Set<ConfiguredPropertyDescriptor> set = new TreeSet<ConfiguredPropertyDescriptor>(); for (ConfiguredPropertyDescriptor configuredDescriptor : _configuredProperties) { final boolean include; if (includeArrays) { include = ReflectionUtils.is(configuredDescriptor.getBaseType(), type); } else { Class<?> baseType = configuredDescriptor.getType(); if (baseType.isArray() == type.isArray()) { include = ReflectionUtils.is(baseType, type); } else { include = false; } } if (include) { set.add(configuredDescriptor); } } return set; } @Override public int compareTo(ComponentDescriptor<?> o) { if (o == null) { return 1; } Class<?> otherBeanClass = o.getComponentClass(); if (otherBeanClass == null) { return 1; } String thisBeanClassName = this.getComponentClass().toString(); String thatBeanClassName = otherBeanClass.toString(); return thisBeanClassName.compareTo(thatBeanClassName); } }