/**
* DataCleaner (community edition)
* 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.datacleaner.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.Iterator;
import java.util.Set;
import java.util.TreeSet;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.inject.Inject;
import javax.inject.Named;
import org.apache.commons.lang.ArrayUtils;
import org.datacleaner.api.Alias;
import org.datacleaner.api.Categorized;
import org.datacleaner.api.Close;
import org.datacleaner.api.ComponentCategory;
import org.datacleaner.api.ComponentSuperCategory;
import org.datacleaner.api.Configured;
import org.datacleaner.api.Description;
import org.datacleaner.api.Distributed;
import org.datacleaner.api.HasDistributionAdvice;
import org.datacleaner.api.Initialize;
import org.datacleaner.api.MultiStreamComponent;
import org.datacleaner.api.Provided;
import org.datacleaner.api.Validate;
import org.datacleaner.util.ReflectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A {@link ComponentDescriptor} for simple components. Simple components covers
* reference data types (Dictionary, SynonymCatalog, StringPattern) as well as
* custom configuration components.
*
* Simple components support the {@link Configured}, {@link Validate},
* {@link Initialize} and {@link Close} annotations as well as the
* {@link 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(final Class<B> beanClass) {
this(beanClass, false);
}
public SimpleComponentDescriptor(final Class<B> beanClass, final boolean initialize) {
super(beanClass);
_configuredProperties = new TreeSet<>();
_providedProperties = new TreeSet<>();
_validateMethods = new HashSet<>();
_initializeMethods = new HashSet<>();
_closeMethods = new HashSet<>();
if (initialize) {
visitClass();
}
}
@Override
public String getDisplayName() {
final Named named = getAnnotation(Named.class);
if (named == null) {
return getComponentClass().getSimpleName();
}
return named.value();
}
@Override
public final String getDescription() {
final Description description = getAnnotation(Description.class);
if (description == null) {
return null;
}
return description.value();
}
@Override
public <A extends Annotation> A getAnnotation(final Class<A> annotationClass) {
return ReflectionUtils.getAnnotation(getComponentClass(), annotationClass);
}
@Override
public Set<Annotation> getAnnotations() {
final Annotation[] annotations = getComponentClass().getAnnotations();
return new HashSet<>(Arrays.asList(annotations));
}
@Override
public Set<ComponentCategory> getComponentCategories() {
final Categorized categorized = getAnnotation(Categorized.class);
if (categorized == null) {
return Collections.emptySet();
}
final Class<? extends ComponentCategory>[] value = categorized.value();
if (value == null || value.length == 0) {
return Collections.emptySet();
}
final Set<ComponentCategory> result = new HashSet<>();
for (final Class<? extends ComponentCategory> categoryClass : value) {
if (categoryClass != ComponentCategory.class) {
final ComponentCategory category = ReflectionUtils.newInstance(categoryClass);
result.add(category);
}
}
return result;
}
@Override
public ComponentSuperCategory getComponentSuperCategory() {
final Categorized categorized = getAnnotation(Categorized.class);
Class<? extends ComponentSuperCategory> superCategoryClass;
if (categorized == null) {
superCategoryClass = getDefaultComponentSuperCategoryClass();
} else {
superCategoryClass = categorized.superCategory();
if (superCategoryClass == ComponentSuperCategory.class) {
superCategoryClass = getDefaultComponentSuperCategoryClass();
}
}
return ReflectionUtils.newInstance(superCategoryClass);
}
/**
* Defines the {@link ComponentSuperCategory} to return, if no
* {@link ComponentSuperCategory} was defined
*
* @return
*/
protected Class<? extends ComponentSuperCategory> getDefaultComponentSuperCategoryClass() {
return ComponentSuperCategory.class;
}
@Override
public B newInstance() {
try {
return getComponentClass().newInstance();
} catch (final RuntimeException e) {
throw e;
} catch (final Exception e) {
throw new IllegalStateException("Could not construct new instance of " + getComponentClass(), e);
}
}
@Override
protected void visitClass() {
super.visitClass();
if (ReflectionUtils.isCloseable(getComponentClass())) {
try {
final Method method = getComponentClass().getMethod("close", new Class<?>[0]);
final CloseMethodDescriptorImpl cmd = new CloseMethodDescriptorImpl(method, this);
_closeMethods.add(cmd);
} catch (final 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(final 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);
}
final ConfiguredPropertyDescriptor cpd = new ConfiguredPropertyDescriptorImpl(field, this);
_configuredProperties.add(cpd);
}
}
@Override
protected void visitMethod(final 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 final Set<InitializeMethodDescriptor> getInitializeMethods() {
return Collections.unmodifiableSet(_initializeMethods);
}
public final Set<ConfiguredPropertyDescriptor> getConfiguredProperties() {
return Collections.unmodifiableSet(_configuredProperties);
}
public final Set<CloseMethodDescriptor> getCloseMethods() {
return Collections.unmodifiableSet(_closeMethods);
}
@Override
public final Set<ValidateMethodDescriptor> getValidateMethods() {
return Collections.unmodifiableSet(_validateMethods);
}
@Override
public final Set<ProvidedPropertyDescriptor> getProvidedProperties() {
return Collections.unmodifiableSet(_providedProperties);
}
@Override
public final Set<ProvidedPropertyDescriptor> getProvidedPropertiesByType(final Class<?> cls) {
final Set<ProvidedPropertyDescriptor> result = new HashSet<>();
for (final ProvidedPropertyDescriptor descriptor : _providedProperties) {
if (ReflectionUtils.is(descriptor.getType(), cls)) {
result.add(descriptor);
}
}
return result;
}
@Override
public final ConfiguredPropertyDescriptor getConfiguredProperty(final String configuredName) {
for (final ConfiguredPropertyDescriptor configuredDescriptor : _configuredProperties) {
if (configuredName.equals(configuredDescriptor.getName())) {
return configuredDescriptor;
}
}
for (final ConfiguredPropertyDescriptor configuredDescriptor : _configuredProperties) {
final String[] aliases = configuredDescriptor.getAliases();
if (ArrayUtils.contains(aliases, configuredName)) {
return configuredDescriptor;
}
}
return null;
}
@Override
public final Set<ConfiguredPropertyDescriptor> getConfiguredPropertiesByAnnotation(
final Class<? extends Annotation> annotationClass) {
final Set<ConfiguredPropertyDescriptor> set = new TreeSet<>();
for (final ConfiguredPropertyDescriptor configuredDescriptor : _configuredProperties) {
final Annotation annotation = configuredDescriptor.getAnnotation(annotationClass);
if (annotation != null) {
set.add(configuredDescriptor);
}
}
return set;
}
@Override
public final Set<ConfiguredPropertyDescriptor> getConfiguredPropertiesByType(final Class<?> type,
final boolean includeArrays) {
final Set<ConfiguredPropertyDescriptor> set = new TreeSet<>();
for (final ConfiguredPropertyDescriptor configuredDescriptor : _configuredProperties) {
final boolean include;
if (includeArrays) {
include = ReflectionUtils.is(configuredDescriptor.getBaseType(), type);
} else {
final 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(final ComponentDescriptor<?> o) {
if (o == null) {
return 1;
}
final Class<?> otherComponentClass = o.getComponentClass();
if (otherComponentClass == null) {
return 1;
}
int diff = this.getDisplayName().compareTo(o.getDisplayName());
if (diff == 0) {
final String thisBeanClassName = this.getComponentClass().toString();
final String thatBeanClassName = otherComponentClass.toString();
diff = thisBeanClassName.compareTo(thatBeanClassName);
}
return diff;
}
@Override
public final boolean isDistributable() {
final Distributed distributed = getAnnotation(Distributed.class);
if (distributed != null) {
return distributed.value();
}
final boolean hasDistributionAdvice = ReflectionUtils.is(getComponentClass(), HasDistributionAdvice.class);
if (hasDistributionAdvice) {
return true;
}
return isDistributableByDefault();
}
protected boolean isDistributableByDefault() {
return false;
}
@Override
public boolean isMultiStreamComponent() {
return ReflectionUtils.is(getComponentClass(), MultiStreamComponent.class);
}
@Override
public final String[] getAliases() {
final Alias alias = getAnnotation(Alias.class);
if (alias == null) {
return new String[0];
}
return alias.value();
}
@Override
public final Set<ConfiguredPropertyDescriptor> getConfiguredPropertiesForInput() {
return getConfiguredPropertiesForInput(true);
}
@Override
public final Set<ConfiguredPropertyDescriptor> getConfiguredPropertiesForInput(final boolean includeOptional) {
final Set<ConfiguredPropertyDescriptor> descriptors = new TreeSet<>(_configuredProperties);
for (final Iterator<ConfiguredPropertyDescriptor> it = descriptors.iterator(); it.hasNext(); ) {
final ConfiguredPropertyDescriptor propertyDescriptor = it.next();
if (!propertyDescriptor.isInputColumn()) {
it.remove();
} else if (!includeOptional && !propertyDescriptor.isRequired()) {
it.remove();
}
}
return descriptors;
}
}