/** * 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.job.builder; import java.lang.reflect.Array; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; import org.apache.metamodel.util.EqualsBuilder; import org.eobjects.analyzer.descriptors.BeanDescriptor; import org.eobjects.analyzer.descriptors.ConfiguredPropertyDescriptor; import org.eobjects.analyzer.job.AnalysisJob; import org.eobjects.analyzer.job.BeanConfiguration; import org.eobjects.analyzer.job.ImmutableBeanConfiguration; import org.eobjects.analyzer.lifecycle.LifeCycleHelper; import org.eobjects.analyzer.result.renderer.Renderable; import org.eobjects.analyzer.util.ReflectionUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Abstract {@link ComponentBuilder} for components of a {@link AnalysisJob}. * * @param <D> * the component descriptor type (eg. AnalyzerBeanDescriptor) * @param <E> * the actual component type (eg. Analyzer) * @param <B> * the concrete {@link ComponentBuilder} (eg. AnalyzerJobBuilder) */ @SuppressWarnings("unchecked") public abstract class AbstractBeanJobBuilder<D extends BeanDescriptor<E>, E, B extends ComponentBuilder> implements ComponentBuilder, Renderable { private static final Logger logger = LoggerFactory.getLogger(AbstractBeanJobBuilder.class); private final D _descriptor; private final E _configurableBean; private final AnalysisJobBuilder _analysisJobBuilder; private final Map<String, String> _metadataProperties; private String _name; public AbstractBeanJobBuilder(AnalysisJobBuilder analysisJobBuilder, D descriptor, Class<?> builderClass) { if (analysisJobBuilder == null) { throw new IllegalArgumentException("analysisJobBuilder cannot be null"); } if (descriptor == null) { throw new IllegalArgumentException("descriptor cannot be null"); } if (builderClass == null) { throw new IllegalArgumentException("builderClass cannot be null"); } _analysisJobBuilder = analysisJobBuilder; _descriptor = descriptor; if (!ReflectionUtils.is(getClass(), builderClass)) { throw new IllegalArgumentException("Builder class does not correspond to actual class of builder"); } _configurableBean = ReflectionUtils.newInstance(_descriptor.getComponentClass()); _metadataProperties = new LinkedHashMap<>(); } /** * Gets metadata properties as a map. * * @return */ @Override public final Map<String, String> getMetadataProperties() { return _metadataProperties; } /** * Gets a metadata property * * @param key * @return */ @Override public final String getMetadataProperty(String key) { return _metadataProperties.get(key); } /** * Sets a metadata property * * @param key * @param value */ @Override public final void setMetadataProperty(String key, String value) { _metadataProperties.put(key, value); } @Override public void setMetadataProperties(Map<String, String> metadataProperties) { _metadataProperties.clear(); if (metadataProperties != null) { _metadataProperties.putAll(metadataProperties); } } /** * Removes/clears a metadata property * * @param key */ @Override public final void removeMetadataProperty(String key) { _metadataProperties.remove(key); } public final AnalysisJobBuilder getAnalysisJobBuilder() { return _analysisJobBuilder; } @Override public final D getDescriptor() { return _descriptor; } @Override public final E getComponentInstance() { return _configurableBean; } /** * @deprecated use {@link #getComponentInstance()} instead. */ @Deprecated public final E getConfigurableBean() { return getComponentInstance(); } @Override public void setConfiguredProperties(Map<ConfiguredPropertyDescriptor, Object> configuredPropeties) { final ImmutableBeanConfiguration beanConfiguration = new ImmutableBeanConfiguration(configuredPropeties); setConfiguredProperties(beanConfiguration); } /** * Sets the configured properties of this component based on a * {@link BeanConfiguration}. * * @param configuration */ public void setConfiguredProperties(BeanConfiguration configuration) { boolean changed = false; final Set<ConfiguredPropertyDescriptor> properties = getDescriptor().getConfiguredProperties(); for (ConfiguredPropertyDescriptor property : properties) { final Object value = configuration.getProperty(property); final boolean changedValue = setConfiguredPropertyIfChanged(property, value); if (changedValue) { changed = true; } } if (changed) { onConfigurationChanged(); } } @Override public final boolean isConfigured(boolean throwException) throws IllegalStateException, UnconfiguredConfiguredPropertyException { for (ConfiguredPropertyDescriptor configuredProperty : _descriptor.getConfiguredProperties()) { if (!isConfigured(configuredProperty, throwException)) { if (throwException) { throw new UnconfiguredConfiguredPropertyException(this, configuredProperty); } else { return false; } } } try { LifeCycleHelper lifeCycleHelper = new LifeCycleHelper(null, null, false); lifeCycleHelper.validate(getDescriptor(), getConfigurableBean()); } catch (RuntimeException e) { if (throwException) { throw e; } else { return false; } } return true; } @Override public String getName() { return _name; } public B setName(String name) { _name = name; return (B) this; } @Override public boolean isConfigured() { return isConfigured(false); } @Override public boolean isConfigured(ConfiguredPropertyDescriptor configuredProperty, boolean throwException) throws UnconfiguredConfiguredPropertyException { if (configuredProperty.isRequired()) { Map<ConfiguredPropertyDescriptor, Object> configuredProperties = getConfiguredProperties(); Object value = configuredProperties.get(configuredProperty); if (configuredProperty.isArray() && value != null) { if (Array.getLength(value) == 0) { value = null; } } if (value == null) { if (throwException) { throw new UnconfiguredConfiguredPropertyException(this, configuredProperty); } else { logger.debug("Configured property is not set: " + configuredProperty); return false; } } } return true; } @Override public B setConfiguredProperty(String configuredName, Object value) { ConfiguredPropertyDescriptor configuredProperty = _descriptor.getConfiguredProperty(configuredName); if (configuredProperty == null) { throw new IllegalArgumentException("No such configured property: " + configuredName); } return setConfiguredProperty(configuredProperty, value); } @Override public B setConfiguredProperty(ConfiguredPropertyDescriptor configuredProperty, Object value) { final boolean changed = setConfiguredPropertyIfChanged(configuredProperty, value); if (changed) { onConfigurationChanged(); } return (B) this; } /** * Sets a configured property if it has changed. * * Note that this method is for internal use. It does not invoke * {@link #onConfigurationChanged()} even if changes happen. The reason for * this is to allow code reuse and avoid chatty use of the notification * method. * * @param configuredProperty * @param value * @return true if the value was changed or false if it was not */ protected boolean setConfiguredPropertyIfChanged(final ConfiguredPropertyDescriptor configuredProperty, final Object value) { if (configuredProperty == null) { throw new IllegalArgumentException("configuredProperty cannot be null"); } final Object currentValue = configuredProperty.getValue(_configurableBean); if (EqualsBuilder.equals(currentValue, value)) { // no change return false; } if (value != null) { boolean correctType = true; if (configuredProperty.isArray()) { if (value.getClass().isArray()) { int length = Array.getLength(value); for (int i = 0; i < length; i++) { Object valuePart = Array.get(value, i); if (valuePart == null) { logger.warn("Element no. {} in array (size {}) is null! Value passed to {}", new Object[] { i, length, configuredProperty }); } else { if (!ReflectionUtils.is(valuePart.getClass(), configuredProperty.getBaseType())) { correctType = false; } } } } else { if (!ReflectionUtils.is(value.getClass(), configuredProperty.getBaseType())) { correctType = false; } } } else { if (!ReflectionUtils.is(value.getClass(), configuredProperty.getBaseType())) { correctType = false; } } if (!correctType) { throw new IllegalArgumentException("Invalid value type: " + value.getClass().getName() + ", expected: " + configuredProperty.getBaseType().getName()); } } configuredProperty.setValue(_configurableBean, value); return true; } @Override public Map<ConfiguredPropertyDescriptor, Object> getConfiguredProperties() { Map<ConfiguredPropertyDescriptor, Object> map = new HashMap<ConfiguredPropertyDescriptor, Object>(); Set<ConfiguredPropertyDescriptor> configuredProperties = getDescriptor().getConfiguredProperties(); for (ConfiguredPropertyDescriptor propertyDescriptor : configuredProperties) { Object value = getConfiguredProperty(propertyDescriptor); if (value != null) { map.put(propertyDescriptor, value); } } return Collections.unmodifiableMap(map); } /** * method that can be used by sub-classes to add callback logic when the * configuration of the bean changes */ public void onConfigurationChanged() { } @Override public Object getConfiguredProperty(ConfiguredPropertyDescriptor propertyDescriptor) { return propertyDescriptor.getValue(getConfigurableBean()); } }