Skip to content

Commit

Permalink
Issue datacleaner#370: Improved exception hierarchy+handling in valid…
Browse files Browse the repository at this point in the history
…ation cases.
  • Loading branch information
kaspersorensen committed Apr 21, 2015
1 parent 04bf058 commit c3d147c
Show file tree
Hide file tree
Showing 12 changed files with 186 additions and 64 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,34 +30,32 @@
* validateion method is using the @Validate annotation.
*
* @see Validate
*
*
*/
public interface ValidateMethodDescriptor extends Serializable {

/**
* Invokes the validate method
*
* @param component
* the component to validate
*/
public void validate(Object component);
/**
* Invokes the validate method
*
* @param component
* the component to validate
*/
public void validate(Object component);

/**
* Gets the annotations of the method
*
* @return the annotations of the method
*/
public Set<Annotation> getAnnotations();
/**
* Gets the annotations of the method
*
* @return the annotations of the method
*/
public Set<Annotation> getAnnotations();

/**
* Gets a particular annotation of the method
*
* @param <A>
* the annotation type
* @param annotationClass
* the annotation class to look for
* @return a matching annotation or null, if none is present
*/
public <A extends Annotation> A getAnnotation(Class<A> annotationClass);
/**
* Gets a particular annotation of the method
*
* @param <A>
* the annotation type
* @param annotationClass
* the annotation class to look for
* @return a matching annotation or null, if none is present
*/
public <A extends Annotation> A getAnnotation(Class<A> annotationClass);
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import org.datacleaner.job.JaxbJobMetadataFactoryImpl;
import org.datacleaner.job.JaxbJobWriter;
import org.datacleaner.job.builder.AnalysisJobBuilder;
import org.datacleaner.job.builder.NoResultProducingComponentsException;
import org.datacleaner.user.MonitorConnection;
import org.datacleaner.user.UserPreferences;
import org.datacleaner.util.FileFilters;
Expand Down Expand Up @@ -90,13 +91,13 @@ public void actionPerformed(ActionEvent event) {
_window.applyPropertyValues();
analysisJob = _analysisJobBuilder.toAnalysisJob();
} catch (Exception e) {
if ("No Analyzers in job".equals(e.getMessage())) {
// TODO: Have a better way to diagnose this issue
if (e instanceof NoResultProducingComponentsException) {
int result = JOptionPane
.showConfirmDialog(
_window.toComponent(),
"You job does not have any analyzer components in it, and is thus 'incomplete'. Do you want to save it anyway?",
"No analyzers in job", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
"You job does not have any result-producing components in it, and is thus 'incomplete'. Do you want to save it anyway?",
"No result producing components in job", JOptionPane.YES_NO_OPTION,
JOptionPane.WARNING_MESSAGE);
if (result == JOptionPane.YES_OPTION) {
analysisJob = _analysisJobBuilder.toAnalysisJob(false);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
import org.datacleaner.guice.InjectorBuilder;
import org.datacleaner.guice.JobFile;
import org.datacleaner.guice.Nullable;
import org.datacleaner.job.ComponentValidationException;
import org.datacleaner.job.JaxbJobWriter;
import org.datacleaner.job.builder.AnalysisJobBuilder;
import org.datacleaner.job.builder.AnalyzerChangeListener;
Expand Down Expand Up @@ -517,12 +518,18 @@ public void updateStatusLabel() {
final String errorMessage;
if (ex instanceof UnconfiguredConfiguredPropertyException) {
executeable = false;
UnconfiguredConfiguredPropertyException unconfiguredConfiguredPropertyException = (UnconfiguredConfiguredPropertyException) ex;
ConfiguredPropertyDescriptor configuredProperty = unconfiguredConfiguredPropertyException
final UnconfiguredConfiguredPropertyException unconfiguredConfiguredPropertyException = (UnconfiguredConfiguredPropertyException) ex;
final ConfiguredPropertyDescriptor configuredProperty = unconfiguredConfiguredPropertyException
.getConfiguredProperty();
ComponentBuilder componentBuilder = unconfiguredConfiguredPropertyException.getComponentBuilder();
final ComponentBuilder componentBuilder = unconfiguredConfiguredPropertyException
.getComponentBuilder();
errorMessage = "Property '" + configuredProperty.getName() + "' in "
+ LabelUtils.getLabel(componentBuilder) + " is not set!";
} else if (ex instanceof ComponentValidationException) {
executeable = false;
final ComponentValidationException componentValidationException = (ComponentValidationException) ex;
errorMessage = componentValidationException.getComponentDescriptor().getDisplayName()
+ " validation failed: " + ex.getMessage();
} else {
errorMessage = ex.getMessage();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@
import java.util.List;
import java.util.Set;

import org.datacleaner.util.ReflectionUtils;
import org.apache.metamodel.util.BaseObject;
import org.datacleaner.util.ReflectionUtils;

/**
* Abstract descriptor for things that are represented by a method on a
Expand Down Expand Up @@ -57,6 +57,10 @@ public AbstractMethodDescriptor(Method method, ComponentDescriptor<?> componentD
_name = method.getName();
_componentDescriptor = componentDescriptor;
}

public ComponentDescriptor<?> getComponentDescriptor() {
return _componentDescriptor;
}

public final Method getMethod() {
if (_method == null) {
Expand All @@ -70,22 +74,29 @@ public final String toString() {
return getClass().getSimpleName() + "[method=" + _name + "]";
}

protected final void invoke(Object component) {
protected final void invoke(Object component) throws RuntimeException, IllegalStateException {
try {
_method.invoke(component);
} catch (RuntimeException e) {
throw e;
} catch (InvocationTargetException e) {
Throwable targetException = e.getTargetException();
if (targetException instanceof RuntimeException) {
throw (RuntimeException) targetException;
}
throw new IllegalStateException("Invoked method threw exception", targetException);
throw convertThrownException(component, e);
} catch (Exception e) {
if (e instanceof RuntimeException) {
throw (RuntimeException) e;
}
throw new IllegalStateException("Could not invoke method " + getMethod(), e);
}
}

protected RuntimeException convertThrownException(Object component, InvocationTargetException e) {
final Throwable targetException = e.getTargetException();
if (targetException instanceof RuntimeException) {
throw (RuntimeException) targetException;
}
throw new RuntimeException(targetException);
}

public final Set<Annotation> getAnnotations() {
Annotation[] annotations = getMethod().getAnnotations();
return new HashSet<Annotation>(Arrays.asList(annotations));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,26 @@
*/
package org.datacleaner.descriptors;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import org.datacleaner.job.ComponentValidationException;

final class ValidateMethodDescriptorImpl extends AbstractMethodDescriptor implements ValidateMethodDescriptor {

private static final long serialVersionUID = 1L;
private static final long serialVersionUID = 1L;

public ValidateMethodDescriptorImpl(Method method, ComponentDescriptor<?> componentDescriptor) {
super(method, componentDescriptor);
}
public ValidateMethodDescriptorImpl(Method method, ComponentDescriptor<?> componentDescriptor) {
super(method, componentDescriptor);
}

@Override
public void validate(Object component) {
invoke(component);
}
@Override
public void validate(Object component) throws ComponentValidationException {
invoke(component);
}

@Override
protected RuntimeException convertThrownException(Object component, InvocationTargetException e) {
return new ComponentValidationException(getComponentDescriptor(), component, e.getCause());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public ComponentConfigurationException(String message) {
super(message);
}

public ComponentConfigurationException(String message, Exception cause) {
public ComponentConfigurationException(String message, Throwable cause) {
super(message, cause);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* 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.job;

import org.datacleaner.api.Validate;
import org.datacleaner.descriptors.ComponentDescriptor;

/**
* Exception thrown if validation (typically using {@link Validate} annotated
* methods) of a component fails.
*/
public class ComponentValidationException extends ComponentConfigurationException {

private static final long serialVersionUID = 1L;

private final ComponentDescriptor<?> _componentDescriptor;
private final Object _componentInstance;

public ComponentValidationException(ComponentDescriptor<?> componentDescriptor, Object componentInstance,
Throwable cause) {
super(cause.getMessage(), cause);
_componentDescriptor = componentDescriptor;
_componentInstance = componentInstance;
}

public ComponentDescriptor<?> getComponentDescriptor() {
return _componentDescriptor;
}

public Object getComponentInstance() {
return _componentInstance;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
import org.datacleaner.job.AnalysisJob;
import org.datacleaner.job.ComponentConfiguration;
import org.datacleaner.job.ComponentRequirement;
import org.datacleaner.job.ComponentValidationException;
import org.datacleaner.job.FilterOutcome;
import org.datacleaner.job.HasComponentRequirement;
import org.datacleaner.job.HasFilterOutcomes;
Expand Down Expand Up @@ -199,7 +200,7 @@ public void setConfiguredProperties(ComponentConfiguration configuration) {
}

@Override
public final boolean isConfigured(boolean throwException) throws IllegalStateException,
public final boolean isConfigured(boolean throwException) throws ComponentValidationException,
UnconfiguredConfiguredPropertyException {
for (ConfiguredPropertyDescriptor configuredProperty : _descriptor.getConfiguredProperties()) {
if (!isConfigured(configuredProperty, throwException)) {
Expand All @@ -213,7 +214,7 @@ public final boolean isConfigured(boolean throwException) throws IllegalStateExc

try {
LifeCycleHelper lifeCycleHelper = new LifeCycleHelper(_analysisJobBuilder.getConfiguration(), null, false);
lifeCycleHelper.validate(getDescriptor(), getConfigurableBean());
lifeCycleHelper.validate(getDescriptor(), getComponentInstance());
} catch (RuntimeException e) {
if (throwException) {
throw e;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import org.datacleaner.api.HasAnalyzerResult;
import org.datacleaner.api.InputColumn;
import org.datacleaner.api.Transformer;
import org.datacleaner.api.Validate;
import org.datacleaner.configuration.DataCleanerConfiguration;
import org.datacleaner.connection.Datastore;
import org.datacleaner.connection.DatastoreConnection;
Expand All @@ -58,6 +59,7 @@
import org.datacleaner.job.AnalyzerJob;
import org.datacleaner.job.ComponentJob;
import org.datacleaner.job.ComponentRequirement;
import org.datacleaner.job.ComponentValidationException;
import org.datacleaner.job.FilterJob;
import org.datacleaner.job.FilterOutcome;
import org.datacleaner.job.IdGenerator;
Expand Down Expand Up @@ -616,10 +618,19 @@ public List<InputColumn<?>> getAvailableInputColumns(Class<?> dataType) {
* contain more detailed information about the cause of the
* validation error, whereas a boolean contains no details.
* @return true if the analysis job builder is correctly configured
* @throws UnconfiguredConfiguredPropertyException
* if a required property is not configured
* @throws ComponentValidationException
* if custom validation (using {@link Validate} method or so) of
* a component fails.
* @throws NoResultProducingComponentsException
* if no result producing components (see
* {@link HasAnalyzerResult}) exist in the job.
* @throws IllegalStateException
* if any other (mostly unexpected) configuration issue occurs
*/
public boolean isConfigured(final boolean throwException) throws IllegalStateException,
UnconfiguredConfiguredPropertyException {
public boolean isConfigured(final boolean throwException) throws UnconfiguredConfiguredPropertyException,
ComponentValidationException, NoResultProducingComponentsException, IllegalStateException {
if (_datastoreConnection == null) {
if (throwException) {
throw new IllegalStateException("No Datastore or DatastoreConnection set");
Expand All @@ -636,7 +647,7 @@ public boolean isConfigured(final boolean throwException) throws IllegalStateExc

if (getResultProducingComponentBuilders().isEmpty()) {
if (throwException) {
throw new IllegalStateException("No result producing components in job");
throw new NoResultProducingComponentsException();
}
return false;
}
Expand Down Expand Up @@ -774,9 +785,11 @@ public AnalysisJobMetadata createMetadata() {
*
* @return
* @throws IllegalStateException
* if the job is invalidly configured.
* if the job is invalidly configured. See
* {@link #isConfigured(boolean)} for detailed exception
* descriptions.
*/
public AnalysisJob toAnalysisJob() throws IllegalStateException {
public AnalysisJob toAnalysisJob() throws RuntimeException {
return toAnalysisJob(true);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.datacleaner.job.AnalysisJob;
import org.datacleaner.job.ComponentConfiguration;
import org.datacleaner.job.ComponentRequirement;
import org.datacleaner.job.ComponentValidationException;
import org.datacleaner.job.HasComponentRequirement;
import org.datacleaner.job.InputColumnSinkJob;
import org.datacleaner.metadata.HasMetadataProperties;
Expand Down Expand Up @@ -75,10 +76,10 @@ public interface ComponentBuilder extends HasMetadataProperties, InputColumnSink
*
* @param throwException
* @return
* @throws IllegalStateException
* @throws ComponentValidationException
* @throws UnconfiguredConfiguredPropertyException
*/
public boolean isConfigured(boolean throwException) throws IllegalStateException,
public boolean isConfigured(boolean throwException) throws ComponentValidationException,
UnconfiguredConfiguredPropertyException;

/**
Expand Down
Loading

0 comments on commit c3d147c

Please sign in to comment.