Skip to content

Commit

Permalink
CAY-2846 Modeler: Allow to disable some validation rules in the proje…
Browse files Browse the repository at this point in the history
…ct (#617)

* Provide core logic

* Add validation config tab

* Enhancements in config saving and loading

* Build validators with config supplier

* Clean up

* Apply suggestions from code review

* Data channel -> data domain

* Refactor accessing to validation config

* Fix some UI issues, remove description pane

* Add inspection tooltip

* Use inspection name as description for now

* Fix typos

* Simplify validation performer

* Refactor UpdateValidationConfigAction

* Add some validation config actions to menus

* Clean up

* Add validation config loading test

* Fix selection error after inspection disabling

* Remove unused from ShowValidationConfigAction

* Move ShowValidationConfigAction to project menu

* Fix some tree UI issues on macOS
  • Loading branch information
m-dzianishchyts committed Jul 2, 2024
1 parent 009c753 commit 24ffdfd
Show file tree
Hide file tree
Showing 60 changed files with 3,294 additions and 916 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,36 +25,51 @@
import org.apache.cayenne.util.Util;
import org.apache.cayenne.validation.ValidationResult;

import java.util.function.Supplier;

/**
* Base validation for all query types
*/
class BaseQueryValidator extends ConfigurationNodeValidator {
abstract class BaseQueryValidator<T extends QueryDescriptor> extends ConfigurationNodeValidator<T> {

void validateCacheGroup(QueryDescriptor query, ValidationResult validationResult) {
String cacheGroup = query.getProperty(QueryMetadata.CACHE_GROUPS_PROPERTY);
if(cacheGroup != null && cacheGroup.contains(",")) {
addFailure(validationResult, query, "Invalid cache group \"%s\", " +
"multiple groups are deprecated", cacheGroup);
}
/**
* @param configSupplier the config defining the behavior of this validator.
* @since 5.0
*/
public BaseQueryValidator(Supplier<ValidationConfig> configSupplier) {
super(configSupplier);
}

@Override
public void validate(T node, ValidationResult validationResult) {
validateQuery(node, validationResult);
}

protected Performer<T> validateQuery(T query, ValidationResult validationResult) {
return on(query, validationResult)
.performIfEnabled(Inspection.QUERY_NO_NAME, this::checkForName)
.performIfEnabled(Inspection.QUERY_NAME_DUPLICATE, this::checkForNameDuplicates)
.performIfEnabled(Inspection.QUERY_MULTI_CACHE_GROUP, this::checkForMultiCacheGroup);
}

void validateName(QueryDescriptor query, ValidationResult validationResult) {
final String name = query.getName();
void checkForName(T query, ValidationResult validationResult) {

// Must have name
String name = query.getName();
if (Util.isEmptyString(name)) {
addFailure(validationResult, query, "Unnamed " + query.getType());
return;
}
}

void checkForNameDuplicates(T query, ValidationResult validationResult) {
String name = query.getName();
DataMap map = query.getDataMap();
if (map == null) {
if (map == null || Util.isEmptyString(name)) {
return;
}

// check for duplicate names in the parent context
if(hasDuplicateQueryDescriptorInDataMap(query, map)) {
if (hasDuplicateQueryDescriptorInDataMap(query, map)) {
addFailure(validationResult, query, "Duplicate query name: %s", name);
return;
}
Expand All @@ -71,14 +86,21 @@ void validateName(QueryDescriptor query, ValidationResult validationResult) {
}

if (hasDuplicateQueryDescriptorInDataMap(query, nextMap)) {
addFailure(validationResult, query,
"Duplicate %s name in another DataMap: %s",
addFailure(validationResult, query, "Duplicate %s name in another DataMap: %s",
query.getType(), name);
return;
}
}
}

void checkForMultiCacheGroup(T query, ValidationResult validationResult) {
String cacheGroup = query.getProperty(QueryMetadata.CACHE_GROUPS_PROPERTY);
if (cacheGroup != null && cacheGroup.contains(",")) {
addFailure(validationResult, query, "Invalid cache group '%s', multiple groups are deprecated",
cacheGroup);
}
}

private boolean hasDuplicateQueryDescriptorInDataMap(QueryDescriptor queryDescriptor, DataMap dataMap) {
for (final QueryDescriptor otherQuery : dataMap.getQueryDescriptors()) {
if (otherQuery == queryDescriptor) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,29 +18,82 @@
****************************************************************/
package org.apache.cayenne.project.validation;

import org.apache.cayenne.configuration.ConfigurationNode;
import org.apache.cayenne.validation.SimpleValidationFailure;
import org.apache.cayenne.validation.ValidationFailure;
import org.apache.cayenne.validation.ValidationResult;

import java.util.ArrayList;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Predicate;
import java.util.function.Supplier;

/**
* A base superclass of various node validators.
*
*
* @since 3.1
*/
public abstract class ConfigurationNodeValidator {
public abstract class ConfigurationNodeValidator<T extends ConfigurationNode> {

protected final Supplier<ValidationConfig> configSupplier;

/**
* @param configSupplier the config defining the behavior of this validator.
* @since 5.0
*/
public ConfigurationNodeValidator(Supplier<ValidationConfig> configSupplier) {
this.configSupplier = configSupplier;
}

public void addFailure(
ValidationResult validationResult,
Object source,
String messageFormat,
Object... messageParameters) {
/**
* @param node the node that needs to be validated.
* @param validationResult the appendable validation result.
* @since 5.0
*/
public abstract void validate(T node, ValidationResult validationResult);

public void addFailure(ValidationResult validationResult, T source, String messageFormat,
Object... messageParameters) {
String message = String.format(messageFormat, messageParameters);
validationResult.addFailure(new SimpleValidationFailure(source, message));
validationResult.addFailure(new ProjectValidationFailure(source, message));
}

public void addFailure(ValidationResult validationResult, SimpleValidationFailure failure) {
validationResult.addFailure(failure);
}

public void addFailure(
ValidationResult validationResult,
SimpleValidationFailure failure) {
validationResult.addFailure(failure);

protected Performer<T> on(T node, ValidationResult validationResult) {
return new Performer<>(node, validationResult);
}

protected class Performer<N> {

private final N node;
private final ValidationResult validationResult;

protected Performer(N node, ValidationResult validationResult) {
this.node = node;
this.validationResult = validationResult;
}

protected Performer<N> performIfEnabled(Inspection inspection, BiConsumer<N, ValidationResult> action) {
return performIfEnabled(inspection, () -> action.accept(node, validationResult));
}

protected Performer<N> performIfEnabled(Inspection inspection, Runnable action) {
if (configSupplier.get().isEnabled(inspection)) {
performAndMarkFailures(inspection, action);
}
return this;
}

private void performAndMarkFailures(Inspection inspection, Runnable action) {
List<ValidationFailure> failuresBefore = new ArrayList<>(validationResult.getFailures());
action.run();
validationResult.getFailures().stream()
.filter(Predicate.not(failuresBefore::contains))
.forEach(failure -> ((ProjectValidationFailure) failure).setInspection(inspection));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,25 @@
import org.apache.cayenne.util.Util;
import org.apache.cayenne.validation.ValidationResult;

class DataChannelValidator extends ConfigurationNodeValidator {
import java.util.function.Supplier;

void validate(DataChannelDescriptor domain, ValidationResult validationResult) {
class DataChannelValidator extends ConfigurationNodeValidator<DataChannelDescriptor> {

/**
* @param configSupplier the config defining the behavior of this validator.
* @since 5.0
*/
public DataChannelValidator(Supplier<ValidationConfig> configSupplier) {
super(configSupplier);
}

@Override
public void validate(DataChannelDescriptor node, ValidationResult validationResult) {
on(node, validationResult)
.performIfEnabled(Inspection.DATA_CHANNEL_NO_NAME, this::checkForName);
}

private void checkForName(DataChannelDescriptor domain, ValidationResult validationResult) {
String name = domain.getName();
if (Util.isEmptyString(name)) {
addFailure(validationResult, domain, "Unnamed DataDomain");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,45 +24,38 @@
import org.apache.cayenne.util.Util;
import org.apache.cayenne.validation.ValidationResult;

class DataMapValidator extends ConfigurationNodeValidator {
import java.util.function.Supplier;

void validate(DataMap map, ValidationResult validationResult) {
validateName(map, validationResult);
validateNodeLinks(map, validationResult);
validateJavaPackage(map, validationResult);
}

private void validateNodeLinks(DataMap map, ValidationResult validationResult) {
DataChannelDescriptor domain = map.getDataChannelDescriptor();
if (domain == null) {
return;
}
class DataMapValidator extends ConfigurationNodeValidator<DataMap> {

boolean unlinked = true;
int nodeCount = 0;
for (DataNodeDescriptor node : domain.getNodeDescriptors()) {
nodeCount++;
if (node.getDataMapNames().contains(map.getName())) {
unlinked = false;
break;
}
}
/**
* @param configSupplier the config defining the behavior of this validator.
* @since 5.0
*/
public DataMapValidator(Supplier<ValidationConfig> configSupplier) {
super(configSupplier);
}

if (unlinked && nodeCount > 0) {
addFailure(validationResult, map, "DataMap is not linked to any DataNodes");
}
@Override
public void validate(DataMap node, ValidationResult validationResult) {
on(node, validationResult)
.performIfEnabled(Inspection.DATA_MAP_NO_NAME, this::checkForName)
.performIfEnabled(Inspection.DATA_MAP_NAME_DUPLICATE, this::checkForNameDuplicates)
.performIfEnabled(Inspection.DATA_MAP_NODE_LINKAGE, this::checkForNodeLinkage)
.performIfEnabled(Inspection.DATA_MAP_JAVA_PACKAGE, this::validateJavaPackage);
}

private void validateName(DataMap map, ValidationResult validationResult) {
private void checkForName(DataMap map, ValidationResult validationResult) {
String name = map.getName();

if (Util.isEmptyString(name)) {
addFailure(validationResult, map, "Unnamed DataMap");
return;
}
}

private void checkForNameDuplicates(DataMap map, ValidationResult validationResult) {
String name = map.getName();
DataChannelDescriptor domain = map.getDataChannelDescriptor();
if (domain == null) {
if (domain == null || Util.isEmptyString(name)) {
return;
}

Expand All @@ -71,25 +64,44 @@ private void validateName(DataMap map, ValidationResult validationResult) {
if (otherMap == map) {
continue;
}

if (name.equals(otherMap.getName())) {
addFailure(validationResult, map, "Duplicate DataMap name: %s", name);
return;
}
}
}

private void checkForNodeLinkage(DataMap map, ValidationResult validationResult) {
DataChannelDescriptor domain = map.getDataChannelDescriptor();
if (domain == null) {
return;
}

boolean linked = false;
int nodeCount = 0;
for (DataNodeDescriptor node : domain.getNodeDescriptors()) {
nodeCount++;
if (node.getDataMapNames().contains(map.getName())) {
linked = true;
break;
}
}

if (!linked && nodeCount > 0) {
addFailure(validationResult, map, "DataMap is not linked to any DataNodes");
}
}

private void validateJavaPackage(DataMap map, ValidationResult validationResult) {
String javaPackage = map.getDefaultPackage();

if(Util.isEmptyString(javaPackage)) {
if (Util.isEmptyString(javaPackage)) {
addFailure(validationResult, map, "Java package is not set in DataMap '%s'", map.getName());
return;
}

NameValidationHelper helper = NameValidationHelper.getInstance();
String invalidChars = helper.invalidCharsInJavaClassName(javaPackage);
if(invalidChars != null) {
if (invalidChars != null) {
addFailure(validationResult, map, "DataMap '%s' Java package '%s' contains invalid characters: %s",
map.getName(), javaPackage, invalidChars);
}
Expand Down
Loading

0 comments on commit 24ffdfd

Please sign in to comment.