Unverified Commit bc2293e1 authored by TAO Xinxiu (Isabelle)'s avatar TAO Xinxiu (Isabelle) Committed by GitHub
Browse files

Support the workflow variable to be defined as optional (#3777)

parent 3ad1e4a3
...@@ -23,23 +23,30 @@ ...@@ -23,23 +23,30 @@
* If needed, contact us to obtain a release under GPL Version 2 or 3 * If needed, contact us to obtain a release under GPL Version 2 or 3
* or a different license than the AGPL. * or a different license than the AGPL.
*/ */
package org.ow2.proactive.scheduler.common.job.factories.spi.model.factory; package org.ow2.proactive.scheduler.common.job.factories.spi.model.converter;
import org.ow2.proactive.scheduler.common.job.factories.spi.model.converter.Converter; import org.apache.commons.lang3.StringUtils;
import org.ow2.proactive.scheduler.common.job.factories.spi.model.exceptions.ModelSyntaxException; import org.ow2.proactive.scheduler.common.job.factories.spi.model.exceptions.ConversionException;
import org.ow2.proactive.scheduler.common.job.factories.spi.model.validator.OptionalValidator;
import org.ow2.proactive.scheduler.common.job.factories.spi.model.validator.URIValidator;
import org.ow2.proactive.scheduler.common.job.factories.spi.model.validator.Validator;
public class OptionalURIParserValidator extends URIParserValidator { /**
* Accept the blank string by converting it to null. When the value is not blank, use its specific converter to convert its value.
* @param <T>
*/
public class NullConverter<T> implements Converter<T> {
Converter<T> converter;
public OptionalURIParserValidator(String model) throws ModelSyntaxException { public NullConverter(Converter<T> converter) {
super(model, ModelType.OPTIONAL_URI); this.converter = converter;
} }
@Override @Override
protected Validator<String> createValidator(String model, Converter<String> converter) { public T convert(String parameterValue) throws ConversionException {
return new OptionalValidator<>(new URIValidator()); // When the parameter value is not provided, it's null. Otherwise, use its proper converter
if (StringUtils.isBlank(parameterValue)) {
return null;
} else {
return converter.convert(parameterValue);
}
} }
} }
...@@ -91,7 +91,7 @@ public abstract class BaseParserValidator<T> implements ParserValidator<T> { ...@@ -91,7 +91,7 @@ public abstract class BaseParserValidator<T> implements ParserValidator<T> {
* Returns the class used by this parser to convert string parameter values. * Returns the class used by this parser to convert string parameter values.
* @return class * @return class
*/ */
protected Class getClassType() { protected Class<?> getClassType() {
return type.getClassType(); return type.getClassType();
} }
...@@ -159,9 +159,6 @@ public abstract class BaseParserValidator<T> implements ParserValidator<T> { ...@@ -159,9 +159,6 @@ public abstract class BaseParserValidator<T> implements ParserValidator<T> {
@Override @Override
public T parseAndValidate(String parameterValue, ModelValidatorContext context) public T parseAndValidate(String parameterValue, ModelValidatorContext context)
throws ConversionException, ValidationException, ModelSyntaxException { throws ConversionException, ValidationException, ModelSyntaxException {
if (parameterValue == null) {
throw new ConversionException(parameterValue, getClassType());
}
Converter<T> converter = createConverter(model); Converter<T> converter = createConverter(model);
return createValidator(model, converter).validate(converter.convert(parameterValue), context); return createValidator(model, converter).validate(converter.convert(parameterValue), context);
} }
......
...@@ -49,32 +49,28 @@ public enum ModelType { ...@@ -49,32 +49,28 @@ public enum ModelType {
SHORT(ShortParserValidator.class, Short.class), SHORT(ShortParserValidator.class, Short.class),
SPEL(SPELParserValidator.class, String.class), SPEL(SPELParserValidator.class, String.class),
URI(URIParserValidator.class, String.class), URI(URIParserValidator.class, String.class),
OPTIONAL_URI(OptionalURIParserValidator.class, String.class),
URL(URLParserValidator.class, String.class), URL(URLParserValidator.class, String.class),
OPTIONAL_URL(OptionalURLParserValidator.class, String.class),
HIDDEN(HiddenParserValidator.class, String.class), HIDDEN(HiddenParserValidator.class, String.class),
CREDENTIAL(CredentialParserValidator.class, String.class), CREDENTIAL(CredentialParserValidator.class, String.class),
USER_FILE(UserFileParserValidator.class, String.class), USER_FILE(UserFileParserValidator.class, String.class),
OPTIONAL_USER_FILE(OptionalUserFileParserValidator.class, String.class), GLOBAL_FILE(GlobalFileParserValidator.class, String.class);
GLOBAL_FILE(GlobalFileParserValidator.class, String.class),
OPTIONAL_GLOBAL_FILE(OptionalGlobalFileParserValidator.class, String.class);
// The parser validator of the model type // The parser validator of the model type
private Class typeParserValidator; private Class<?> typeParserValidator;
// The parameter string value is expected to be converted to which class by its parser // The parameter string value is expected to be converted to which class by its parser
private Class classType; private Class<?> classType;
ModelType(Class typeParserValidator, Class classType) { ModelType(Class<?> typeParserValidator, Class<?> classType) {
this.typeParserValidator = typeParserValidator; this.typeParserValidator = typeParserValidator;
this.classType = classType; this.classType = classType;
} }
public Class getTypeParserValidator() { public Class<?> getTypeParserValidator() {
return typeParserValidator; return typeParserValidator;
} }
public Class getClassType() { public Class<?> getClassType() {
return classType; return classType;
} }
} }
/*
* ProActive Parallel Suite(TM):
* The Open Source library for parallel and distributed
* Workflows & Scheduling, Orchestration, Cloud Automation
* and Big Data Analysis on Enterprise Grids & Clouds.
*
* Copyright (c) 2007 - 2017 ActiveEon
* Contact: contact@activeeon.com
*
* This library is free software: you can redistribute it and/or
* modify it under the terms of the GNU Affero General Public License
* as published by the Free Software Foundation: version 3 of
* the License.
*
* 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If needed, contact us to obtain a release under GPL Version 2 or 3
* or a different license than the AGPL.
*/
package org.ow2.proactive.scheduler.common.job.factories.spi.model.factory;
import org.ow2.proactive.scheduler.common.job.factories.spi.model.converter.Converter;
import org.ow2.proactive.scheduler.common.job.factories.spi.model.exceptions.ModelSyntaxException;
import org.ow2.proactive.scheduler.common.job.factories.spi.model.validator.GlobalFileValidator;
import org.ow2.proactive.scheduler.common.job.factories.spi.model.validator.OptionalValidator;
import org.ow2.proactive.scheduler.common.job.factories.spi.model.validator.Validator;
public class OptionalGlobalFileParserValidator extends GlobalFileParserValidator {
public OptionalGlobalFileParserValidator(String model) throws ModelSyntaxException {
super(model, ModelType.OPTIONAL_GLOBAL_FILE);
}
@Override
protected Validator<String> createValidator(String model, Converter<String> converter) {
return new OptionalValidator<>(new GlobalFileValidator());
}
}
...@@ -25,21 +25,46 @@ ...@@ -25,21 +25,46 @@
*/ */
package org.ow2.proactive.scheduler.common.job.factories.spi.model.factory; package org.ow2.proactive.scheduler.common.job.factories.spi.model.factory;
import static org.ow2.proactive.scheduler.common.job.factories.spi.model.validator.ModelValidator.OPTIONAL_VARIABLE_SUFFIX;
import org.apache.commons.lang3.StringUtils;
import org.ow2.proactive.scheduler.common.job.factories.spi.model.converter.Converter; import org.ow2.proactive.scheduler.common.job.factories.spi.model.converter.Converter;
import org.ow2.proactive.scheduler.common.job.factories.spi.model.converter.NullConverter;
import org.ow2.proactive.scheduler.common.job.factories.spi.model.exceptions.ModelSyntaxException; import org.ow2.proactive.scheduler.common.job.factories.spi.model.exceptions.ModelSyntaxException;
import org.ow2.proactive.scheduler.common.job.factories.spi.model.validator.ModelValidator;
import org.ow2.proactive.scheduler.common.job.factories.spi.model.validator.OptionalValidator; import org.ow2.proactive.scheduler.common.job.factories.spi.model.validator.OptionalValidator;
import org.ow2.proactive.scheduler.common.job.factories.spi.model.validator.UserFileValidator;
import org.ow2.proactive.scheduler.common.job.factories.spi.model.validator.Validator; import org.ow2.proactive.scheduler.common.job.factories.spi.model.validator.Validator;
import com.google.common.annotations.VisibleForTesting;
/**
* @author ActiveEon Team
* @since 14/08/19
*/
public class OptionalParserValidator<T> extends BaseParserValidator<T> {
@VisibleForTesting
BaseParserValidator<T> parentParserValidator;
public class OptionalUserFileParserValidator extends UserFileParserValidator { public OptionalParserValidator(String model, ModelType type) throws ModelSyntaxException {
super(removeSuffix(model), type, ".+");
if (!model.endsWith(OPTIONAL_VARIABLE_SUFFIX)) {
throw new ModelSyntaxException("Optional Model should end with \"?\"");
}
this.parentParserValidator = (BaseParserValidator<T>) ModelValidator.newParserValidator(type, this.model);
}
public OptionalUserFileParserValidator(String model) throws ModelSyntaxException { @Override
super(model, ModelType.OPTIONAL_USER_FILE); protected Converter<T> createConverter(String model) throws ModelSyntaxException {
return new NullConverter<>(parentParserValidator.createConverter(this.model));
} }
@Override @Override
protected Validator<String> createValidator(String model, Converter<String> converter) { protected Validator<T> createValidator(String model, Converter<T> converter) throws ModelSyntaxException {
return new OptionalValidator<>(new UserFileValidator()); return new OptionalValidator<>(parentParserValidator.createValidator(this.model, converter));
}
private static String removeSuffix(String model) {
return StringUtils.removeEnd(model, OPTIONAL_VARIABLE_SUFFIX);
} }
} }
/*
* ProActive Parallel Suite(TM):
* The Open Source library for parallel and distributed
* Workflows & Scheduling, Orchestration, Cloud Automation
* and Big Data Analysis on Enterprise Grids & Clouds.
*
* Copyright (c) 2007 - 2017 ActiveEon
* Contact: contact@activeeon.com
*
* This library is free software: you can redistribute it and/or
* modify it under the terms of the GNU Affero General Public License
* as published by the Free Software Foundation: version 3 of
* the License.
*
* 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If needed, contact us to obtain a release under GPL Version 2 or 3
* or a different license than the AGPL.
*/
package org.ow2.proactive.scheduler.common.job.factories.spi.model.factory;
import org.ow2.proactive.scheduler.common.job.factories.spi.model.converter.Converter;
import org.ow2.proactive.scheduler.common.job.factories.spi.model.exceptions.ModelSyntaxException;
import org.ow2.proactive.scheduler.common.job.factories.spi.model.validator.OptionalValidator;
import org.ow2.proactive.scheduler.common.job.factories.spi.model.validator.URLValidator;
import org.ow2.proactive.scheduler.common.job.factories.spi.model.validator.Validator;
public class OptionalURLParserValidator extends URLParserValidator {
public OptionalURLParserValidator(String model) throws ModelSyntaxException {
super(model, ModelType.OPTIONAL_URL);
}
@Override
protected Validator<String> createValidator(String model, Converter<String> converter) {
return new OptionalValidator<>(new URLValidator());
}
}
...@@ -38,10 +38,6 @@ public class URIParserValidator extends BaseParserValidator<String> { ...@@ -38,10 +38,6 @@ public class URIParserValidator extends BaseParserValidator<String> {
super(model, ModelType.URI); super(model, ModelType.URI);
} }
public URIParserValidator(String model, ModelType type) throws ModelSyntaxException {
super(model, type);
}
@Override @Override
protected Converter<String> createConverter(String model) throws ModelSyntaxException { protected Converter<String> createConverter(String model) throws ModelSyntaxException {
return new IdentityConverter(); return new IdentityConverter();
......
...@@ -26,29 +26,15 @@ ...@@ -26,29 +26,15 @@
package org.ow2.proactive.scheduler.common.job.factories.spi.model.validator; package org.ow2.proactive.scheduler.common.job.factories.spi.model.validator;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.List;
import org.ow2.proactive.scheduler.common.job.factories.spi.model.ModelValidatorContext; import org.ow2.proactive.scheduler.common.job.factories.spi.model.ModelValidatorContext;
import org.ow2.proactive.scheduler.common.job.factories.spi.model.exceptions.ModelSyntaxException; import org.ow2.proactive.scheduler.common.job.factories.spi.model.exceptions.ModelSyntaxException;
import org.ow2.proactive.scheduler.common.job.factories.spi.model.exceptions.ValidationException; import org.ow2.proactive.scheduler.common.job.factories.spi.model.exceptions.ValidationException;
import org.ow2.proactive.scheduler.common.job.factories.spi.model.factory.BooleanParserValidator;
import org.ow2.proactive.scheduler.common.job.factories.spi.model.factory.CRONParserValidator;
import org.ow2.proactive.scheduler.common.job.factories.spi.model.factory.CatalogObjectParserValidator;
import org.ow2.proactive.scheduler.common.job.factories.spi.model.factory.DateTimeParserValidator;
import org.ow2.proactive.scheduler.common.job.factories.spi.model.factory.DoubleParserValidator;
import org.ow2.proactive.scheduler.common.job.factories.spi.model.factory.FloatParserValidator;
import org.ow2.proactive.scheduler.common.job.factories.spi.model.factory.IntegerParserValidator;
import org.ow2.proactive.scheduler.common.job.factories.spi.model.factory.JSONParserValidator;
import org.ow2.proactive.scheduler.common.job.factories.spi.model.factory.ListParserValidator;
import org.ow2.proactive.scheduler.common.job.factories.spi.model.factory.LongParserValidator;
import org.ow2.proactive.scheduler.common.job.factories.spi.model.factory.ModelFromURLParserValidator;
import org.ow2.proactive.scheduler.common.job.factories.spi.model.factory.ModelType; import org.ow2.proactive.scheduler.common.job.factories.spi.model.factory.ModelType;
import org.ow2.proactive.scheduler.common.job.factories.spi.model.factory.NotEmptyParserValidator; import org.ow2.proactive.scheduler.common.job.factories.spi.model.factory.OptionalParserValidator;
import org.ow2.proactive.scheduler.common.job.factories.spi.model.factory.ParserValidator; import org.ow2.proactive.scheduler.common.job.factories.spi.model.factory.ParserValidator;
import org.ow2.proactive.scheduler.common.job.factories.spi.model.factory.RegexpParserValidator;
import org.ow2.proactive.scheduler.common.job.factories.spi.model.factory.SPELParserValidator;
import org.ow2.proactive.scheduler.common.job.factories.spi.model.factory.ShortParserValidator;
import org.ow2.proactive.scheduler.common.job.factories.spi.model.factory.URIParserValidator;
import org.ow2.proactive.scheduler.common.job.factories.spi.model.factory.URLParserValidator;
import com.google.common.base.Strings; import com.google.common.base.Strings;
...@@ -59,6 +45,10 @@ public class ModelValidator implements Validator<String> { ...@@ -59,6 +45,10 @@ public class ModelValidator implements Validator<String> {
public static final String PREFIX = "PA:"; public static final String PREFIX = "PA:";
public static final String OPTIONAL_VARIABLE_SUFFIX = "?";
public static final List<ModelType> NEVER_OPTIONAL_MODEL = Arrays.asList(ModelType.NOT_EMPTY_STRING);
public ModelValidator(String model) { public ModelValidator(String model) {
if (Strings.isNullOrEmpty(model)) { if (Strings.isNullOrEmpty(model)) {
throw new IllegalArgumentException("Model cannot be empty"); throw new IllegalArgumentException("Model cannot be empty");
...@@ -69,7 +59,7 @@ public class ModelValidator implements Validator<String> { ...@@ -69,7 +59,7 @@ public class ModelValidator implements Validator<String> {
@Override @Override
public String validate(String parameterValue, ModelValidatorContext context) throws ValidationException { public String validate(String parameterValue, ModelValidatorContext context) throws ValidationException {
try { try {
ParserValidator validator = createParserValidator(); ParserValidator<?> validator = createParserValidator();
if (validator != null) { if (validator != null) {
validator.parseAndValidate(parameterValue, context); validator.parseAndValidate(parameterValue, context);
} }
...@@ -87,7 +77,7 @@ public class ModelValidator implements Validator<String> { ...@@ -87,7 +77,7 @@ public class ModelValidator implements Validator<String> {
* @return a registered model parser or null if no registered parser could be found. * @return a registered model parser or null if no registered parser could be found.
* @throws ModelSyntaxException if an error occurred during the parser creation * @throws ModelSyntaxException if an error occurred during the parser creation
*/ */
protected ParserValidator createParserValidator() throws ModelSyntaxException { protected ParserValidator<?> createParserValidator() throws ModelSyntaxException {
String uppercaseModel = model.toUpperCase(); String uppercaseModel = model.toUpperCase();
if (!uppercaseModel.startsWith(PREFIX)) { if (!uppercaseModel.startsWith(PREFIX)) {
return null; return null;
...@@ -95,29 +85,43 @@ public class ModelValidator implements Validator<String> { ...@@ -95,29 +85,43 @@ public class ModelValidator implements Validator<String> {
uppercaseModel = removePrefix(uppercaseModel); uppercaseModel = removePrefix(uppercaseModel);
for (ModelType type : ModelType.values()) { for (ModelType type : ModelType.values()) {
if (uppercaseModel.startsWith(type.name())) { if (uppercaseModel.startsWith(type.name())) {
try { String modelNoPrefix = removePrefix(model);
return (ParserValidator) type.getTypeParserValidator() if (uppercaseModel.endsWith(OPTIONAL_VARIABLE_SUFFIX)) {
.getDeclaredConstructor(String.class) if (NEVER_OPTIONAL_MODEL.contains(type)) {
.newInstance(removePrefix(model)); throw new ModelSyntaxException(String.format("Invalid model '%s': The type '%s' is disallowed to be optional",
} catch (InstantiationException | IllegalAccessException | InvocationTargetException model,
| NoSuchMethodException e) { type));
if (e instanceof InvocationTargetException) {
// unwrap InvocationTargetException to get more specific error message
Throwable exceptionCause = e.getCause();
if (exceptionCause instanceof ModelSyntaxException) {
throw (ModelSyntaxException) exceptionCause;
}
} }
throw new ModelSyntaxException(String.format("Error during create the parser [%s] for the model [%s].", return new OptionalParserValidator<>(modelNoPrefix, type);
type.getTypeParserValidator().getSimpleName(), } else {
model), return newParserValidator(type, modelNoPrefix);
e);
} }
} }
} }
throw new ModelSyntaxException("Unrecognized type in model '" + model + "'"); throw new ModelSyntaxException("Unrecognized type in model '" + model + "'");
} }
public static ParserValidator<?> newParserValidator(ModelType type, String model) throws ModelSyntaxException {
try {
return (ParserValidator<?>) type.getTypeParserValidator()
.getDeclaredConstructor(String.class)
.newInstance(model);
} catch (InstantiationException | IllegalAccessException | InvocationTargetException
| NoSuchMethodException e) {
if (e instanceof InvocationTargetException) {
// unwrap InvocationTargetException to get more specific error message
Throwable exceptionCause = e.getCause();
if (exceptionCause instanceof ModelSyntaxException) {
throw (ModelSyntaxException) exceptionCause;
}
}
throw new ModelSyntaxException(String.format("Error during create the parser [%s] for the model [%s].",
type.getTypeParserValidator().getSimpleName(),
model),
e);
}
}
private String removePrefix(String model) { private String removePrefix(String model) {
return model.substring(PREFIX.length()); return model.substring(PREFIX.length());
} }
......
...@@ -44,7 +44,7 @@ public class OptionalValidator<T> implements Validator<T> { ...@@ -44,7 +44,7 @@ public class OptionalValidator<T> implements Validator<T> {
@Override @Override
public T validate(T parameterValue, ModelValidatorContext context) throws ValidationException { public T validate(T parameterValue, ModelValidatorContext context) throws ValidationException {
// When the parameter value is not provided, it's validated. Otherwise, use its proper validator // When the parameter value is not provided, it's validated. Otherwise, use its proper validator
if (StringUtils.isBlank(parameterValue.toString())) { if (parameterValue == null || StringUtils.isBlank(parameterValue.toString())) {
return parameterValue; return parameterValue;
} else { } else {
return validator.validate(parameterValue, context); return validator.validate(parameterValue, context);
......
/*
* ProActive Parallel Suite(TM):
* The Open Source library for parallel and distributed
* Workflows & Scheduling, Orchestration, Cloud Automation
* and Big Data Analysis on Enterprise Grids & Clouds.
*
* Copyright (c) 2007 - 2017 ActiveEon
* Contact: contact@activeeon.com
*
* This library is free software: you can redistribute it and/or
* modify it under the terms of the GNU Affero General Public License
* as published by the Free Software Foundation: version 3 of
* the License.
*
* 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If needed, contact us to obtain a release under GPL Version 2 or 3
* or a different license than the AGPL.
*/
package org.ow2.proactive.scheduler.common.job.factories.spi.model.factory;
import static org.mockito.Mockito.when;
import static org.ow2.proactive.scheduler.common.SchedulerConstants.GLOBALSPACE_NAME;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.ow2.proactive.scheduler.common.SchedulerSpaceInterface;
import org.ow2.proactive.scheduler.common.exception.NotConnectedException;
import org.ow2.proactive.scheduler.common.exception.PermissionException;
import org.ow2.proactive.scheduler.common.job.factories.spi.model.ModelValidatorContext;
import org.ow2.proactive.scheduler.common.job.factories.spi.model.exceptions.ConversionException;
import org.ow2.proactive.scheduler.common.job.factories.spi.model.exceptions.ModelSyntaxException;
import org.ow2.proactive.scheduler.common.job.factories.spi.model.exceptions.ValidationException;
public class OptionalGlobalFileParserValidatorTest {
final String existGlobalFilePath = "global folder/exist-file.txt";
final String notExistGlobalFilePath = "not-exist-file.png";
@Mock
private ModelValidatorContext context;
@Mock
private SchedulerSpaceInterface schedulerSpaceInterface;
@Before
public void init() throws NotConnectedException, PermissionException {