package io.eguan.configuration;
/*
* #%L
* Project eguan
* %%
* Copyright (C) 2012 - 2017 Oodrive
* %%
* 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.
* #L%
*/
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import javax.annotation.Nonnull;
import javax.annotation.concurrent.Immutable;
/**
* Class for report items describing validation errors when throwing {@link ConfigValidationException}s.
*
* @author oodrive
* @author pwehrle
* @author llambert
*/
@Immutable
public final class ValidationError implements Serializable {
private static final long serialVersionUID = -6015469385141576890L;
/**
* Static member to return when there is no validation error.
*/
public static final ValidationError NO_ERROR = new ValidationError(ErrorType.NONE,
new ConfigurationContext[] { null }, new ConfigKey[] { null }, null, "no error");
/**
* Enumeration of possible error types.
*
*
*/
public enum ErrorType {
/**
* Returned when there is no error.
*/
NONE,
/**
* Returned for empty, null or invalid context names.
*/
CONTEXT_NAME,
/**
* Returned for null keys in contexts or duplicate keys between contexts.
*/
CONTEXT_KEYS,
/**
* Returned for null or invalid key names.
*/
KEYS_NAME,
/**
* Returned for null values.
*/
VALUE_NULL,
/**
* Returned for invalid values according to a key's constraints.
*/
VALUE_INVALID,
/**
* Returned on missing values for required keys.
*/
VALUE_REQUIRED;
}
/**
* Generates a multi-line error report from the given {@link ValidationError} instance.
*
* @param error
* a non-<code>null</code> {@link ValidationError}
* @return a multi-line error report including all information extracted from the argument, or {@value #NO_ERROR
* #getErrorMessage()} if the argument is {@link #NO_ERROR}
* @throws NullPointerException
* if the argument is <code>null</code>
*/
// TODO: find a way to bind generic type arguments to avoid unchecked casts and raw types
@SuppressWarnings({ "rawtypes", "unchecked" })
public static final String getFormattedErrorReport(@Nonnull final ValidationError error)
throws NullPointerException {
if (NO_ERROR.equals(Objects.requireNonNull(error))) {
return NO_ERROR.getErrorMessage();
}
final StringBuilder formattedContextList = new StringBuilder();
final String separator = System.getProperty("line.separator");
final ErrorType errType = error.getType();
formattedContextList.append("type=" + errType.toString() + separator);
final List<ConfigurationContext<?, ?>> contextList = Arrays.asList(error.getConfigurationContexts());
final List<ConfigKey<?>> keyList = Arrays.asList(error.getConfigKeys());
for (final ConfigurationContext<?, ?> currContext : contextList) {
formattedContextList.append("context=" + currContext.getClass().getSimpleName());
final ArrayList<ConfigKey<?>> contextKeyList = new ArrayList<ConfigKey<?>>(currContext.getConfigKeys());
contextKeyList.retainAll(keyList);
if (contextKeyList.isEmpty()) {
continue;
}
formattedContextList.append("; key(s)=");
for (final ConfigKey<?> currKey : contextKeyList) {
formattedContextList.append(currContext.getPropertyKey((ConfigKey) currKey));
if (contextKeyList.indexOf(currKey) < contextKeyList.size() - 1) {
formattedContextList.append(", ");
}
}
formattedContextList.append(separator);
}
if (error.getValue() != null) {
formattedContextList.append("Provided value: " + error.getValue() + separator);
}
formattedContextList.append("Error message: " + error.getErrorMessage() + separator);
return formattedContextList.toString();
}
private final ErrorType type;
private final HashSet<ConfigurationContext<?, ?>> contexts = new HashSet<ConfigurationContext<?, ?>>();
private final ConfigKey<?>[] configKeys;
private final Object value;
private final String errorMessage;
/**
* Constructs an immutable instance.
*
* @param type
* the {@link ErrorType} representing this error
* @param contexts
* an array of affected {@link ConfigurationContext}, may be null
* @param configKeys
* the optional array of affected {@link ConfigKey}s within the context
* @param value
* the optional value affected to the configuration key
* @param errorMessage
* an explicit free-text error message describing the problem
*/
public ValidationError(@Nonnull final ErrorType type, final ConfigurationContext<?, ?>[] contexts,
final ConfigKey<?>[] configKeys, final Object value, @Nonnull final String errorMessage) {
super();
this.type = Objects.requireNonNull(type);
this.contexts.addAll(Arrays.asList(contexts));
this.configKeys = configKeys;
this.value = value;
this.errorMessage = Objects.requireNonNull(errorMessage);
}
/**
* Constructor taking only one context and config key argument.
*
* @param type
* the {@link ErrorType} representing this error
* @param context
* the affected {@link ConfigurationContext}, may be <code>null</code>
* @param configKey
* the optional affected {@link ConfigKey}s within the context
* @param value
* the optional value affected to the configuration key
* @param errorMessage
* an explicit free-text error message describing the problem
*/
public ValidationError(@Nonnull final ErrorType type, final ConfigurationContext<?, ?> context,
final ConfigKey<?> configKey, final Object value, @Nonnull final String errorMessage) {
this(type, (context == null ? new ConfigurationContext[0] : new ConfigurationContext[] { context }),
(configKey == null ? new ConfigKey[0] : new ConfigKey[] { configKey }), value, errorMessage);
}
/**
* Gets the {@link ErrorType} of this error.
*
* @return the non-{@code null} error type
*/
public final ErrorType getType() {
return type;
}
/**
* Sets the configuration context outside of construction.
*
* @param context
* the affected, non-{@code null} {@link ConfigurationContext}
*/
public final void addConfigurationContext(@Nonnull final ConfigurationContext<?, ?> context) {
contexts.add(Objects.requireNonNull(context));
}
/**
* Gets the {@link ConfigurationContext}s affected by this error.
*
* @return an array of {@link ConfigurationContext}s which may be empty
*/
public final ConfigurationContext<?, ?>[] getConfigurationContexts() {
return contexts.toArray(new ConfigurationContext<?, ?>[contexts.size()]);
}
/**
* Gets the optional affected {@link ConfigKey} within the {@link #getContext() context}.
*
* @return the affected {@link ConfigKey}, or {@code null} if undefined
*/
public final ConfigKey<?>[] getConfigKeys() {
return configKeys;
}
/**
* Gets the optional value affected to the {@link #getConfigKeys() configuration key}.
*
* @return the set value, or {@code null} if either no configuration key is set or no value is defined
*/
public final Object getValue() {
return value;
}
/**
* Gets an the error message describing the problem.
*
* @return the error message
*/
public final String getErrorMessage() {
return errorMessage;
}
}