mirror of
https://github.com/theonedev/onedev.git
synced 2025-12-10 20:24:15 -06:00
chore: Various bean validation improvements
This commit is contained in:
parent
a6fb15d7a9
commit
77cf1da58c
4
pom.xml
4
pom.xml
@ -641,8 +641,8 @@
|
||||
</repository>
|
||||
</repositories>
|
||||
<properties>
|
||||
<commons.version>3.0.13</commons.version>
|
||||
<agent.version>2.2.18</agent.version>
|
||||
<commons.version>3.0.14</commons.version>
|
||||
<agent.version>2.2.19</agent.version>
|
||||
<slf4j.version>2.0.9</slf4j.version>
|
||||
<logback.version>1.4.14</logback.version>
|
||||
<antlr.version>4.7.2</antlr.version>
|
||||
|
||||
@ -3,6 +3,7 @@ package io.onedev.server;
|
||||
import static com.google.common.collect.Lists.newArrayList;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
@ -23,6 +24,9 @@ import javax.persistence.OneToOne;
|
||||
import javax.persistence.Transient;
|
||||
import javax.persistence.Version;
|
||||
import javax.validation.Configuration;
|
||||
import javax.validation.Path;
|
||||
import javax.validation.Path.Node;
|
||||
import javax.validation.TraversableResolver;
|
||||
import javax.validation.Validation;
|
||||
import javax.validation.Validator;
|
||||
import javax.validation.ValidatorFactory;
|
||||
@ -71,6 +75,7 @@ import io.onedev.commons.utils.ExceptionUtils;
|
||||
import io.onedev.commons.utils.StringUtils;
|
||||
import io.onedev.k8shelper.KubernetesHelper;
|
||||
import io.onedev.k8shelper.OsInfo;
|
||||
import io.onedev.server.annotation.Shallow;
|
||||
import io.onedev.server.attachment.AttachmentManager;
|
||||
import io.onedev.server.attachment.DefaultAttachmentManager;
|
||||
import io.onedev.server.buildspec.BuildSpecSchemaResource;
|
||||
@ -382,6 +387,7 @@ import io.onedev.server.util.xstream.ReflectionConverter;
|
||||
import io.onedev.server.util.xstream.StringConverter;
|
||||
import io.onedev.server.util.xstream.VersionedDocumentConverter;
|
||||
import io.onedev.server.validation.MessageInterpolator;
|
||||
import io.onedev.server.validation.ShallowValidatorProvider;
|
||||
import io.onedev.server.validation.ValidatorProvider;
|
||||
import io.onedev.server.web.DefaultUrlManager;
|
||||
import io.onedev.server.web.DefaultWicketFilter;
|
||||
@ -446,8 +452,31 @@ public class CoreModule extends AbstractPluginModule {
|
||||
.messageInterpolator(new MessageInterpolator());
|
||||
return configuration.buildValidatorFactory();
|
||||
}).in(Singleton.class);
|
||||
|
||||
bind(ValidatorFactory.class).annotatedWith(Shallow.class).toProvider(() -> {
|
||||
Configuration<?> configuration = Validation
|
||||
.byDefaultProvider()
|
||||
.configure()
|
||||
.traversableResolver(new TraversableResolver() {
|
||||
|
||||
@Override
|
||||
public boolean isReachable(Object traversableObject, Node traversableProperty,
|
||||
Class<?> rootBeanType, Path pathToTraversableObject, ElementType elementType) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCascadable(Object traversableObject, Node traversableProperty,
|
||||
Class<?> rootBeanType, Path pathToTraversableObject, ElementType elementType) {
|
||||
return false;
|
||||
}
|
||||
})
|
||||
.messageInterpolator(new MessageInterpolator());
|
||||
return configuration.buildValidatorFactory();
|
||||
}).in(Singleton.class);
|
||||
|
||||
bind(Validator.class).toProvider(ValidatorProvider.class).in(Singleton.class);
|
||||
bind(Validator.class).annotatedWith(Shallow.class).toProvider(ShallowValidatorProvider.class).in(Singleton.class);
|
||||
|
||||
configurePersistence();
|
||||
configureSecurity();
|
||||
|
||||
@ -10,6 +10,7 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.ObjectStreamException;
|
||||
import java.io.Serializable;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.InetAddress;
|
||||
import java.net.ServerSocket;
|
||||
@ -365,6 +366,10 @@ public class OneDev extends AbstractPlugin implements Serializable, Runnable {
|
||||
return AppLoader.getInstance(type);
|
||||
}
|
||||
|
||||
public static <T> T getInstance(Class<T> type, Class<? extends Annotation> annotationClass) {
|
||||
return AppLoader.getInstance(type, annotationClass);
|
||||
}
|
||||
|
||||
public static <T> Set<T> getExtensions(Class<T> extensionPoint) {
|
||||
return AppLoader.getExtensions(extensionPoint);
|
||||
}
|
||||
|
||||
@ -14,4 +14,5 @@ public @interface DependsOn {
|
||||
String value() default "";
|
||||
|
||||
boolean inverse() default false;
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,12 @@
|
||||
package io.onedev.server.annotation;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
import com.google.inject.BindingAnnotation;
|
||||
|
||||
@BindingAnnotation
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface Shallow {
|
||||
|
||||
}
|
||||
@ -1,10 +1,42 @@
|
||||
package io.onedev.server.buildspec;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.Stack;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.validation.ConstraintValidatorContext;
|
||||
import javax.validation.ConstraintViolation;
|
||||
import javax.validation.Valid;
|
||||
import javax.validation.ValidationException;
|
||||
import javax.validation.Validator;
|
||||
|
||||
import org.apache.commons.lang3.SerializationUtils;
|
||||
import org.apache.wicket.Component;
|
||||
import org.yaml.snakeyaml.DumperOptions.FlowStyle;
|
||||
import org.yaml.snakeyaml.nodes.MappingNode;
|
||||
import org.yaml.snakeyaml.nodes.Node;
|
||||
import org.yaml.snakeyaml.nodes.NodeTuple;
|
||||
import org.yaml.snakeyaml.nodes.ScalarNode;
|
||||
import org.yaml.snakeyaml.nodes.SequenceNode;
|
||||
import org.yaml.snakeyaml.nodes.Tag;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.cache.LoadingCache;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import io.onedev.commons.codeassist.InputCompletion;
|
||||
import io.onedev.commons.codeassist.InputStatus;
|
||||
import io.onedev.commons.codeassist.InputSuggestion;
|
||||
@ -33,21 +65,6 @@ import io.onedev.server.validation.Validatable;
|
||||
import io.onedev.server.web.page.project.blob.ProjectBlobPage;
|
||||
import io.onedev.server.web.util.SuggestionUtils;
|
||||
import io.onedev.server.web.util.WicketUtils;
|
||||
import org.apache.commons.lang3.SerializationUtils;
|
||||
import org.apache.wicket.Component;
|
||||
import org.yaml.snakeyaml.DumperOptions.FlowStyle;
|
||||
import org.yaml.snakeyaml.nodes.*;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.validation.ConstraintValidatorContext;
|
||||
import javax.validation.ConstraintViolation;
|
||||
import javax.validation.ValidationException;
|
||||
import javax.validation.Validator;
|
||||
import java.io.Serializable;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
@Editable
|
||||
@ClassValidating
|
||||
@ -105,6 +122,7 @@ public class BuildSpec implements Serializable, Validatable {
|
||||
private transient Map<String, JobProperty> propertyMap;
|
||||
|
||||
@Editable
|
||||
@Valid
|
||||
public List<Job> getJobs() {
|
||||
return jobs;
|
||||
}
|
||||
@ -115,6 +133,7 @@ public class BuildSpec implements Serializable, Validatable {
|
||||
}
|
||||
|
||||
@Editable
|
||||
@Valid
|
||||
public List<StepTemplate> getStepTemplates() {
|
||||
return stepTemplates;
|
||||
}
|
||||
@ -125,6 +144,7 @@ public class BuildSpec implements Serializable, Validatable {
|
||||
}
|
||||
|
||||
@Editable
|
||||
@Valid
|
||||
public List<Service> getServices() {
|
||||
return services;
|
||||
}
|
||||
@ -135,6 +155,7 @@ public class BuildSpec implements Serializable, Validatable {
|
||||
}
|
||||
|
||||
@Editable
|
||||
@Valid
|
||||
public List<JobProperty> getProperties() {
|
||||
return properties;
|
||||
}
|
||||
@ -145,6 +166,7 @@ public class BuildSpec implements Serializable, Validatable {
|
||||
}
|
||||
|
||||
@Editable
|
||||
@Valid
|
||||
public List<Import> getImports() {
|
||||
return imports;
|
||||
}
|
||||
@ -271,27 +293,10 @@ public class BuildSpec implements Serializable, Validatable {
|
||||
return isValid;
|
||||
}
|
||||
|
||||
private Validator getValidator() {
|
||||
return OneDev.getInstance(Validator.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid(ConstraintValidatorContext context) {
|
||||
boolean isValid = true;
|
||||
|
||||
int index = 0;
|
||||
for (Job job: jobs) {
|
||||
for (ConstraintViolation<Job> violation: getValidator().validate(job)) {
|
||||
context.buildConstraintViolationWithTemplate(violation.getMessage())
|
||||
.addPropertyNode(PROP_JOBS)
|
||||
.addPropertyNode(violation.getPropertyPath().toString())
|
||||
.inIterable().atIndex(index)
|
||||
.addConstraintViolation();
|
||||
isValid = false;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
Set<String> jobNames = new HashSet<>();
|
||||
for (Job job: jobs) {
|
||||
if (!jobNames.add(job.getName())) {
|
||||
@ -300,19 +305,6 @@ public class BuildSpec implements Serializable, Validatable {
|
||||
isValid = false;
|
||||
}
|
||||
}
|
||||
|
||||
index = 0;
|
||||
for (Service service: services) {
|
||||
for (ConstraintViolation<Service> violation: getValidator().validate(service)) {
|
||||
context.buildConstraintViolationWithTemplate(violation.getMessage())
|
||||
.addPropertyNode(PROP_SERVICES)
|
||||
.addPropertyNode(violation.getPropertyPath().toString())
|
||||
.inIterable().atIndex(index)
|
||||
.addConstraintViolation();
|
||||
isValid = false;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
Set<String> serviceNames = new HashSet<>();
|
||||
for (Service service: services) {
|
||||
@ -322,19 +314,6 @@ public class BuildSpec implements Serializable, Validatable {
|
||||
isValid = false;
|
||||
}
|
||||
}
|
||||
|
||||
index = 0;
|
||||
for (StepTemplate stepTemplate: stepTemplates) {
|
||||
for (ConstraintViolation<StepTemplate> violation: getValidator().validate(stepTemplate)) {
|
||||
context.buildConstraintViolationWithTemplate(violation.getMessage())
|
||||
.addPropertyNode(PROP_STEP_TEMPLATES)
|
||||
.addPropertyNode(violation.getPropertyPath().toString())
|
||||
.inIterable().atIndex(index)
|
||||
.addConstraintViolation();
|
||||
isValid = false;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
Set<String> stepTemplateNames = new HashSet<>();
|
||||
for (StepTemplate template: stepTemplates) {
|
||||
@ -344,19 +323,6 @@ public class BuildSpec implements Serializable, Validatable {
|
||||
isValid = false;
|
||||
}
|
||||
}
|
||||
|
||||
index = 0;
|
||||
for (JobProperty property: properties) {
|
||||
for (ConstraintViolation<JobProperty> violation: getValidator().validate(property)) {
|
||||
context.buildConstraintViolationWithTemplate(violation.getMessage())
|
||||
.addPropertyNode(PROP_PROPERTIES)
|
||||
.addPropertyNode(violation.getPropertyPath().toString())
|
||||
.inIterable().atIndex(index)
|
||||
.addConstraintViolation();
|
||||
isValid = false;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
Set<String> propertyNames = new HashSet<>();
|
||||
for (JobProperty property : properties) {
|
||||
@ -366,20 +332,6 @@ public class BuildSpec implements Serializable, Validatable {
|
||||
isValid = false;
|
||||
}
|
||||
}
|
||||
|
||||
index = 0;
|
||||
for (Import aImport: getImports()) {
|
||||
Validator validator = OneDev.getInstance(Validator.class);
|
||||
for (ConstraintViolation<Import> violation: validator.validate(aImport)) {
|
||||
context.buildConstraintViolationWithTemplate(violation.getMessage())
|
||||
.addPropertyNode(PROP_IMPORTS)
|
||||
.addPropertyNode(violation.getPropertyPath().toString())
|
||||
.inIterable().atIndex(index)
|
||||
.addConstraintViolation();
|
||||
isValid = false;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
Set<String> importProjectAndRevisions = new HashSet<>();
|
||||
for (Import aImport: imports) {
|
||||
@ -2335,4 +2287,113 @@ public class BuildSpec implements Serializable, Validatable {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private void migrate42(VersionedYamlDoc doc, Stack<Integer> versions) {
|
||||
migrate42_processNode(doc);
|
||||
}
|
||||
|
||||
private void migrate42_processNode(Node node) {
|
||||
if (node instanceof MappingNode) {
|
||||
MappingNode mappingNode = (MappingNode) node;
|
||||
|
||||
if (mappingNode.getTag() != null && !mappingNode.getTag().equals(Tag.MAP)
|
||||
&& mappingNode.getTag().getValue().startsWith("!")) {
|
||||
var tagValue = mappingNode.getTag().getValue().substring(1);
|
||||
boolean hasTypeTuple = false;
|
||||
for (NodeTuple tuple : mappingNode.getValue()) {
|
||||
if (tuple.getKeyNode() instanceof ScalarNode) {
|
||||
ScalarNode keyNode = (ScalarNode) tuple.getKeyNode();
|
||||
if ("type".equals(keyNode.getValue())) {
|
||||
hasTypeTuple = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasTypeTuple) {
|
||||
mappingNode.getValue().add(0, new NodeTuple(
|
||||
new ScalarNode(Tag.STR, "type"),
|
||||
new ScalarNode(Tag.STR, tagValue)));
|
||||
}
|
||||
mappingNode.setTag(Tag.MAP);
|
||||
}
|
||||
|
||||
for (NodeTuple tuple : mappingNode.getValue()) {
|
||||
migrate42_processNode(tuple.getKeyNode());
|
||||
migrate42_processNode(tuple.getValueNode());
|
||||
}
|
||||
} else if (node instanceof SequenceNode) {
|
||||
SequenceNode sequenceNode = (SequenceNode) node;
|
||||
for (Node item : sequenceNode.getValue()) {
|
||||
migrate42_processNode(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private void migrate43(VersionedYamlDoc doc, Stack<Integer> versions) {
|
||||
for (NodeTuple specTuple: doc.getValue()) {
|
||||
String specKey = ((ScalarNode)specTuple.getKeyNode()).getValue();
|
||||
if (specKey.equals("jobs")) {
|
||||
SequenceNode jobsNode = (SequenceNode) specTuple.getValueNode();
|
||||
for (Node jobsNodeItem: jobsNode.getValue()) {
|
||||
MappingNode jobNode = (MappingNode) jobsNodeItem;
|
||||
boolean hasRetryCondition = false;
|
||||
for (var itJobTuple = jobNode.getValue().iterator(); itJobTuple.hasNext();) {
|
||||
var jobTuple = itJobTuple.next();
|
||||
var keyNode = (ScalarNode) jobTuple.getKeyNode();
|
||||
if (keyNode.getValue().equals("retryCondition")) {
|
||||
var valueNode = (ScalarNode) jobTuple.getValueNode();
|
||||
if (StringUtils.isBlank(valueNode.getValue())) {
|
||||
itJobTuple.remove();
|
||||
} else {
|
||||
hasRetryCondition = true;
|
||||
}
|
||||
break;
|
||||
} else if (keyNode.getValue().equals("triggers")) {
|
||||
SequenceNode triggersNode = (SequenceNode) jobTuple.getValueNode();
|
||||
for (Node triggerNode: triggersNode.getValue()) {
|
||||
MappingNode triggerMappingNode = (MappingNode) triggerNode;
|
||||
boolean hasUserMatch = false;
|
||||
String triggerType = null;
|
||||
for (var triggerTuple: triggerMappingNode.getValue()) {
|
||||
String triggerTupleKey = ((ScalarNode)triggerTuple.getKeyNode()).getValue();
|
||||
if (triggerTupleKey.equals("type")) {
|
||||
triggerType = ((ScalarNode)triggerTuple.getValueNode()).getValue();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ("BranchUpdateTrigger".equals(triggerType)) {
|
||||
for (var itTriggerTuple = triggerMappingNode.getValue().iterator(); itTriggerTuple.hasNext();) {
|
||||
var triggerTuple = itTriggerTuple.next();
|
||||
String triggerTupleKey = ((ScalarNode)triggerTuple.getKeyNode()).getValue();
|
||||
if (triggerTupleKey.equals("userMatch")) {
|
||||
var valueNode = (ScalarNode) triggerTuple.getValueNode();
|
||||
if (StringUtils.isBlank(valueNode.getValue())) {
|
||||
itTriggerTuple.remove();
|
||||
} else {
|
||||
hasUserMatch = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasUserMatch) {
|
||||
triggerMappingNode.getValue().add(new NodeTuple(
|
||||
new ScalarNode(Tag.STR, "userMatch"),
|
||||
new ScalarNode(Tag.STR, "anyone")));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!hasRetryCondition) {
|
||||
jobNode.getValue().add(new NodeTuple(
|
||||
new ScalarNode(Tag.STR, "retryCondition"),
|
||||
new ScalarNode(Tag.STR, "never")));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,13 +1,20 @@
|
||||
package io.onedev.server.buildspec;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
@ -17,21 +24,31 @@ import javax.ws.rs.Path;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang3.SerializationUtils;
|
||||
import org.yaml.snakeyaml.DumperOptions;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
|
||||
import io.onedev.commons.loader.ImplementationRegistry;
|
||||
import io.onedev.commons.utils.ClassUtils;
|
||||
import io.onedev.commons.utils.ExplicitException;
|
||||
import io.onedev.commons.utils.StringUtils;
|
||||
import io.onedev.server.annotation.Code;
|
||||
import io.onedev.server.annotation.DependsOn;
|
||||
import io.onedev.server.annotation.Editable;
|
||||
import io.onedev.server.annotation.ImplementationProvider;
|
||||
import io.onedev.server.annotation.Interpolative;
|
||||
import io.onedev.server.annotation.Multiline;
|
||||
import io.onedev.server.annotation.Patterns;
|
||||
import io.onedev.server.annotation.RetryCondition;
|
||||
import io.onedev.server.annotation.UserMatch;
|
||||
import io.onedev.server.buildspec.job.Job;
|
||||
import io.onedev.server.data.migration.MigrationHelper;
|
||||
import io.onedev.server.model.support.build.JobProperty;
|
||||
import io.onedev.server.rest.annotation.Api;
|
||||
import io.onedev.server.util.Pair;
|
||||
import io.onedev.server.util.ReflectionUtils;
|
||||
import io.onedev.server.util.patternset.PatternSet;
|
||||
import io.onedev.server.web.editable.BeanDescriptor;
|
||||
import io.onedev.server.web.editable.EditableUtils;
|
||||
import io.onedev.server.web.editable.PropertyDescriptor;
|
||||
@ -60,40 +77,167 @@ public class BuildSpecSchemaResource {
|
||||
this.yaml = new Yaml(options);
|
||||
}
|
||||
|
||||
private void processProperty(Map<String, Object> currentNode, PropertyDescriptor property) {
|
||||
var description = property.getDescription();
|
||||
if (description != null)
|
||||
currentNode.put("description", description);
|
||||
private void processProperty(Map<String, Object> currentNode, Object bean, PropertyDescriptor property) {
|
||||
var descriptionSections = new ArrayList<String>();
|
||||
var descriptionSection = property.getDescription();
|
||||
if (descriptionSection != null)
|
||||
descriptionSections.add(descriptionSection);
|
||||
|
||||
var getter = property.getPropertyGetter();
|
||||
var returnType = property.getPropertyClass();
|
||||
|
||||
if (returnType == String.class) {
|
||||
if (getter.getAnnotation(Code.class) == null && getter.getAnnotation(Multiline.class) == null) {
|
||||
descriptionSections.add("NOTE: If set, the value can only contain one line");
|
||||
}
|
||||
InputStream grammarStream = null;
|
||||
if (getter.getAnnotation(Patterns.class) != null) {
|
||||
if (getter.getAnnotation(Interpolative.class) != null) {
|
||||
grammarStream = PatternSet.class.getResourceAsStream("InterpolativePatternSet.g4");
|
||||
} else {
|
||||
grammarStream = PatternSet.class.getResourceAsStream("PatternSet.g4");
|
||||
}
|
||||
} else if (getter.getAnnotation(RetryCondition.class) != null) {
|
||||
grammarStream = io.onedev.server.buildspec.job.retrycondition.RetryCondition.class.getResourceAsStream("RetryCondition.g4");
|
||||
} else if (getter.getAnnotation(UserMatch.class) != null) {
|
||||
grammarStream = io.onedev.server.util.usermatch.UserMatch.class.getResourceAsStream("UserMatch.g4");
|
||||
}
|
||||
if (grammarStream != null) {
|
||||
try {
|
||||
var grammar = IOUtils.toString(grammarStream, StandardCharsets.UTF_8);
|
||||
descriptionSections.add("NOTE: If set, the value should conform with below ANTLR v4 grammar:\n\n" + grammar);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (descriptionSections.size() != 0)
|
||||
currentNode.put("description", StringUtils.join(descriptionSections, "\n\n"));
|
||||
|
||||
Class<?> elementClass = null;
|
||||
if (Collection.class.isAssignableFrom(returnType)) {
|
||||
var elementClass = ReflectionUtils.getCollectionElementClass(property.getPropertyGetter().getGenericReturnType());
|
||||
elementClass = ReflectionUtils.getCollectionElementClass(property.getPropertyGetter().getGenericReturnType());
|
||||
if (elementClass == null)
|
||||
throw new ExplicitException("Unknown collection element class (bean: " + property.getBeanClass() + ", property: " + property.getPropertyName() + ")");
|
||||
processCollectionProperty(currentNode, elementClass);
|
||||
} else {
|
||||
processType(currentNode, returnType);
|
||||
}
|
||||
|
||||
Object defaultValue;
|
||||
var value = property.getPropertyValue(bean);
|
||||
if (value instanceof Integer) {
|
||||
var intValue = (Integer) value;
|
||||
if (intValue == 0)
|
||||
defaultValue = null;
|
||||
else
|
||||
defaultValue = intValue;
|
||||
} else if (value instanceof Long) {
|
||||
var longValue = (Long) value;
|
||||
if (longValue == 0)
|
||||
defaultValue = null;
|
||||
else
|
||||
defaultValue = longValue;
|
||||
} else if (value instanceof Double) {
|
||||
var doubleValue = (Double) value;
|
||||
if (doubleValue == 0)
|
||||
defaultValue = null;
|
||||
else
|
||||
defaultValue = doubleValue;
|
||||
} else if (value instanceof Float) {
|
||||
var floatValue = (Float) value;
|
||||
if (floatValue == 0)
|
||||
defaultValue = null;
|
||||
else
|
||||
defaultValue = floatValue;
|
||||
} else if (value instanceof Boolean) {
|
||||
var booleanValue = (Boolean) value;
|
||||
if (!booleanValue)
|
||||
defaultValue = null;
|
||||
else
|
||||
defaultValue = booleanValue;
|
||||
} else if (value instanceof Enum) {
|
||||
var enumValue = (Enum<?>) value;
|
||||
defaultValue = enumValue.name();
|
||||
} else if (value instanceof String || value instanceof Date) {
|
||||
defaultValue = value;
|
||||
} else {
|
||||
defaultValue = null;
|
||||
}
|
||||
if (defaultValue != null) {
|
||||
currentNode.put("default", defaultValue);
|
||||
}
|
||||
}
|
||||
|
||||
private Object newBean(Class<?> beanClass) {
|
||||
try {
|
||||
return beanClass.getConstructor().newInstance();
|
||||
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException
|
||||
| InvocationTargetException | NoSuchMethodException | SecurityException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void processBean(Map<String, Object> currentNode, Class<?> beanClass,
|
||||
Map<String, PropertyDescriptor> processedProperties) {
|
||||
Collection<Class<?>> implementations, Set<String> processedProperties) {
|
||||
var propsNode = (Map<String, Object>) currentNode.get("properties");
|
||||
if (propsNode == null) {
|
||||
propsNode = new HashMap<>();
|
||||
currentNode.put("properties", propsNode);
|
||||
}
|
||||
var requiredNode = (List<String>) currentNode.get("required");
|
||||
if (requiredNode == null) {
|
||||
requiredNode = new ArrayList<>();
|
||||
currentNode.put("required", requiredNode);
|
||||
}
|
||||
|
||||
var beanDescriptor = new BeanDescriptor(beanClass);
|
||||
|
||||
var propertyMap = new HashMap<String, PropertyDescriptor>();
|
||||
for (var groupProperties: beanDescriptor.getProperties().values()) {
|
||||
for (var property: groupProperties) {
|
||||
propertyMap.put(property.getPropertyName(), property);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* As long as one implementation overrides a property in base class, we will exclude the property from
|
||||
* common property section, and add it individually to each implementation's property section to avoid
|
||||
* defining same property both in common section and oneOf section
|
||||
*/
|
||||
var excludedProperties = new HashSet<String>();
|
||||
Map<String, byte[]> valueBytesMap = new HashMap<>();
|
||||
Object bean = null;
|
||||
for (var implementation: implementations) {
|
||||
bean = newBean(implementation);
|
||||
for (var groupProperties: new BeanDescriptor(implementation).getProperties().values()) {
|
||||
for (var property: groupProperties) {
|
||||
if (!excludedProperties.contains(property.getPropertyName())
|
||||
&& propertyMap.containsKey(property.getPropertyName())
|
||||
&& property.getBeanClass() != beanClass) {
|
||||
excludedProperties.add(property.getPropertyName());
|
||||
}
|
||||
if (!excludedProperties.contains(property.getPropertyName())) {
|
||||
var value = property.getPropertyValue(bean);
|
||||
var lastValueBytes = valueBytesMap.get(property.getPropertyName());
|
||||
if (lastValueBytes == null) {
|
||||
lastValueBytes = SerializationUtils.serialize((Serializable) value);
|
||||
valueBytesMap.put(property.getPropertyName(), lastValueBytes);
|
||||
} else if (!Arrays.equals(lastValueBytes, SerializationUtils.serialize((Serializable) value))) {
|
||||
excludedProperties.add(property.getPropertyName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bean == null) {
|
||||
bean = newBean(beanClass);
|
||||
}
|
||||
|
||||
var dependents = new ArrayList<Pair<PropertyDescriptor, DependsOn>>();
|
||||
for (var groupProperties: new BeanDescriptor(beanClass).getProperties().values()) {
|
||||
for (var groupProperties: beanDescriptor.getProperties().values()) {
|
||||
for (var property: groupProperties) {
|
||||
if (processedProperties.putIfAbsent(property.getPropertyName(), property) == null) {
|
||||
if (!excludedProperties.contains(property.getPropertyName()) && processedProperties.add(property.getPropertyName())) {
|
||||
if (property.getPropertyName().equals("type"))
|
||||
throw new ExplicitException("Property 'type' is reserved (class: " + beanClass.getName() + ")");
|
||||
var dependsOn = property.getPropertyGetter().getAnnotation(DependsOn.class);
|
||||
@ -104,7 +248,7 @@ public class BuildSpecSchemaResource {
|
||||
requiredNode.add(property.getPropertyName());
|
||||
var propNode = new HashMap<String, Object>();
|
||||
propsNode.put(property.getPropertyName(), propNode);
|
||||
processProperty(propNode, property);
|
||||
processProperty(propNode, bean, property);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -116,7 +260,7 @@ public class BuildSpecSchemaResource {
|
||||
var allOfItemNode = new HashMap<String, Object>();
|
||||
allOfNode.add(allOfItemNode);
|
||||
var dependsOn = dependent.getRight();
|
||||
var dependencyProperty = processedProperties.get(dependsOn.property());
|
||||
var dependencyProperty = propertyMap.get(dependsOn.property());
|
||||
if (dependencyProperty == null)
|
||||
throw new ExplicitException("Dependency property not found: " + dependsOn.property());
|
||||
|
||||
@ -176,7 +320,7 @@ public class BuildSpecSchemaResource {
|
||||
branchNode.put("properties", branchPropsNode);
|
||||
var propNode = new HashMap<String, Object>();
|
||||
branchPropsNode.put(property.getPropertyName(), propNode);
|
||||
processProperty(propNode, property);
|
||||
processProperty(propNode, bean, property);
|
||||
if (property.isPropertyRequired()) {
|
||||
var requiredList = new ArrayList<String>();
|
||||
requiredList.add(property.getPropertyName());
|
||||
@ -184,6 +328,11 @@ public class BuildSpecSchemaResource {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!propsNode.isEmpty())
|
||||
currentNode.put("properties", propsNode);
|
||||
if (!requiredNode.isEmpty())
|
||||
currentNode.put("required", requiredNode);
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
@ -210,7 +359,7 @@ public class BuildSpecSchemaResource {
|
||||
} else if (type.getAnnotation(Editable.class) != null) {
|
||||
if (ClassUtils.isConcrete(type)) {
|
||||
currentNode.put("type", "object");
|
||||
processBean(currentNode, type, new HashMap<>());
|
||||
processBean(currentNode, type, new ArrayList<>(), new HashSet<>());
|
||||
currentNode.put("additionalProperties", false);
|
||||
} else {
|
||||
processPolymorphic(currentNode, type);
|
||||
@ -232,10 +381,10 @@ public class BuildSpecSchemaResource {
|
||||
currentNode.put("type", "object");
|
||||
|
||||
var propsNode = new HashMap<String, Object>();
|
||||
currentNode.put("properties", propsNode);
|
||||
var typeNode = new HashMap<String, Object>();
|
||||
propsNode.put("type", typeNode);
|
||||
typeNode.put("type", "string");
|
||||
propsNode.put("type", typeNode);
|
||||
currentNode.put("properties", propsNode);
|
||||
|
||||
var enumList = new ArrayList<String>();
|
||||
typeNode.put("enum", enumList);
|
||||
@ -244,8 +393,8 @@ public class BuildSpecSchemaResource {
|
||||
requiredList.add("type");
|
||||
currentNode.put("required", requiredList);
|
||||
|
||||
var processedProperties = new HashMap<String, PropertyDescriptor>();
|
||||
processBean(currentNode, baseClass, processedProperties);
|
||||
var processedProperties = new HashSet<String>();
|
||||
processBean(currentNode, baseClass, implementations, processedProperties);
|
||||
|
||||
var oneOfList = new ArrayList<Map<String, Object>>();
|
||||
currentNode.put("oneOf", oneOfList);
|
||||
@ -253,15 +402,16 @@ public class BuildSpecSchemaResource {
|
||||
for (var implementation: implementations) {
|
||||
enumList.add(implementation.getSimpleName());
|
||||
var oneOfItemNode = new HashMap<String, Object>();
|
||||
oneOfList.add(oneOfItemNode);
|
||||
var oneOfItemPropsNode = new HashMap<String, Object>();
|
||||
var typeConstNode = new HashMap<String, Object>();
|
||||
typeConstNode.put("const", implementation.getSimpleName());
|
||||
oneOfItemPropsNode.put("type", typeConstNode);
|
||||
oneOfItemNode.put("properties", oneOfItemPropsNode);
|
||||
var description = EditableUtils.getDescription(implementation);
|
||||
if (description != null)
|
||||
oneOfItemNode.put("description", description);
|
||||
processBean(oneOfItemNode, implementation, new HashMap<>(processedProperties));
|
||||
var implementationPropsNode = (Map<String, Object>) oneOfItemNode.get("properties");
|
||||
var typeConstNode = new HashMap<String, Object>();
|
||||
typeConstNode.put("const", implementation.getSimpleName());
|
||||
implementationPropsNode.put("type", typeConstNode);
|
||||
processBean(oneOfItemNode, implementation, new ArrayList<>(), new HashSet<>(processedProperties));
|
||||
oneOfList.add(oneOfItemNode);
|
||||
}
|
||||
}
|
||||
|
||||
@ -279,7 +429,6 @@ public class BuildSpecSchemaResource {
|
||||
}
|
||||
}
|
||||
|
||||
@Path("/")
|
||||
@GET
|
||||
@SuppressWarnings("unchecked")
|
||||
public String getBuildSpecSchema() {
|
||||
|
||||
@ -64,7 +64,7 @@ public class Import implements Serializable, Validatable {
|
||||
|
||||
private transient BuildSpec buildSpec;
|
||||
|
||||
private static ThreadLocal<Stack<String>> importChain = ThreadLocal.withInitial(Stack::new);
|
||||
private static ThreadLocal<Stack<String>> IMPORT_CHAIN = ThreadLocal.withInitial(Stack::new);
|
||||
|
||||
// change Named("projectPath") also if change name of this property
|
||||
@Editable(order=100, name="Project", description="Specify project to import build spec from")
|
||||
@ -194,8 +194,8 @@ public class Import implements Serializable, Validatable {
|
||||
public boolean isValid(ConstraintValidatorContext context) {
|
||||
try {
|
||||
var commit = getCommit();
|
||||
if (importChain.get().contains(commit.name())) {
|
||||
List<String> circular = new ArrayList<>(importChain.get());
|
||||
if (IMPORT_CHAIN.get().contains(commit.name())) {
|
||||
List<String> circular = new ArrayList<>(IMPORT_CHAIN.get());
|
||||
circular.add(commit.name());
|
||||
String errorMessage = MessageFormat.format(
|
||||
_T("Circular build spec imports ({0})"), circular);
|
||||
@ -203,7 +203,7 @@ public class Import implements Serializable, Validatable {
|
||||
context.buildConstraintViolationWithTemplate(errorMessage).addConstraintViolation();
|
||||
return false;
|
||||
} else {
|
||||
importChain.get().push(commit.name());
|
||||
IMPORT_CHAIN.get().push(commit.name());
|
||||
try {
|
||||
Validator validator = OneDev.getInstance(Validator.class);
|
||||
BuildSpec buildSpec = getBuildSpec();
|
||||
@ -226,7 +226,7 @@ public class Import implements Serializable, Validatable {
|
||||
JobAuthorizationContext.pop();
|
||||
}
|
||||
} finally {
|
||||
importChain.get().pop();
|
||||
IMPORT_CHAIN.get().pop();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
|
||||
@ -6,6 +6,7 @@ import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
|
||||
import io.onedev.commons.codeassist.InputCompletion;
|
||||
@ -87,6 +88,7 @@ public class Service implements NamedElement {
|
||||
|
||||
@Editable(order=300, name="Environment Variables", description="Optionally specify environment variables of "
|
||||
+ "the service")
|
||||
@Valid
|
||||
public List<EnvVar> getEnvVars() {
|
||||
return envVars;
|
||||
}
|
||||
|
||||
@ -83,6 +83,8 @@ public class Job implements NamedElement, Validatable {
|
||||
public static final String PROP_RETRY_CONDITION = "retryCondition";
|
||||
|
||||
public static final String PROP_POST_BUILD_ACTIONS = "postBuildActions";
|
||||
|
||||
public static final String PROP_JOB_EXECUTOR = "jobExecutor";
|
||||
|
||||
private String name;
|
||||
|
||||
@ -106,7 +108,7 @@ public class Job implements NamedElement, Validatable {
|
||||
|
||||
private String sequentialGroup;
|
||||
|
||||
private String retryCondition;
|
||||
private String retryCondition = "never";
|
||||
|
||||
private int maxRetries = 3;
|
||||
|
||||
@ -189,6 +191,7 @@ public class Job implements NamedElement, Validatable {
|
||||
}
|
||||
|
||||
@Editable(order=200, description="Steps will be executed serially on same node, sharing the same <a href='https://docs.onedev.io/concepts#job-workspace'>job workspace</a>")
|
||||
@Valid
|
||||
public List<Step> getSteps() {
|
||||
return steps;
|
||||
}
|
||||
@ -280,8 +283,9 @@ public class Job implements NamedElement, Validatable {
|
||||
this.sequentialGroup = sequentialGroup;
|
||||
}
|
||||
|
||||
@Editable(order=9400, placeholder="Never retry", group="More Settings", description="Specify condition to retry build upon failure")
|
||||
@Editable(order=9400, group="More Settings", description="Specify condition to retry build upon failure")
|
||||
@RetryCondition
|
||||
@NotEmpty
|
||||
public String getRetryCondition() {
|
||||
return retryCondition;
|
||||
}
|
||||
@ -292,7 +296,7 @@ public class Job implements NamedElement, Validatable {
|
||||
|
||||
@Editable(order=9410, group="More Settings", description="Maximum of retries before giving up")
|
||||
@Min(value=1, message="This value should not be less than 1")
|
||||
@DependsOn(property="retryCondition")
|
||||
@DependsOn(property="retryCondition", value = "never", inverse = true)
|
||||
public int getMaxRetries() {
|
||||
return maxRetries;
|
||||
}
|
||||
@ -305,7 +309,7 @@ public class Job implements NamedElement, Validatable {
|
||||
"Delay of subsequent retries will be calculated using an exponential back-off based on " +
|
||||
"this value")
|
||||
@Min(value=1, message="This value should not be less than 1")
|
||||
@DependsOn(property="retryCondition")
|
||||
@DependsOn(property="retryCondition", value = "never", inverse = true)
|
||||
public int getRetryDelay() {
|
||||
return retryDelay;
|
||||
}
|
||||
@ -348,6 +352,14 @@ public class Job implements NamedElement, Validatable {
|
||||
public boolean isValid(ConstraintValidatorContext context) {
|
||||
boolean isValid = true;
|
||||
|
||||
var jobExecutors = OneDev.getInstance(SettingManager.class).getJobExecutors();
|
||||
if (jobExecutor != null && !jobExecutor.contains("@")
|
||||
&& jobExecutors.stream().noneMatch(it->it.getName().equals(jobExecutor))) {
|
||||
isValid = false;
|
||||
context.buildConstraintViolationWithTemplate("Job executor not found: " + jobExecutor)
|
||||
.addPropertyNode(PROP_JOB_EXECUTOR).addConstraintViolation();
|
||||
}
|
||||
|
||||
Set<String> dependencyJobNames = new HashSet<>();
|
||||
for (JobDependency dependency: jobDependencies) {
|
||||
if (!dependencyJobNames.add(dependency.getJobName())) {
|
||||
@ -366,18 +378,16 @@ public class Job implements NamedElement, Validatable {
|
||||
}
|
||||
}
|
||||
|
||||
if (getRetryCondition() != null) {
|
||||
try {
|
||||
io.onedev.server.buildspec.job.retrycondition.RetryCondition.parse(this, getRetryCondition());
|
||||
} catch (Exception e) {
|
||||
String message = e.getMessage();
|
||||
if (message == null)
|
||||
message = "Malformed retry condition";
|
||||
context.buildConstraintViolationWithTemplate(message)
|
||||
.addPropertyNode(PROP_RETRY_CONDITION)
|
||||
.addConstraintViolation();
|
||||
isValid = false;
|
||||
}
|
||||
try {
|
||||
io.onedev.server.buildspec.job.retrycondition.RetryCondition.parse(this, getRetryCondition());
|
||||
} catch (Exception e) {
|
||||
String message = e.getMessage();
|
||||
if (message == null)
|
||||
message = "Malformed retry condition";
|
||||
context.buildConstraintViolationWithTemplate(message)
|
||||
.addPropertyNode(PROP_RETRY_CONDITION)
|
||||
.addConstraintViolation();
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
if (isValid) {
|
||||
|
||||
@ -0,0 +1,31 @@
|
||||
package io.onedev.server.buildspec.job.retrycondition;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.persistence.criteria.CriteriaBuilder;
|
||||
import javax.persistence.criteria.CriteriaQuery;
|
||||
import javax.persistence.criteria.From;
|
||||
import javax.persistence.criteria.Predicate;
|
||||
|
||||
import io.onedev.server.util.ProjectScope;
|
||||
import io.onedev.server.util.criteria.Criteria;
|
||||
|
||||
public class NeverCriteria extends Criteria<RetryContext> {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public Predicate getPredicate(@Nullable ProjectScope projectScope, CriteriaQuery<?> query, From<RetryContext, RetryContext> from, CriteriaBuilder builder) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(RetryContext context) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toStringWithoutParens() {
|
||||
return RetryCondition.getRuleName(RetryConditionLexer.Never);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
grammar RetryCondition;
|
||||
|
||||
condition
|
||||
: WS* criteria WS* EOF
|
||||
: WS* (criteria|Never) WS* EOF
|
||||
;
|
||||
|
||||
criteria
|
||||
@ -12,7 +12,11 @@ criteria
|
||||
| Not WS* LParens WS* criteria WS* RParens #NotCriteria
|
||||
| LParens WS* criteria WS* RParens #ParensCriteria
|
||||
;
|
||||
|
||||
|
||||
Never
|
||||
: 'never'
|
||||
;
|
||||
|
||||
Contains
|
||||
: 'contains'
|
||||
;
|
||||
|
||||
@ -87,57 +87,60 @@ public class RetryCondition extends Criteria<RetryContext> {
|
||||
|
||||
Criteria<RetryContext> criteria;
|
||||
|
||||
criteria = new RetryConditionBaseVisitor<Criteria<RetryContext>>() {
|
||||
if (conditionContext.Never() != null) {
|
||||
criteria = new NeverCriteria();
|
||||
} else {
|
||||
criteria = new RetryConditionBaseVisitor<Criteria<RetryContext>>() {
|
||||
|
||||
@Override
|
||||
public Criteria<RetryContext> visitParensCriteria(ParensCriteriaContext ctx) {
|
||||
return visit(ctx.criteria()).withParens(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Criteria<RetryContext> visitFieldOperatorCriteria(FieldOperatorCriteriaContext ctx) {
|
||||
String fieldName = getValue(ctx.Quoted().getText());
|
||||
int operator = ctx.operator.getType();
|
||||
checkField(job, fieldName, operator);
|
||||
return new ParamEmptyCriteria(fieldName, operator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Criteria<RetryContext> visitFieldOperatorValueCriteria(FieldOperatorValueCriteriaContext ctx) {
|
||||
String fieldName = getValue(ctx.Quoted(0).getText());
|
||||
String fieldValue = getValue(ctx.Quoted(1).getText());
|
||||
int operator = ctx.operator.getType();
|
||||
checkField(job, fieldName, operator);
|
||||
|
||||
@Override
|
||||
public Criteria<RetryContext> visitParensCriteria(ParensCriteriaContext ctx) {
|
||||
return visit(ctx.criteria()).withParens(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Criteria<RetryContext> visitFieldOperatorCriteria(FieldOperatorCriteriaContext ctx) {
|
||||
String fieldName = getValue(ctx.Quoted().getText());
|
||||
int operator = ctx.operator.getType();
|
||||
checkField(job, fieldName, operator);
|
||||
return new ParamEmptyCriteria(fieldName, operator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Criteria<RetryContext> visitFieldOperatorValueCriteria(FieldOperatorValueCriteriaContext ctx) {
|
||||
String fieldName = getValue(ctx.Quoted(0).getText());
|
||||
String fieldValue = getValue(ctx.Quoted(1).getText());
|
||||
int operator = ctx.operator.getType();
|
||||
checkField(job, fieldName, operator);
|
||||
|
||||
if (fieldName.equals(NAME_LOG))
|
||||
return new LogCriteria(fieldValue);
|
||||
else
|
||||
return new ParamCriteria(fieldName, fieldValue, operator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Criteria<RetryContext> visitOrCriteria(OrCriteriaContext ctx) {
|
||||
List<Criteria<RetryContext>> childCriterias = new ArrayList<>();
|
||||
for (CriteriaContext childCtx: ctx.criteria())
|
||||
childCriterias.add(visit(childCtx));
|
||||
return new OrCriteria<RetryContext>(childCriterias);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Criteria<RetryContext> visitAndCriteria(AndCriteriaContext ctx) {
|
||||
List<Criteria<RetryContext>> childCriterias = new ArrayList<>();
|
||||
for (CriteriaContext childCtx: ctx.criteria())
|
||||
childCriterias.add(visit(childCtx));
|
||||
return new AndCriteria<RetryContext>(childCriterias);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Criteria<RetryContext> visitNotCriteria(NotCriteriaContext ctx) {
|
||||
return new NotCriteria<RetryContext>(visit(ctx.criteria()));
|
||||
}
|
||||
|
||||
}.visit(conditionContext.criteria());
|
||||
|
||||
if (fieldName.equals(NAME_LOG))
|
||||
return new LogCriteria(fieldValue);
|
||||
else
|
||||
return new ParamCriteria(fieldName, fieldValue, operator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Criteria<RetryContext> visitOrCriteria(OrCriteriaContext ctx) {
|
||||
List<Criteria<RetryContext>> childCriterias = new ArrayList<>();
|
||||
for (CriteriaContext childCtx: ctx.criteria())
|
||||
childCriterias.add(visit(childCtx));
|
||||
return new OrCriteria<RetryContext>(childCriterias);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Criteria<RetryContext> visitAndCriteria(AndCriteriaContext ctx) {
|
||||
List<Criteria<RetryContext>> childCriterias = new ArrayList<>();
|
||||
for (CriteriaContext childCtx: ctx.criteria())
|
||||
childCriterias.add(visit(childCtx));
|
||||
return new AndCriteria<RetryContext>(childCriterias);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Criteria<RetryContext> visitNotCriteria(NotCriteriaContext ctx) {
|
||||
return new NotCriteria<RetryContext>(visit(ctx.criteria()));
|
||||
}
|
||||
|
||||
}.visit(conditionContext.criteria());
|
||||
}
|
||||
return new RetryCondition(criteria);
|
||||
}
|
||||
|
||||
|
||||
@ -3,6 +3,8 @@ package io.onedev.server.buildspec.job.trigger;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
|
||||
@ -21,6 +23,7 @@ import io.onedev.server.event.project.RefUpdated;
|
||||
import io.onedev.server.git.GitUtils;
|
||||
import io.onedev.server.model.Project;
|
||||
import io.onedev.server.util.patternset.PatternSet;
|
||||
import io.onedev.server.util.usermatch.Anyone;
|
||||
import io.onedev.server.web.util.SuggestionUtils;
|
||||
|
||||
@Editable(order=100, name="Branch update", description=""
|
||||
@ -36,7 +39,7 @@ public class BranchUpdateTrigger extends JobTrigger {
|
||||
|
||||
private String paths;
|
||||
|
||||
private String userMatch;
|
||||
private String userMatch = new Anyone().toString();
|
||||
|
||||
@Editable(name="Branches", order=100, placeholder="Any branch", description="Optionally specify space-separated branches "
|
||||
+ "to check. Use '**' or '*' or '?' for <a href='https://docs.onedev.io/appendix/path-wildcard' target='_blank'>path wildcard match</a>. "
|
||||
@ -67,8 +70,9 @@ public class BranchUpdateTrigger extends JobTrigger {
|
||||
this.paths = paths;
|
||||
}
|
||||
|
||||
@Editable(order=150, name="Applicable Users", placeholder = "Any user", description="Optionally specify applicable users who pushed the change")
|
||||
@Editable(order=300, name="Applicable Users", description="Optionally specify applicable users who pushed the change")
|
||||
@UserMatch
|
||||
@NotEmpty
|
||||
public String getUserMatch() {
|
||||
return userMatch;
|
||||
}
|
||||
@ -108,7 +112,7 @@ public class BranchUpdateTrigger extends JobTrigger {
|
||||
}
|
||||
|
||||
private boolean pushedBy(RefUpdated refUpdated) {
|
||||
if (getUserMatch() != null && refUpdated.getUser() != null) {
|
||||
if (refUpdated.getUser() != null) {
|
||||
return io.onedev.server.util.usermatch.UserMatch.parse(getUserMatch()).matches(refUpdated.getUser());
|
||||
} else {
|
||||
return true;
|
||||
@ -135,22 +139,26 @@ public class BranchUpdateTrigger extends JobTrigger {
|
||||
@Override
|
||||
public String getTriggerDescription() {
|
||||
String description;
|
||||
if (getBranches() != null && getPaths() != null && getUserMatch() != null)
|
||||
description = String.format("When update branches '%s' and touch files '%s' and pushed by '%s'", getBranches(), getPaths(), getUserMatch());
|
||||
else if (getBranches() != null && getUserMatch() != null)
|
||||
description = String.format("When update branches '%s' and pushed by '%s'", getBranches(), getUserMatch());
|
||||
else if (getPaths() != null && getUserMatch() != null)
|
||||
description = String.format("When touch files '%s' and pushed by '%s'", getPaths(), getUserMatch());
|
||||
else if (getBranches() != null && getPaths() != null)
|
||||
description = String.format("When update branches '%s' and touch files '%s'", getBranches(), getPaths());
|
||||
else if (getBranches() != null)
|
||||
description = String.format("When update branches '%s'", getBranches());
|
||||
else if (getPaths() != null)
|
||||
description = String.format("When touch files '%s'", getPaths());
|
||||
else if (getUserMatch() != null)
|
||||
description = String.format("When pushed by '%s'", getUserMatch());
|
||||
else
|
||||
description = "When update branches";
|
||||
|
||||
if (getUserMatch() == null || getUserMatch().equals(new Anyone().toString())) {
|
||||
if (getBranches() != null && getPaths() != null)
|
||||
description = String.format("When update branches '%s' and touch files '%s'", getBranches(), getPaths());
|
||||
else if (getBranches() != null)
|
||||
description = String.format("When update branches '%s'", getBranches());
|
||||
else if (getPaths() != null)
|
||||
description = String.format("When touch files '%s'", getPaths());
|
||||
else
|
||||
description = "When update branches";
|
||||
} else {
|
||||
if (getBranches() != null && getPaths() != null)
|
||||
description = String.format("When update branches '%s' and touch files '%s' and pushed by '%s'", getBranches(), getPaths(), getUserMatch());
|
||||
else if (getBranches() != null)
|
||||
description = String.format("When update branches '%s' and pushed by '%s'", getBranches(), getUserMatch());
|
||||
else if (getPaths() != null)
|
||||
description = String.format("When touch files '%s' and pushed by '%s'", getPaths(), getUserMatch());
|
||||
else
|
||||
description = "When pushed by '" + getUserMatch() + "'";
|
||||
}
|
||||
return description;
|
||||
}
|
||||
|
||||
|
||||
@ -7,6 +7,7 @@ import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
@ -82,6 +83,7 @@ public class BuildImageStep extends Step {
|
||||
@Editable(order=1000, group="More Settings", description="Optionally specify registry logins to override " +
|
||||
"those defined in job executor. For built-in registry, use <code>@server_url@</code> for registry url, " +
|
||||
"<code>@job_token@</code> for user name, and access token secret for password secret")
|
||||
@Valid
|
||||
public List<RegistryLogin> getRegistryLogins() {
|
||||
return registryLogins;
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ package io.onedev.server.buildspec.step;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import io.onedev.commons.codeassist.InputSuggestion;
|
||||
@ -35,6 +36,7 @@ public class CheckoutStep extends Step {
|
||||
@Editable(order=100, description="By default code is cloned via an auto-generated credential, " +
|
||||
"which only has read permission over current project. In case the job needs to <a href='https://docs.onedev.io/tutorials/cicd/commit-and-push' target='_blank'>push code to server</a>, " +
|
||||
"you should supply custom credential with appropriate permissions here")
|
||||
@Valid
|
||||
@NotNull
|
||||
public GitCredential getCloneCredential() {
|
||||
return cloneCredential;
|
||||
|
||||
@ -6,6 +6,7 @@ import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
@ -74,6 +75,7 @@ public class CommandStep extends Step {
|
||||
}
|
||||
|
||||
@Editable(order=110)
|
||||
@Valid
|
||||
@NotNull
|
||||
public Interpreter getInterpreter() {
|
||||
return interpreter;
|
||||
@ -100,6 +102,7 @@ public class CommandStep extends Step {
|
||||
"those defined in job executor. For built-in registry, use <code>@server_url@</code> for registry url, " +
|
||||
"<code>@job_token@</code> for user name, and access token secret for password secret")
|
||||
@DependsOn(property="runInContainer")
|
||||
@Valid
|
||||
public List<RegistryLogin> getRegistryLogins() {
|
||||
return registryLogins;
|
||||
}
|
||||
@ -110,6 +113,7 @@ public class CommandStep extends Step {
|
||||
|
||||
@Editable(order=9900, name="Environment Variables", group="More Settings", description="Optionally specify environment "
|
||||
+ "variables for this step")
|
||||
@Valid
|
||||
public List<EnvVar> getEnvVars() {
|
||||
return envVars;
|
||||
}
|
||||
|
||||
@ -23,7 +23,8 @@ import java.util.List;
|
||||
|
||||
import static io.onedev.server.buildspec.step.StepGroup.PUBLISH;
|
||||
|
||||
@Editable(order=1050, group= PUBLISH, name="Artifacts")
|
||||
@Editable(order=1050, group= PUBLISH, name="Artifacts", description="This step copies files from job workspace " +
|
||||
"to build artifacts directory, so that they can be accessed after job is completed")
|
||||
public class PublishArtifactStep extends ServerSideStep {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
@ -46,8 +47,7 @@ public class PublishArtifactStep extends ServerSideStep {
|
||||
this.sourcePath = sourcePath;
|
||||
}
|
||||
|
||||
@Editable(order=100, description="Specify files under above directory to be published. "
|
||||
+ "Use * or ? for pattern match")
|
||||
@Editable(order=100, description="Specify files under above directory to be published")
|
||||
@Interpolative(variableSuggester="suggestVariables")
|
||||
@Patterns(path=true)
|
||||
@NotEmpty
|
||||
|
||||
@ -8,6 +8,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.validation.Valid;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
|
||||
import io.onedev.commons.codeassist.InputSuggestion;
|
||||
@ -97,6 +98,7 @@ public class RunContainerStep extends Step {
|
||||
|
||||
@Editable(order=400, name="Environment Variables", group="More Settings", description="Optionally specify environment "
|
||||
+ "variables for the container")
|
||||
@Valid
|
||||
public List<EnvVar> getEnvVars() {
|
||||
return envVars;
|
||||
}
|
||||
@ -106,6 +108,7 @@ public class RunContainerStep extends Step {
|
||||
}
|
||||
|
||||
@Editable(order=500, group = "More Settings", description="Optionally mount directories or files under job workspace into container")
|
||||
@Valid
|
||||
public List<VolumeMount> getVolumeMounts() {
|
||||
return volumeMounts;
|
||||
}
|
||||
@ -117,6 +120,7 @@ public class RunContainerStep extends Step {
|
||||
@Editable(order=600, group="More Settings", description="Optionally specify registry logins to override " +
|
||||
"those defined in job executor. For built-in registry, use <code>@server_url@</code> for registry url, " +
|
||||
"<code>@job_token@</code> for user name, and access token secret for password secret")
|
||||
@Valid
|
||||
public List<RegistryLogin> getRegistryLogins() {
|
||||
return registryLogins;
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@ import static java.util.stream.Collectors.toList;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
|
||||
import io.onedev.commons.codeassist.InputSuggestion;
|
||||
@ -46,6 +47,7 @@ public class RunImagetoolsStep extends Step {
|
||||
@Editable(order=200, group="More Settings", description="Optionally specify registry logins to override " +
|
||||
"those defined in job executor. For built-in registry, use <code>@server_url@</code> for registry url, " +
|
||||
"<code>@job_token@</code> for user name, and access token secret for password secret")
|
||||
@Valid
|
||||
public List<RegistryLogin> getRegistryLogins() {
|
||||
return registryLogins;
|
||||
}
|
||||
|
||||
@ -47,7 +47,10 @@ public class SetupCacheStep extends Step {
|
||||
|
||||
@Editable(order=100, name="Cache Key", description = "This key is used to determine if there is a cache hit in " +
|
||||
"project hierarchy (search from current project to root project in order, same for load keys below). " +
|
||||
"A cache is considered hit if its key is exactly the same as the key defined here")
|
||||
"A cache is considered hit if its key is exactly the same as the key defined here.<br>" +
|
||||
"<b>NOTE:</b> In case your project has lock files(package.json, pom.xml, etc.) able to represent cache state, " +
|
||||
"this key should be defined as <cache name>-@file:checksum.txt@, where checksum.txt is generated " +
|
||||
"from these lock files with the <b>generate checksum step</b> defined before this step")
|
||||
@Interpolative(variableSuggester="suggestVariables")
|
||||
@NotEmpty
|
||||
public String getKey() {
|
||||
@ -123,7 +126,7 @@ public class SetupCacheStep extends Step {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static List<InputSuggestion> suggestVariables(String matchWith) {
|
||||
return BuildSpec.suggestVariables(matchWith, true, false, false);
|
||||
return BuildSpec.suggestVariables(matchWith, true, true, false);
|
||||
}
|
||||
|
||||
@Editable(order=500, description = "Specify a <a href='https://docs.onedev.io/tutorials/cicd/job-secrets' target='_blank'>job secret</a> whose value is an access token with upload cache permission " +
|
||||
|
||||
@ -36,7 +36,7 @@ public abstract class Step implements Serializable {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Editable(order=10000, description="Under which condition this step should run. <b>Successful</b> means all " +
|
||||
@Editable(order=10000, description="Under which condition this step should run. <b>SUCCESSFUL</b> means all " +
|
||||
"non-optional steps running before this step are successful")
|
||||
@NotNull
|
||||
public ExecuteCondition getCondition() {
|
||||
|
||||
@ -51,6 +51,7 @@ public class StepTemplate implements NamedElement {
|
||||
}
|
||||
|
||||
@Editable(order=200, description="Steps will be executed serially on same node, sharing the same <a href='https://docs.onedev.io/concepts#job-workspace'>job workspace</a>")
|
||||
@Valid
|
||||
public List<Step> getSteps() {
|
||||
return steps;
|
||||
}
|
||||
|
||||
@ -68,6 +68,7 @@ public class UseTemplateStep extends CompositeStep {
|
||||
|
||||
@Editable(order=300, name="Exclude Param Combos")
|
||||
@ShowCondition("isExcludeParamMapsVisible")
|
||||
@Valid
|
||||
public List<ParamMap> getExcludeParamMaps() {
|
||||
return excludeParamMaps;
|
||||
}
|
||||
|
||||
@ -8304,4 +8304,27 @@ public class DataMigrator {
|
||||
}
|
||||
}
|
||||
|
||||
private void migrate211(File dataDir, Stack<Integer> versions) {
|
||||
for (File file : dataDir.listFiles()) {
|
||||
if (file.getName().startsWith("Projects.xml")) {
|
||||
VersionedXmlDoc dom = VersionedXmlDoc.fromFile(file);
|
||||
for (Element projectElement : dom.getRootElement().elements()) {
|
||||
for (Element branchProtectionElement : projectElement.element("branchProtections").elements()) {
|
||||
Element userMatchElement = branchProtectionElement.element("userMatch");
|
||||
if (userMatchElement == null) {
|
||||
branchProtectionElement.addElement("userMatch").setText("anyone");
|
||||
}
|
||||
}
|
||||
for (Element tagProtectionElement : projectElement.element("tagProtections").elements()) {
|
||||
Element userMatchElement = tagProtectionElement.element("userMatch");
|
||||
if (userMatchElement == null) {
|
||||
tagProtectionElement.addElement("userMatch").setText("anyone");
|
||||
}
|
||||
}
|
||||
}
|
||||
dom.writeToFile(file, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -703,23 +703,19 @@ public class DefaultJobManager implements JobManager, Runnable, CodePullAuthoriz
|
||||
private boolean checkRetry(Job job, JobContext jobContext, TaskLogger jobLogger,
|
||||
@Nullable Throwable throwable, int retried) {
|
||||
if (retried < job.getMaxRetries() && sessionManager.call(() -> {
|
||||
if (job.getRetryCondition() != null) {
|
||||
RetryCondition retryCondition = RetryCondition.parse(job, job.getRetryCondition());
|
||||
AtomicReference<String> errorMessage = new AtomicReference<>(null);
|
||||
if (throwable != null) {
|
||||
log(throwable, new TaskLogger() {
|
||||
RetryCondition retryCondition = RetryCondition.parse(job, job.getRetryCondition());
|
||||
AtomicReference<String> errorMessage = new AtomicReference<>(null);
|
||||
if (throwable != null) {
|
||||
log(throwable, new TaskLogger() {
|
||||
|
||||
@Override
|
||||
public void log(String message, String sessionId) {
|
||||
errorMessage.set(message);
|
||||
}
|
||||
@Override
|
||||
public void log(String message, String sessionId) {
|
||||
errorMessage.set(message);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
return retryCondition.matches(new RetryContext(buildManager.load(jobContext.getBuildId()), errorMessage.get()));
|
||||
} else {
|
||||
return false;
|
||||
});
|
||||
}
|
||||
return retryCondition.matches(new RetryContext(buildManager.load(jobContext.getBuildId()), errorMessage.get()));
|
||||
})) {
|
||||
if (throwable != null)
|
||||
log(throwable, jobLogger);
|
||||
|
||||
@ -1342,7 +1342,7 @@ public class Project extends AbstractEntity implements LabelSupport<ProjectLabel
|
||||
public TagProtection getTagProtection(String tagName, User user) {
|
||||
for (TagProtection protection: getHierarchyTagProtections()) {
|
||||
if (protection.isEnabled()
|
||||
&& (protection.getUserMatch() == null || UserMatch.parse(protection.getUserMatch()).matches(user))
|
||||
&& UserMatch.parse(protection.getUserMatch()).matches(user)
|
||||
&& PatternSet.parse(protection.getTags()).matches(new PathMatcher(), tagName)) {
|
||||
return protection;
|
||||
}
|
||||
@ -1361,7 +1361,7 @@ public class Project extends AbstractEntity implements LabelSupport<ProjectLabel
|
||||
branchName = "main";
|
||||
for (BranchProtection protection: getHierarchyBranchProtections()) {
|
||||
if (protection.isEnabled()
|
||||
&& (protection.getUserMatch() == null || UserMatch.parse(protection.getUserMatch()).matches(user))
|
||||
&& UserMatch.parse(protection.getUserMatch()).matches(user)
|
||||
&& PatternSet.parse(protection.getBranches()).matches(new PathMatcher(), branchName)) {
|
||||
return protection;
|
||||
}
|
||||
|
||||
@ -41,6 +41,7 @@ import io.onedev.server.model.User;
|
||||
import io.onedev.server.util.patternset.PatternSet;
|
||||
import io.onedev.server.util.reviewrequirement.ReviewRequirement;
|
||||
import io.onedev.server.util.usage.Usage;
|
||||
import io.onedev.server.util.usermatch.Anyone;
|
||||
import io.onedev.server.util.usermatch.UserMatch;
|
||||
import io.onedev.server.web.util.SuggestionUtils;
|
||||
|
||||
@ -57,7 +58,7 @@ public class BranchProtection implements Serializable {
|
||||
|
||||
private String branches;
|
||||
|
||||
private String userMatch;
|
||||
private String userMatch = new Anyone().toString();
|
||||
|
||||
private boolean preventForcedPush = true;
|
||||
|
||||
@ -116,8 +117,9 @@ public class BranchProtection implements Serializable {
|
||||
return SuggestionUtils.suggestBranches(Project.get(), matchWith);
|
||||
}
|
||||
|
||||
@Editable(order=150, name="Applicable Users", placeholder = "Any user", description="Rule will apply only if the user changing the branch matches criteria specified here")
|
||||
@Editable(order=150, name="Applicable Users", description="Rule will apply only if the user changing the branch matches criteria specified here")
|
||||
@io.onedev.server.annotation.UserMatch
|
||||
@NotEmpty(message="may not be empty")
|
||||
public String getUserMatch() {
|
||||
return userMatch;
|
||||
}
|
||||
@ -309,8 +311,7 @@ public class BranchProtection implements Serializable {
|
||||
}
|
||||
|
||||
public void onRenameGroup(String oldName, String newName) {
|
||||
if (userMatch != null)
|
||||
userMatch = UserMatch.onRenameGroup(userMatch, oldName, newName);
|
||||
userMatch = UserMatch.onRenameGroup(userMatch, oldName, newName);
|
||||
reviewRequirement = ReviewRequirement.onRenameGroup(reviewRequirement, oldName, newName);
|
||||
|
||||
for (FileProtection fileProtection: getFileProtections()) {
|
||||
@ -321,7 +322,7 @@ public class BranchProtection implements Serializable {
|
||||
|
||||
public Usage onDeleteGroup(String groupName) {
|
||||
Usage usage = new Usage();
|
||||
if (userMatch != null && UserMatch.isUsingGroup(userMatch, groupName))
|
||||
if (UserMatch.isUsingGroup(userMatch, groupName))
|
||||
usage.add("applicable users");
|
||||
if (ReviewRequirement.isUsingGroup(reviewRequirement, groupName))
|
||||
usage.add("required reviewers");
|
||||
@ -336,8 +337,7 @@ public class BranchProtection implements Serializable {
|
||||
}
|
||||
|
||||
public void onRenameUser(String oldName, String newName) {
|
||||
if (userMatch != null)
|
||||
userMatch = UserMatch.onRenameUser(userMatch, oldName, newName);
|
||||
userMatch = UserMatch.onRenameUser(userMatch, oldName, newName);
|
||||
reviewRequirement = ReviewRequirement.onRenameUser(reviewRequirement, oldName, newName);
|
||||
|
||||
for (FileProtection fileProtection: getFileProtections()) {
|
||||
@ -348,7 +348,7 @@ public class BranchProtection implements Serializable {
|
||||
|
||||
public Usage onDeleteUser(String userName) {
|
||||
Usage usage = new Usage();
|
||||
if (userMatch != null && UserMatch.isUsingUser(userMatch, userName))
|
||||
if (UserMatch.isUsingUser(userMatch, userName))
|
||||
usage.add("applicable users");
|
||||
if (ReviewRequirement.isUsingUser(reviewRequirement, userName))
|
||||
usage.add("required reviewers");
|
||||
|
||||
@ -1,20 +1,20 @@
|
||||
package io.onedev.server.model.support.code;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
|
||||
import io.onedev.commons.codeassist.InputSuggestion;
|
||||
import io.onedev.server.annotation.Editable;
|
||||
import io.onedev.server.annotation.Patterns;
|
||||
import io.onedev.server.model.Project;
|
||||
import io.onedev.server.util.patternset.PatternSet;
|
||||
import io.onedev.server.util.usage.Usage;
|
||||
import io.onedev.server.util.usermatch.Anyone;
|
||||
import io.onedev.server.util.usermatch.UserMatch;
|
||||
import io.onedev.server.web.util.SuggestionUtils;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Editable
|
||||
public class TagProtection implements Serializable {
|
||||
|
||||
@ -24,7 +24,7 @@ public class TagProtection implements Serializable {
|
||||
|
||||
private String tags;
|
||||
|
||||
private String userMatch;
|
||||
private String userMatch = new Anyone().toString();
|
||||
|
||||
private boolean preventUpdate = true;
|
||||
|
||||
@ -59,8 +59,9 @@ public class TagProtection implements Serializable {
|
||||
return SuggestionUtils.suggestTags(Project.get(), matchWith);
|
||||
}
|
||||
|
||||
@Editable(order=150, name="Applicable Users", placeholder = "Any user", description="Rule will apply if user operating the tag matches criteria specified here")
|
||||
@Editable(order=150, name="Applicable Users", description="Rule will apply if user operating the tag matches criteria specified here")
|
||||
@io.onedev.server.annotation.UserMatch
|
||||
@NotEmpty(message="may not be empty")
|
||||
public String getUserMatch() {
|
||||
return userMatch;
|
||||
}
|
||||
@ -115,25 +116,23 @@ public class TagProtection implements Serializable {
|
||||
}
|
||||
|
||||
public void onRenameGroup(String oldName, String newName) {
|
||||
if (userMatch != null)
|
||||
userMatch = UserMatch.onRenameGroup(userMatch, oldName, newName);
|
||||
userMatch = UserMatch.onRenameGroup(userMatch, oldName, newName);
|
||||
}
|
||||
|
||||
public Usage onDeleteGroup(String groupName) {
|
||||
Usage usage = new Usage();
|
||||
if (userMatch != null && UserMatch.isUsingGroup(userMatch, groupName))
|
||||
if (UserMatch.isUsingGroup(userMatch, groupName))
|
||||
usage.add("applicable users");
|
||||
return usage.prefix("tag protection '" + getTags() + "'").prefix("code");
|
||||
}
|
||||
|
||||
public void onRenameUser(String oldName, String newName) {
|
||||
if (userMatch != null)
|
||||
userMatch = UserMatch.onRenameUser(userMatch, oldName, newName);
|
||||
userMatch = UserMatch.onRenameUser(userMatch, oldName, newName);
|
||||
}
|
||||
|
||||
public Usage onDeleteUser(String userName) {
|
||||
Usage usage = new Usage();
|
||||
if (userMatch != null && UserMatch.isUsingUser(userMatch, userName))
|
||||
if (UserMatch.isUsingUser(userMatch, userName))
|
||||
usage.add("applicable users");
|
||||
return usage.prefix("tag protection '" + getTags() + "'").prefix("code");
|
||||
}
|
||||
|
||||
@ -20,6 +20,7 @@ import javax.inject.Singleton;
|
||||
import javax.persistence.EntityNotFoundException;
|
||||
import javax.validation.Valid;
|
||||
import javax.validation.ValidationException;
|
||||
import javax.validation.Validator;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.GET;
|
||||
@ -30,6 +31,7 @@ import javax.ws.rs.Path;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.QueryParam;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
|
||||
import org.apache.shiro.authz.UnauthenticatedException;
|
||||
import org.apache.shiro.authz.UnauthorizedException;
|
||||
@ -38,8 +40,11 @@ import org.unbescape.html.HtmlEscape;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.base.Throwables;
|
||||
|
||||
import io.onedev.commons.utils.ExplicitException;
|
||||
import io.onedev.commons.utils.StringUtils;
|
||||
import io.onedev.server.SubscriptionManager;
|
||||
import io.onedev.server.buildspec.BuildSpec;
|
||||
@ -66,6 +71,7 @@ import io.onedev.server.entitymanager.UserManager;
|
||||
import io.onedev.server.entityreference.BuildReference;
|
||||
import io.onedev.server.entityreference.IssueReference;
|
||||
import io.onedev.server.entityreference.PullRequestReference;
|
||||
import io.onedev.server.exception.ExceptionUtils;
|
||||
import io.onedev.server.exception.InvalidIssueFieldsException;
|
||||
import io.onedev.server.exception.InvalidReferenceException;
|
||||
import io.onedev.server.exception.IssueLinkValidationException;
|
||||
@ -171,6 +177,8 @@ public class McpHelperResource {
|
||||
|
||||
private final UrlManager urlManager;
|
||||
|
||||
private final Validator validator;
|
||||
|
||||
@Inject
|
||||
public McpHelperResource(ObjectMapper objectMapper, SettingManager settingManager,
|
||||
UserManager userManager, IssueManager issueManager, ProjectManager projectManager,
|
||||
@ -183,7 +191,7 @@ public class McpHelperResource {
|
||||
PullRequestAssignmentManager pullRequestAssignmentManager,
|
||||
PullRequestLabelManager pullRequestLabelManager, UrlManager urlManager,
|
||||
PullRequestCommentManager pullRequestCommentManager, BuildManager buildManager,
|
||||
BuildParamManager buildParamManager, JobManager jobManager) {
|
||||
BuildParamManager buildParamManager, JobManager jobManager, Validator validator) {
|
||||
this.objectMapper = objectMapper;
|
||||
this.settingManager = settingManager;
|
||||
this.issueManager = issueManager;
|
||||
@ -208,6 +216,7 @@ public class McpHelperResource {
|
||||
this.buildManager = buildManager;
|
||||
this.buildParamManager = buildParamManager;
|
||||
this.jobManager = jobManager;
|
||||
this.validator = validator;
|
||||
}
|
||||
|
||||
private String getIssueQueryStringDescription() {
|
||||
@ -1464,18 +1473,6 @@ public class McpHelperResource {
|
||||
}
|
||||
}
|
||||
|
||||
@Path("/migrate-build-spec")
|
||||
@POST
|
||||
@Consumes(MediaType.TEXT_PLAIN)
|
||||
@Produces(MediaType.TEXT_PLAIN)
|
||||
public String migrateBuildSpec(@NotNull String buildSpecString) {
|
||||
if (SecurityUtils.getAuthUser() == null)
|
||||
throw new UnauthenticatedException();
|
||||
|
||||
var buildSpec = BuildSpec.parse(buildSpecString.getBytes(StandardCharsets.UTF_8));
|
||||
return VersionedYamlDoc.fromBean(buildSpec).toYaml();
|
||||
}
|
||||
|
||||
@Path("/get-pull-request")
|
||||
@GET
|
||||
public Map<String, Object> getPullRequest(
|
||||
@ -1678,7 +1675,16 @@ public class McpHelperResource {
|
||||
if (baseCommitId == null)
|
||||
throw new NotAcceptableException("No common base for source and target branches");
|
||||
|
||||
request.setTitle((String) data.remove("title"));
|
||||
var title = (String) data.remove("title");
|
||||
|
||||
// Remove issue number suffix generated by AI
|
||||
var cleanedTitle = title.replaceFirst("\\s*\\([a-zA-Z]+-\\d+\\)$", "");
|
||||
cleanedTitle = cleanedTitle.replaceFirst("\\s*\\(#\\d+\\)$", "").trim();
|
||||
if (cleanedTitle.length() != 0)
|
||||
request.setTitle(cleanedTitle);
|
||||
else
|
||||
request.setTitle(title);
|
||||
|
||||
request.setDescription((String) data.remove("description"));
|
||||
request.setTarget(target);
|
||||
request.setSource(source);
|
||||
@ -2125,6 +2131,43 @@ public class McpHelperResource {
|
||||
return buildMap;
|
||||
}
|
||||
|
||||
@Path("/check-build-spec")
|
||||
@POST
|
||||
@Consumes(MediaType.TEXT_PLAIN)
|
||||
@Produces(MediaType.TEXT_PLAIN)
|
||||
public Response checkBuildSpec(
|
||||
@QueryParam("project") @NotNull String projectPath,
|
||||
@NotNull String buildSpecString) {
|
||||
if (SecurityUtils.getAuthUser() == null)
|
||||
throw new UnauthenticatedException();
|
||||
|
||||
var project = getProject(projectPath);
|
||||
Project.push(project);
|
||||
try {
|
||||
var buildSpec = BuildSpec.parse(buildSpecString.getBytes(StandardCharsets.UTF_8));
|
||||
List<String> validationErrors = new ArrayList<>();
|
||||
for (var violation : validator.validate(buildSpec)) {
|
||||
String message = String.format("Error validating build spec (project: %s, location: %s, message: %s)",
|
||||
project.getPath(), violation.getPropertyPath(), violation.getMessage());
|
||||
validationErrors.add(message);
|
||||
}
|
||||
if (validationErrors.isEmpty()) {
|
||||
return Response.ok(VersionedYamlDoc.fromBean(buildSpec).toYaml()).build();
|
||||
} else {
|
||||
return Response.ok(Joiner.on("\n").join(validationErrors)).build();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
var explicitException = ExceptionUtils.find(e, ExplicitException.class);
|
||||
if (explicitException != null) {
|
||||
return Response.ok(explicitException.getMessage()).build();
|
||||
} else {
|
||||
return Response.ok(Throwables.getStackTraceAsString(e)).build();
|
||||
}
|
||||
} finally {
|
||||
Project.pop();
|
||||
}
|
||||
}
|
||||
|
||||
private Build getBuild(Project currentProject, String referenceString) {
|
||||
BuildReference buildReference;
|
||||
try {
|
||||
|
||||
@ -0,0 +1,32 @@
|
||||
package io.onedev.server.util;
|
||||
|
||||
import io.onedev.server.annotation.DependsOn;
|
||||
|
||||
public class DependsOnUtils {
|
||||
|
||||
public static boolean isPropertyVisible(DependsOn dependsOn, Class<?> dependencyPropertyType, Object dependencyPropertyValue) {
|
||||
if (dependsOn.value().length() != 0) {
|
||||
if (dependencyPropertyValue != null && dependencyPropertyValue.toString().equals(dependsOn.value())) {
|
||||
if (dependsOn.inverse())
|
||||
return false;
|
||||
} else if (!dependsOn.inverse()) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (dependencyPropertyType == boolean.class) {
|
||||
boolean requiredPropertyValue = !dependsOn.inverse();
|
||||
if (requiredPropertyValue != (boolean)dependencyPropertyValue)
|
||||
return false;
|
||||
} else if (dependencyPropertyType == int.class || dependencyPropertyType == long.class || dependencyPropertyType == double.class || dependencyPropertyType == float.class) {
|
||||
int dependencyPropertyIntValue = (int) dependencyPropertyValue;
|
||||
if (dependsOn.inverse() && dependencyPropertyIntValue != 0 || !dependsOn.inverse() && dependencyPropertyIntValue == 0)
|
||||
return false;
|
||||
} else {
|
||||
if (dependsOn.inverse() && dependencyPropertyValue != null || !dependsOn.inverse() && dependencyPropertyValue == null)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
package io.onedev.server.validation;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Provider;
|
||||
import javax.validation.Validator;
|
||||
import javax.validation.ValidatorFactory;
|
||||
|
||||
import io.onedev.server.annotation.Shallow;
|
||||
|
||||
public class ShallowValidatorProvider implements Provider<Validator> {
|
||||
|
||||
private final ValidatorFactory validatorFactory;
|
||||
|
||||
@Inject
|
||||
public ShallowValidatorProvider(@Shallow ValidatorFactory validatorFactory) {
|
||||
this.validatorFactory = validatorFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Validator get() {
|
||||
return validatorFactory.getValidator();
|
||||
}
|
||||
|
||||
}
|
||||
@ -21,6 +21,7 @@ public class RegExValidator implements ConstraintValidator<RegEx, String> {
|
||||
|
||||
@Override
|
||||
public boolean isValid(String value, ConstraintValidatorContext constraintContext) {
|
||||
System.out.println("Validating value: " + value);
|
||||
if (value == null)
|
||||
return true;
|
||||
if (pattern.matcher(value).matches()) {
|
||||
|
||||
@ -1,55 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
inkscape:version="1.0 (4035a4f, 2020-05-01)"
|
||||
sodipodi:docname="edit2.svg"
|
||||
id="svg45"
|
||||
version="1.1"
|
||||
viewBox="0 0 1024 1024"
|
||||
height="200.00px"
|
||||
width="200px"
|
||||
class="icon">
|
||||
<metadata
|
||||
id="metadata51">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs49" />
|
||||
<sodipodi:namedview
|
||||
inkscape:current-layer="svg45"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:window-y="23"
|
||||
inkscape:window-x="0"
|
||||
inkscape:cy="162.63603"
|
||||
inkscape:cx="100"
|
||||
inkscape:zoom="4.135"
|
||||
showgrid="false"
|
||||
id="namedview47"
|
||||
inkscape:window-height="971"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0"
|
||||
guidetolerance="10"
|
||||
gridtolerance="10"
|
||||
objecttolerance="10"
|
||||
borderopacity="1"
|
||||
bordercolor="#666666"
|
||||
pagecolor="#ffffff" />
|
||||
<path
|
||||
style="stroke-width:0.872879"
|
||||
id="path43"
|
||||
d="m 851.96996,76.740834 c 34.21793,13.273184 63.02429,32.821286 79.26775,67.228716 7.05515,14.9465 11.36518,30.37233 11.36518,47.06185 -0.0962,57.73792 0.20107,115.47583 -0.0437,173.21375 -0.0962,23.13003 -13.23607,37.7802 -32.01484,37.0917 -18.61266,-0.67977 -31.87495,-15.89644 -32.00608,-38.34669 -0.29725,-51.07953 0.2273,-102.15907 -0.0962,-153.22989 -0.31473,-50.16443 -20.10762,-69.50338 -70.61273,-69.5208 q -299.62105,-0.0959 -599.25085,0 c -49.10632,0.0436 -68.71562,19.88798 -68.71562,68.72773 v 624.00523 c 0,48.85717 19.61805,68.51856 68.72436,68.63186 55.68937,0.13073 111.38748,-0.14816 167.07686,0.15687 23.69202,0.13073 38.63286,11.59988 40.41631,29.63153 1.89712,19.17334 -12.19571,34.75607 -34.20918,34.92161 -66.08414,0.48805 -132.21199,1.51649 -198.24367,-0.47055 C 125.77007,964.10064 75.876939,909.97956 75.343649,850.49861 74.609284,763.93967 75.098862,677.32844 75.081377,590.75207 75.063892,466.36064 76.91729,341.96049 74.347011,217.6562 72.887022,147.73451 100.75794,101.46573 165.76675,76.740834 Z M 969.36349,566.71567 c 0.17486,-42.62583 -32.5481,-89.85327 -73.17424,-106.62123 -42.94289,-17.72663 -78.682,-7.31201 -110.88043,25.38725 -74.80035,76.03103 -148.69149,153.1166 -226.73529,225.72257 -50.32151,46.87883 -69.01286,105.64513 -80.98127,168.4204 -8.13921,42.59098 21.34031,79.05516 63.07674,74.50588 55.50578,-6.05701 109.75265,-21.2214 150.94705,-62.99315 83.18436,-84.38887 165.54693,-169.57081 248.47775,-254.19499 19.6618,-20.04486 30.90459,-43.08773 29.26969,-70.2093 z M 546.9111,905.43025 c -16.61064,1.48157 -20.38737,-5.41212 -17.96572,-16.08819 11.53129,-50.81808 26.22733,-99.71882 65.11373,-138.33568 54.70148,-54.28671 108.15278,-109.81098 161.24564,-165.70128 11.73237,-12.34938 18.8662,-12.46267 30.02156,-0.18302 17.92201,19.73112 37.01551,38.43385 56.1702,57.01456 8.86484,8.60186 8.81239,15.06851 0.13989,24.01897 -61.37196,63.32433 -121.94837,127.40688 -183.59134,190.42618 -31.23675,31.90619 -75.40359,36.01103 -111.09024,48.84846 z M 918.465,570.19302 c -0.31473,8.07895 0,16.04461 -4.9832,23.40019 -14.05784,20.62878 -21.65503,21.66589 -39.25358,4.23557 -17.89577,-17.72663 -35.26702,-36.01974 -53.70483,-53.16246 -10.21992,-9.50823 -8.92603,-15.33867 0.64694,-24.89049 16.41832,-16.38449 34.34033,-19.26921 54.83261,-11.25998 26.49836,10.3449 42.52325,34.07627 42.46206,61.68589 z M 502.82296,240.23718 q -96.85754,0 -193.71508,0.0784 c -8.113,0 -16.50575,0 -24.26903,2.00449 -15.86753,4.0264 -26.29728,14.0924 -26.40218,31.31356 -0.10492,17.22114 9.50304,27.69676 25.86889,31.37456 a 120.64573,120.26917 0 0 0 26.42841,2.74527 q 191.45953,0.25274 382.98901,0.14816 c 11.19906,0 22.42437,-0.18302 32.86284,-4.50573 12.82517,-5.31625 19.77541,-15.08595 19.77541,-29.30036 0,-14.03141 -6.25084,-23.9754 -19.23337,-29.69255 -9.80902,-4.35757 -20.20379,-4.21813 -30.59856,-4.20941 H 502.81422 Z m -67.46544,233.04329 c 48.24081,0 96.49035,0.39219 144.72242,-0.22659 20.91193,-0.27017 35.6954,-14.33644 36.12378,-31.80161 0.45461,-18.65043 -14.40755,-32.09792 -36.91934,-32.78642 -14.08408,-0.43576 -28.20313,-0.13944 -42.30469,-0.13944 -80.15948,0 -160.31021,-0.19173 -240.46968,0.16558 -26.91799,0.12202 -43.71223,18.51972 -36.98054,40.00258 5.62139,17.89222 19.53062,24.8992 37.7761,24.84691 46.01148,-0.11329 92.03172,0 138.0432,-0.0697 z m -63.45267,169.07405 c 28.16816,0 56.33631,0.2876 84.49573,-0.0697 19.23338,-0.24401 30.51987,-10.54533 32.48692,-29.52694 a 30.782147,30.686068 0 0 0 -30.43245,-34.11112 q -84.45202,-1.6646 -168.95648,-0.12202 c -19.56559,0.33989 -31.90118,15.49555 -31.40286,33.40519 0.50706,18.30183 12.76396,29.86685 33.75458,30.27646 26.6732,0.52291 53.32891,0.13073 80.04582,0.14816 z" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 5.1 KiB |
@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1758698793059" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1512" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M354.40128 0c-87.04 0-157.44 70.55872-157.44 157.59872v275.68128H78.72c-21.6576 0-39.36256 17.69984-39.36256 39.36256v236.31872c0 21.6576 17.69984 39.35744 39.36256 39.35744h118.24128v118.08256c0 87.04 70.4 157.59872 157.44 157.59872h472.63744c87.04 0 157.59872-70.55872 157.59872-157.59872V315.0336c0-41.74848-38.9888-81.93024-107.52-149.27872l-29.11744-29.12256L818.87744 107.52C751.5392 38.9888 711.39328 0 669.59872 0H354.4064z m0 78.72h287.20128c28.35456 7.0912 27.99616 42.1376 27.99616 76.8v120.16128c0 21.6576 17.69984 39.35744 39.36256 39.35744h118.07744c39.38816 0 78.87872-0.0256 78.87872 39.36256v512c0 43.32032-35.55328 78.87872-78.87872 78.87872H354.4064c-43.32544 0-78.72-35.5584-78.72-78.87872v-118.08256h393.91744c21.66272 0 39.36256-17.69472 39.36256-39.35744V472.64256c0-21.66272-17.69984-39.36256-39.36256-39.36256H275.68128V157.59872c0-43.32032 35.39456-78.87872 78.72-78.87872zM261.2736 506.39872h20.16256l65.28 176.64h-23.04l-19.2-54.71744h-65.28l-19.2 54.71744h-23.04l64.31744-176.64z m-181.43744 0.96256h23.99744l40.32 89.27744 41.28256-89.27744h23.99744l-53.76 107.52v68.15744h-22.07744v-67.2l-53.76-108.47744z m290.87744 0h32.64l49.92 143.03744h0.96256l48.95744-143.03744h33.60256v175.67744h-22.08256v-106.55744c0-10.88 0.32256-26.56256 0.96256-47.04256h-0.96256l-52.79744 153.6h-19.2l-52.80256-153.6h-0.95744c1.28 22.4 1.92 38.72256 1.92 48.96256v104.63744h-20.16256V507.36128z m214.08256 0h22.07744v155.52h69.12v20.15744H584.8064V507.36128z m-312.96 23.04c-1.92 8.96-4.80256 18.23744-8.64256 27.83744l-17.28 50.88256h51.84l-18.23744-50.88256c-3.84-10.88-6.4-20.15744-7.68-27.83744z" p-id="1513"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
@ -34,6 +34,7 @@ import org.unbescape.html.HtmlEscape;
|
||||
|
||||
import io.onedev.commons.loader.AppLoader;
|
||||
import io.onedev.server.annotation.OmitName;
|
||||
import io.onedev.server.annotation.Shallow;
|
||||
import io.onedev.server.annotation.SubscriptionRequired;
|
||||
import io.onedev.server.util.ComponentContext;
|
||||
import io.onedev.server.util.EditContext;
|
||||
@ -294,7 +295,7 @@ public class BeanEditor extends ValueEditor<Serializable> {
|
||||
add(validatable -> {
|
||||
ComponentContext.push(newComponentContext());
|
||||
try {
|
||||
Validator validator = AppLoader.getInstance(Validator.class);
|
||||
Validator validator = AppLoader.getInstance(Validator.class, Shallow.class);
|
||||
for (var violation : validator.validate(validatable.getValue()))
|
||||
error(new Path(violation.getPropertyPath()), violation.getMessage());
|
||||
} finally {
|
||||
|
||||
@ -21,6 +21,7 @@ import io.onedev.server.annotation.ShowCondition;
|
||||
import io.onedev.server.annotation.SubscriptionRequired;
|
||||
import io.onedev.server.util.BeanUtils;
|
||||
import io.onedev.server.util.ComponentContext;
|
||||
import io.onedev.server.util.DependsOnUtils;
|
||||
import io.onedev.server.util.EditContext;
|
||||
import io.onedev.server.util.ReflectionUtils;
|
||||
|
||||
@ -175,28 +176,9 @@ public class PropertyDescriptor implements Serializable {
|
||||
if (dependencyProperty == null) {
|
||||
throw new ExplicitException("Dependency property not found: " + dependsOn.property());
|
||||
}
|
||||
var dependencyPropertyValue = EditContext.get().getInputValue(dependsOn.property());
|
||||
if (dependsOn.value().length() != 0) {
|
||||
if (dependencyPropertyValue != null && dependencyPropertyValue.toString().equals(dependsOn.value())) {
|
||||
if (dependsOn.inverse())
|
||||
return false;
|
||||
} else if (!dependsOn.inverse()) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (dependencyProperty.getPropertyClass() == boolean.class) {
|
||||
boolean requiredPropertyValue = !dependsOn.inverse();
|
||||
if (requiredPropertyValue != (boolean)dependencyPropertyValue)
|
||||
return false;
|
||||
} else if (dependencyProperty.getPropertyClass() == int.class || dependencyProperty.getPropertyClass() == long.class || dependencyProperty.getPropertyClass() == double.class || dependencyProperty.getPropertyClass() == float.class) {
|
||||
int dependencyPropertyIntValue = (int) dependencyPropertyValue;
|
||||
if (dependsOn.inverse() && dependencyPropertyIntValue != 0 || !dependsOn.inverse() && dependencyPropertyIntValue == 0)
|
||||
return false;
|
||||
} else {
|
||||
if (dependsOn.inverse() && dependencyPropertyValue != null || !dependsOn.inverse() && dependencyPropertyValue == null)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
var dependencyPropertyValue = EditContext.get().getInputValue(dependsOn.property());
|
||||
if (!DependsOnUtils.isPropertyVisible(dependsOn, dependencyProperty.getPropertyClass(), dependencyPropertyValue))
|
||||
return false;
|
||||
}
|
||||
getDependencyPropertyNames().remove(getPropertyName());
|
||||
for (String dependencyPropertyName: getDependencyPropertyNames()) {
|
||||
|
||||
@ -1,5 +1,40 @@
|
||||
package io.onedev.server.web.editable.buildspec.step;
|
||||
|
||||
import static io.onedev.server.web.component.floating.AlignPlacement.bottom;
|
||||
import static io.onedev.server.web.translation.Translation._T;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import org.apache.commons.lang3.SerializationUtils;
|
||||
import org.apache.wicket.Component;
|
||||
import org.apache.wicket.ajax.AjaxRequestTarget;
|
||||
import org.apache.wicket.ajax.markup.html.AjaxLink;
|
||||
import org.apache.wicket.event.IEvent;
|
||||
import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator;
|
||||
import org.apache.wicket.extensions.markup.html.repeater.data.table.AbstractColumn;
|
||||
import org.apache.wicket.extensions.markup.html.repeater.data.table.DataTable;
|
||||
import org.apache.wicket.extensions.markup.html.repeater.data.table.HeadersToolbar;
|
||||
import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn;
|
||||
import org.apache.wicket.extensions.markup.html.repeater.data.table.NoRecordsToolbar;
|
||||
import org.apache.wicket.markup.ComponentTag;
|
||||
import org.apache.wicket.markup.head.CssHeaderItem;
|
||||
import org.apache.wicket.markup.head.IHeaderResponse;
|
||||
import org.apache.wicket.markup.html.WebMarkupContainer;
|
||||
import org.apache.wicket.markup.html.basic.Label;
|
||||
import org.apache.wicket.markup.html.panel.Fragment;
|
||||
import org.apache.wicket.markup.repeater.Item;
|
||||
import org.apache.wicket.markup.repeater.data.IDataProvider;
|
||||
import org.apache.wicket.markup.repeater.data.ListDataProvider;
|
||||
import org.apache.wicket.model.IModel;
|
||||
import org.apache.wicket.model.Model;
|
||||
import org.apache.wicket.util.convert.ConversionException;
|
||||
import org.unbescape.html.HtmlEscape;
|
||||
|
||||
import io.onedev.server.buildspec.BuildSpec;
|
||||
import io.onedev.server.buildspec.BuildSpecAware;
|
||||
import io.onedev.server.buildspec.ParamSpecAware;
|
||||
@ -20,35 +55,6 @@ import io.onedev.server.web.editable.PropertyEditor;
|
||||
import io.onedev.server.web.editable.PropertyUpdating;
|
||||
import io.onedev.server.web.util.TextUtils;
|
||||
|
||||
import org.apache.commons.lang3.SerializationUtils;
|
||||
import org.apache.wicket.Component;
|
||||
import org.apache.wicket.ajax.AjaxRequestTarget;
|
||||
import org.apache.wicket.ajax.markup.html.AjaxLink;
|
||||
import org.apache.wicket.event.IEvent;
|
||||
import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator;
|
||||
import org.apache.wicket.extensions.markup.html.repeater.data.table.*;
|
||||
import org.apache.wicket.markup.ComponentTag;
|
||||
import org.apache.wicket.markup.head.CssHeaderItem;
|
||||
import org.apache.wicket.markup.head.IHeaderResponse;
|
||||
import org.apache.wicket.markup.html.WebMarkupContainer;
|
||||
import org.apache.wicket.markup.html.basic.Label;
|
||||
import org.apache.wicket.markup.html.panel.Fragment;
|
||||
import org.apache.wicket.markup.repeater.Item;
|
||||
import org.apache.wicket.markup.repeater.data.IDataProvider;
|
||||
import org.apache.wicket.markup.repeater.data.ListDataProvider;
|
||||
import org.apache.wicket.model.IModel;
|
||||
import org.apache.wicket.model.Model;
|
||||
import org.apache.wicket.util.convert.ConversionException;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static io.onedev.server.web.component.floating.AlignPlacement.bottom;
|
||||
import static io.onedev.server.web.translation.Translation._T;
|
||||
|
||||
class StepListEditPanel extends PropertyEditor<List<Serializable>> {
|
||||
|
||||
private final List<Step> steps;
|
||||
@ -112,7 +118,11 @@ class StepListEditPanel extends PropertyEditor<List<Serializable>> {
|
||||
|
||||
@Override
|
||||
public void populateItem(Item<ICellPopulator<Step>> cellItem, String componentId, IModel<Step> rowModel) {
|
||||
cellItem.add(new Label(componentId, _T(TextUtils.getDisplayValue(rowModel.getObject().getCondition()))));
|
||||
var condition = rowModel.getObject().getCondition();
|
||||
if (condition != null)
|
||||
cellItem.add(new Label(componentId, _T(TextUtils.getDisplayValue(condition))));
|
||||
else
|
||||
cellItem.add(new Label(componentId, "<i>" + HtmlEscape.escapeHtml5(_T("Unspecified")) + "</i>").setEscapeModelStrings(false));
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@ -224,14 +224,14 @@ onedev.server = {
|
||||
setTimeout(function() {
|
||||
// do not use :visible selector directly for performance reason
|
||||
var focusibleSelector = "input[type=text], input[type=password], input:not([type]), textarea, .CodeMirror";
|
||||
var attentionSelector = ".is-invalid";
|
||||
var attentionSelector = ".feedbackPanelERROR";
|
||||
var $attention = $containers.find(attentionSelector).addBack(attentionSelector).filter(":visible:first");
|
||||
if ($attention.length == 0) {
|
||||
attentionSelector = ".feedbackPanelERROR";
|
||||
attentionSelector = ".feedbackPanelWARNING";
|
||||
$attention = $containers.find(attentionSelector).addBack(attentionSelector).filter(":visible:first");
|
||||
}
|
||||
if ($attention.length == 0) {
|
||||
attentionSelector = ".feedbackPanelWARNING";
|
||||
attentionSelector = ".is-invalid";
|
||||
$attention = $containers.find(attentionSelector).addBack(attentionSelector).filter(":visible:first");
|
||||
}
|
||||
|
||||
|
||||
@ -9,8 +9,8 @@
|
||||
</div>
|
||||
<div wicket:id="editPlainTab" class="tab edit-plain mr-4 d-flex align-items-stretch">
|
||||
<a class="d-flex align-items-center font-weight-bold">
|
||||
<wicket:svg href="edit-file" class="icon mr-1"></wicket:svg>
|
||||
<wicket:t>Edit Source</wicket:t>
|
||||
<wicket:svg href="yaml" class="icon mr-1"></wicket:svg>
|
||||
<wicket:t>YAML</wicket:t>
|
||||
</a>
|
||||
</div>
|
||||
<div wicket:id="saveTab" class="save tab mr-4 d-flex align-items-stretch">
|
||||
|
||||
@ -17,7 +17,6 @@ public class TestPage extends BasePage {
|
||||
@Override
|
||||
protected void onInitialize() {
|
||||
super.onInitialize();
|
||||
|
||||
add(new Link<Void>("test") {
|
||||
|
||||
@Override
|
||||
|
||||
@ -11,6 +11,7 @@ import static org.hibernate.validator.internal.util.logging.Messages.MESSAGES;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Executable;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@ -23,6 +24,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.validation.ConstraintValidatorFactory;
|
||||
import javax.validation.ConstraintViolation;
|
||||
@ -66,7 +68,9 @@ import org.hibernate.validator.internal.metadata.aggregated.ReturnValueMetaData;
|
||||
import org.hibernate.validator.internal.metadata.core.MetaConstraint;
|
||||
import org.hibernate.validator.internal.metadata.facets.Cascadable;
|
||||
import org.hibernate.validator.internal.metadata.facets.Validatable;
|
||||
import org.hibernate.validator.internal.metadata.location.AbstractPropertyConstraintLocation;
|
||||
import org.hibernate.validator.internal.metadata.location.ConstraintLocation.ConstraintLocationKind;
|
||||
import org.hibernate.validator.internal.metadata.location.TypeArgumentConstraintLocation;
|
||||
import org.hibernate.validator.internal.util.Contracts;
|
||||
import org.hibernate.validator.internal.util.ExecutableHelper;
|
||||
import org.hibernate.validator.internal.util.ReflectionHelper;
|
||||
@ -74,7 +78,14 @@ import org.hibernate.validator.internal.util.TypeHelper;
|
||||
import org.hibernate.validator.internal.util.logging.Log;
|
||||
import org.hibernate.validator.internal.util.logging.LoggerFactory;
|
||||
|
||||
import io.onedev.commons.utils.ExplicitException;
|
||||
import io.onedev.server.annotation.ClassValidating;
|
||||
import io.onedev.server.annotation.DependsOn;
|
||||
import io.onedev.server.annotation.ShowCondition;
|
||||
import io.onedev.server.util.BeanUtils;
|
||||
import io.onedev.server.util.DependsOnUtils;
|
||||
import io.onedev.server.util.EditContext;
|
||||
import io.onedev.server.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* The main Bean Validation class. This is the core processing class of Hibernate Validator.
|
||||
@ -401,7 +412,7 @@ public class ValidatorImpl implements Validator, ExecutableValidator {
|
||||
while ( groupIterator.hasNext() ) {
|
||||
Group group = groupIterator.next();
|
||||
valueContext.setCurrentGroup( group.getDefiningClass() );
|
||||
validateConstraintsForCurrentGroup( validationContext, valueContext );
|
||||
validatePropertyConstraintsForCurrentGroup( validationContext, valueContext );
|
||||
if ( shouldFailFast( validationContext ) ) {
|
||||
return validationContext.getFailingConstraints();
|
||||
}
|
||||
@ -416,6 +427,20 @@ public class ValidatorImpl implements Validator, ExecutableValidator {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Validate class constraints after cascading constraints. This ensures that when class validator
|
||||
* is called, all properties have been validated recursively
|
||||
*/
|
||||
groupIterator = validationOrder.getGroupIterator();
|
||||
while ( groupIterator.hasNext() ) {
|
||||
Group group = groupIterator.next();
|
||||
valueContext.setCurrentGroup( group.getDefiningClass() );
|
||||
validateClassConstraintsForCurrentGroup( validationContext, valueContext );
|
||||
if ( shouldFailFast( validationContext ) ) {
|
||||
return validationContext.getFailingConstraints();
|
||||
}
|
||||
}
|
||||
|
||||
// now we process sequences. For sequences I have to traverse the object graph since I have to stop processing when an error occurs.
|
||||
Iterator<Sequence> sequenceIterator = validationOrder.getSequenceIterator();
|
||||
while ( sequenceIterator.hasNext() ) {
|
||||
@ -426,7 +451,7 @@ public class ValidatorImpl implements Validator, ExecutableValidator {
|
||||
for ( Group group : groupOfGroups ) {
|
||||
valueContext.setCurrentGroup( group.getDefiningClass() );
|
||||
|
||||
validateConstraintsForCurrentGroup( validationContext, valueContext );
|
||||
validatePropertyConstraintsForCurrentGroup( validationContext, valueContext );
|
||||
if ( shouldFailFast( validationContext ) ) {
|
||||
return validationContext.getFailingConstraints();
|
||||
}
|
||||
@ -435,6 +460,15 @@ public class ValidatorImpl implements Validator, ExecutableValidator {
|
||||
if ( shouldFailFast( validationContext ) ) {
|
||||
return validationContext.getFailingConstraints();
|
||||
}
|
||||
|
||||
/*
|
||||
* Validate class constraints after cascading constraints. This ensures that when class validator
|
||||
* is called, all properties have been validated recursively
|
||||
*/
|
||||
validateClassConstraintsForCurrentGroup( validationContext, valueContext );
|
||||
if ( shouldFailFast( validationContext ) ) {
|
||||
return validationContext.getFailingConstraints();
|
||||
}
|
||||
}
|
||||
if ( validationContext.getFailingConstraints().size() > numberOfViolations ) {
|
||||
break;
|
||||
@ -607,14 +641,14 @@ public class ValidatorImpl implements Validator, ExecutableValidator {
|
||||
CascadingMetaData effectiveCascadingMetaData = cascadingMetaData.addRuntimeContainerSupport( valueExtractorManager, value.getClass() );
|
||||
|
||||
// validate cascading on the annotated object
|
||||
if ( effectiveCascadingMetaData.isCascading() ) {
|
||||
if ( effectiveCascadingMetaData.isCascading() && isCascadeValidationRequired( validationContext, valueContext, cascadable ) ) {
|
||||
validateCascadedAnnotatedObjectForCurrentGroup( value, validationContext, valueContext, effectiveCascadingMetaData );
|
||||
}
|
||||
|
||||
if ( effectiveCascadingMetaData.isContainer() ) {
|
||||
ContainerCascadingMetaData containerCascadingMetaData = effectiveCascadingMetaData.as( ContainerCascadingMetaData.class );
|
||||
|
||||
if ( containerCascadingMetaData.hasContainerElementsMarkedForCascading() ) {
|
||||
if ( containerCascadingMetaData.hasContainerElementsMarkedForCascading() && isCascadeValidationRequired( validationContext, valueContext, cascadable ) ) {
|
||||
// validate cascading on the container elements
|
||||
validateCascadedContainerElementsForCurrentGroup( value, validationContext, valueContext,
|
||||
containerCascadingMetaData.getContainerElementTypesCascadingMetaData() );
|
||||
@ -1279,6 +1313,56 @@ public class ValidatorImpl implements Validator, ExecutableValidator {
|
||||
private boolean isValidationRequired(BaseBeanValidationContext<?> validationContext,
|
||||
ValueContext<?, ?> valueContext,
|
||||
MetaConstraint<?> metaConstraint) {
|
||||
var location = metaConstraint.getLocation();
|
||||
if (location instanceof TypeArgumentConstraintLocation) {
|
||||
location = ((TypeArgumentConstraintLocation) location).getOuterDelegate();
|
||||
}
|
||||
if (location instanceof AbstractPropertyConstraintLocation && valueContext.getCurrentBean() != null) {
|
||||
var bean = valueContext.getCurrentBean();
|
||||
var propertyName = ((AbstractPropertyConstraintLocation<?>) location).getPropertyName();
|
||||
var getter = BeanUtils.findGetter(bean.getClass(), propertyName);
|
||||
if (getter == null) {
|
||||
throw new ExplicitException("Getter not found for property: " + propertyName);
|
||||
}
|
||||
var dependsOn = getter.getAnnotation(DependsOn.class);
|
||||
if (dependsOn != null) {
|
||||
var dependencyGetter = BeanUtils.findGetter(bean.getClass(), dependsOn.property());
|
||||
if (dependencyGetter == null) {
|
||||
throw new ExplicitException("Getter not found for property: " + dependsOn.property());
|
||||
}
|
||||
try {
|
||||
var dependencyPropertyValue = dependencyGetter.invoke(bean);
|
||||
if (!DependsOnUtils.isPropertyVisible(dependsOn, dependencyGetter.getReturnType(), dependencyPropertyValue))
|
||||
return false;
|
||||
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
|
||||
throw new ExplicitException("Error invoking getter for property: " + dependsOn.property(), e);
|
||||
}
|
||||
}
|
||||
var showCondition = getter.getAnnotation(ShowCondition.class);
|
||||
if (showCondition != null) {
|
||||
EditContext.push(new EditContext() {
|
||||
@Override
|
||||
public Object getInputValue(String name) {
|
||||
var getter = BeanUtils.findGetter(bean.getClass(), name);
|
||||
if (getter == null) {
|
||||
throw new ExplicitException("Getter not found for property: " + name);
|
||||
}
|
||||
try {
|
||||
return getter.invoke(bean);
|
||||
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
|
||||
throw new ExplicitException("Error invoking getter for property: " + dependsOn.property(), e);
|
||||
}
|
||||
}
|
||||
});
|
||||
try {
|
||||
if (!(boolean)ReflectionUtils.invokeStaticMethod(bean.getClass(), showCondition.value()))
|
||||
return false;
|
||||
} finally {
|
||||
EditContext.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check if this validation context is qualified to validate the current meta constraint.
|
||||
// For instance, in the case of validateProperty()/validateValue(), the current meta constraint
|
||||
// could be for another property and, in this case, we don't validate it.
|
||||
@ -1336,6 +1420,59 @@ public class ValidatorImpl implements Validator, ExecutableValidator {
|
||||
|| isReturnValueValidation( path );
|
||||
}
|
||||
|
||||
private boolean isCascadeValidationRequired(BaseBeanValidationContext<?> validationContext, ValueContext<?, Object> valueContext, Cascadable cascadable) {
|
||||
if (valueContext.getCurrentBean() != null && cascadable.getConstraintLocationKind() == ConstraintLocationKind.GETTER) {
|
||||
var bean = valueContext.getCurrentBean();
|
||||
var currentPath = valueContext.getPropertyPath();
|
||||
var leafNode = currentPath.getLeafNode();
|
||||
if (leafNode != null) {
|
||||
var propertyName = leafNode.getName();
|
||||
Method getter = BeanUtils.findGetter(bean.getClass(), propertyName);
|
||||
if (getter == null) {
|
||||
throw new ExplicitException("Getter not found for property: " + propertyName);
|
||||
}
|
||||
DependsOn dependsOn = getter.getAnnotation(DependsOn.class);
|
||||
if (dependsOn != null) {
|
||||
Method dependencyGetter = BeanUtils.findGetter(bean.getClass(), dependsOn.property());
|
||||
if (dependencyGetter == null) {
|
||||
throw new ExplicitException("Getter not found for property: " + dependsOn.property());
|
||||
}
|
||||
try {
|
||||
var dependencyPropertyValue = dependencyGetter.invoke(bean);
|
||||
if (!DependsOnUtils.isPropertyVisible(dependsOn, dependencyGetter.getReturnType(), dependencyPropertyValue))
|
||||
return false;
|
||||
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
|
||||
throw new ExplicitException("Error invoking getter for property: " + dependsOn.property(), e);
|
||||
}
|
||||
}
|
||||
ShowCondition showCondition = getter.getAnnotation(ShowCondition.class);
|
||||
if (showCondition != null) {
|
||||
EditContext.push(new EditContext() {
|
||||
@Override
|
||||
public Object getInputValue(String name) {
|
||||
Method getter = BeanUtils.findGetter(bean.getClass(), name);
|
||||
if (getter == null) {
|
||||
throw new ExplicitException("Getter not found for property: " + name);
|
||||
}
|
||||
try {
|
||||
return getter.invoke(bean);
|
||||
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
|
||||
throw new ExplicitException("Error invoking getter for property: " + dependsOn.property(), e);
|
||||
}
|
||||
}
|
||||
});
|
||||
try {
|
||||
if (!(boolean)ReflectionUtils.invokeStaticMethod(bean.getClass(), showCondition.value()))
|
||||
return false;
|
||||
} finally {
|
||||
EditContext.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isCascadeRequired(BaseBeanValidationContext<?> validationContext, Object traversableObject, PathImpl path,
|
||||
ConstraintLocationKind constraintLocationKind) {
|
||||
if ( needToCallTraversableResolver( path, constraintLocationKind ) ) {
|
||||
@ -1393,4 +1530,146 @@ public class ValidatorImpl implements Validator, ExecutableValidator {
|
||||
private Object getCascadableValue(BaseBeanValidationContext<?> validationContext, Object object, Cascadable cascadable) {
|
||||
return cascadable.getValue( object );
|
||||
}
|
||||
|
||||
private void validatePropertyConstraintsForCurrentGroup(BaseBeanValidationContext<?> validationContext, BeanValueContext<?, Object> valueContext) {
|
||||
if ( !valueContext.validatingDefault() ) {
|
||||
validatePropertyConstraintsForNonDefaultGroup( validationContext, valueContext );
|
||||
}
|
||||
else {
|
||||
validatePropertyConstraintsForDefaultGroup( validationContext, valueContext );
|
||||
}
|
||||
}
|
||||
|
||||
private void validateClassConstraintsForCurrentGroup(BaseBeanValidationContext<?> validationContext, BeanValueContext<?, Object> valueContext) {
|
||||
if ( !valueContext.validatingDefault() ) {
|
||||
validateClassConstraintsForNonDefaultGroup( validationContext, valueContext );
|
||||
}
|
||||
else {
|
||||
validateClassConstraintsForDefaultGroup( validationContext, valueContext );
|
||||
}
|
||||
}
|
||||
|
||||
private void validatePropertyConstraintsForNonDefaultGroup(BaseBeanValidationContext<?> validationContext, BeanValueContext<?, Object> valueContext) {
|
||||
Set<MetaConstraint<?>> propertyConstraints = filterPropertyConstraints(valueContext.getCurrentBeanMetaData().getMetaConstraints());
|
||||
validateMetaConstraints( validationContext, valueContext, valueContext.getCurrentBean(), propertyConstraints );
|
||||
validationContext.markCurrentBeanAsProcessed( valueContext );
|
||||
}
|
||||
|
||||
private void validateClassConstraintsForNonDefaultGroup(BaseBeanValidationContext<?> validationContext, BeanValueContext<?, Object> valueContext) {
|
||||
Set<MetaConstraint<?>> classConstraints = filterClassConstraints(valueContext.getCurrentBeanMetaData().getMetaConstraints());
|
||||
validateMetaConstraints( validationContext, valueContext, valueContext.getCurrentBean(), classConstraints );
|
||||
validationContext.markCurrentBeanAsProcessed( valueContext );
|
||||
}
|
||||
|
||||
private <U> void validatePropertyConstraintsForDefaultGroup(BaseBeanValidationContext<?> validationContext, BeanValueContext<U, Object> valueContext) {
|
||||
final BeanMetaData<U> beanMetaData = valueContext.getCurrentBeanMetaData();
|
||||
final Map<Class<?>, Class<?>> validatedInterfaces = new HashMap<>();
|
||||
|
||||
// evaluating the constraints of a bean per class in hierarchy, this is necessary to detect potential default group re-definitions
|
||||
for ( Class<? super U> clazz : beanMetaData.getClassHierarchy() ) {
|
||||
BeanMetaData<? super U> hostingBeanMetaData = beanMetaDataManager.getBeanMetaData( clazz );
|
||||
boolean defaultGroupSequenceIsRedefined = hostingBeanMetaData.isDefaultGroupSequenceRedefined();
|
||||
|
||||
// if the current class redefined the default group sequence, this sequence has to be applied to all the class hierarchy.
|
||||
if ( defaultGroupSequenceIsRedefined ) {
|
||||
Iterator<Sequence> defaultGroupSequence = hostingBeanMetaData.getDefaultValidationSequence( valueContext.getCurrentBean() );
|
||||
Set<MetaConstraint<?>> metaConstraints = filterPropertyConstraints(hostingBeanMetaData.getMetaConstraints());
|
||||
|
||||
while ( defaultGroupSequence.hasNext() ) {
|
||||
for ( GroupWithInheritance groupOfGroups : defaultGroupSequence.next() ) {
|
||||
boolean validationSuccessful = true;
|
||||
|
||||
for ( Group defaultSequenceMember : groupOfGroups ) {
|
||||
validationSuccessful = validateConstraintsForSingleDefaultGroupElement( validationContext, valueContext, validatedInterfaces, clazz,
|
||||
metaConstraints, defaultSequenceMember ) && validationSuccessful;
|
||||
}
|
||||
|
||||
validationContext.markCurrentBeanAsProcessed( valueContext );
|
||||
|
||||
if ( !validationSuccessful ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// fast path in case the default group sequence hasn't been redefined
|
||||
else {
|
||||
Set<MetaConstraint<?>> metaConstraints = filterPropertyConstraints(hostingBeanMetaData.getDirectMetaConstraints());
|
||||
validateConstraintsForSingleDefaultGroupElement( validationContext, valueContext, validatedInterfaces, clazz, metaConstraints,
|
||||
Group.DEFAULT_GROUP );
|
||||
validationContext.markCurrentBeanAsProcessed( valueContext );
|
||||
}
|
||||
|
||||
if ( defaultGroupSequenceIsRedefined ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private <U> void validateClassConstraintsForDefaultGroup(BaseBeanValidationContext<?> validationContext, BeanValueContext<U, Object> valueContext) {
|
||||
final BeanMetaData<U> beanMetaData = valueContext.getCurrentBeanMetaData();
|
||||
final Map<Class<?>, Class<?>> validatedInterfaces = new HashMap<>();
|
||||
|
||||
// evaluating the constraints of a bean per class in hierarchy, this is necessary to detect potential default group re-definitions
|
||||
for ( Class<? super U> clazz : beanMetaData.getClassHierarchy() ) {
|
||||
BeanMetaData<? super U> hostingBeanMetaData = beanMetaDataManager.getBeanMetaData( clazz );
|
||||
boolean defaultGroupSequenceIsRedefined = hostingBeanMetaData.isDefaultGroupSequenceRedefined();
|
||||
|
||||
// if the current class redefined the default group sequence, this sequence has to be applied to all the class hierarchy.
|
||||
if ( defaultGroupSequenceIsRedefined ) {
|
||||
Iterator<Sequence> defaultGroupSequence = hostingBeanMetaData.getDefaultValidationSequence( valueContext.getCurrentBean() );
|
||||
Set<MetaConstraint<?>> metaConstraints = filterClassConstraints(hostingBeanMetaData.getMetaConstraints());
|
||||
|
||||
while ( defaultGroupSequence.hasNext() ) {
|
||||
for ( GroupWithInheritance groupOfGroups : defaultGroupSequence.next() ) {
|
||||
boolean validationSuccessful = true;
|
||||
|
||||
for ( Group defaultSequenceMember : groupOfGroups ) {
|
||||
validationSuccessful = validateConstraintsForSingleDefaultGroupElement( validationContext, valueContext, validatedInterfaces, clazz,
|
||||
metaConstraints, defaultSequenceMember ) && validationSuccessful;
|
||||
}
|
||||
|
||||
validationContext.markCurrentBeanAsProcessed( valueContext );
|
||||
|
||||
if ( !validationSuccessful ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// fast path in case the default group sequence hasn't been redefined
|
||||
else {
|
||||
Set<MetaConstraint<?>> metaConstraints = filterClassConstraints(hostingBeanMetaData.getDirectMetaConstraints());
|
||||
validateConstraintsForSingleDefaultGroupElement( validationContext, valueContext, validatedInterfaces, clazz, metaConstraints,
|
||||
Group.DEFAULT_GROUP );
|
||||
validationContext.markCurrentBeanAsProcessed( valueContext );
|
||||
}
|
||||
|
||||
if ( defaultGroupSequenceIsRedefined ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Set<MetaConstraint<?>> filterPropertyConstraints(Set<MetaConstraint<?>> allConstraints) {
|
||||
return allConstraints.stream()
|
||||
.filter(this::isPropertyConstraint)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
private Set<MetaConstraint<?>> filterClassConstraints(Set<MetaConstraint<?>> allConstraints) {
|
||||
return allConstraints.stream()
|
||||
.filter(this::isClassConstraint)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
private boolean isPropertyConstraint(MetaConstraint<?> metaConstraint) {
|
||||
ConstraintLocationKind kind = metaConstraint.getLocation().getKind();
|
||||
return kind == ConstraintLocationKind.FIELD || kind == ConstraintLocationKind.GETTER;
|
||||
}
|
||||
|
||||
private boolean isClassConstraint(MetaConstraint<?> metaConstraint) {
|
||||
return metaConstraint.getLocation().getKind() == ConstraintLocationKind.TYPE;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1 +1 @@
|
||||
Subproject commit c4d70741345fe3851e7a8545fe7f6d92bd1f6137
|
||||
Subproject commit 1d3daa461ecadfd75cd550740c583afdf2e76431
|
||||
@ -33,8 +33,7 @@ public class ServerShellModule extends AbstractPluginModule {
|
||||
return Sets.newHashSet(ServerShellExecutor.class);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user