fr.ird.observe.maven.plugins.toolbox.ValidatorsCache Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of toolbox-maven-plugin Show documentation
Show all versions of toolbox-maven-plugin Show documentation
ObServe Toolbox Maven plugin module
The newest version!
package fr.ird.observe.maven.plugins.toolbox;
/*-
* #%L
* ObServe Toolkit :: Maven plugin
* %%
* Copyright (C) 2017 - 2020 IRD, Ultreia.io
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program. If not, see
* .
* #L%
*/
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import fr.ird.observe.validation.ValidatorDto;
import io.ultreia.java4all.lang.Objects2;
import org.apache.maven.plugin.logging.Log;
import org.nuiton.validator.NuitonValidatorScope;
import org.nuiton.validator.bean.simple.SimpleBeanValidator;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Collection;
import java.util.EnumSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.TreeSet;
import java.util.stream.Collectors;
/**
* Created on 31/08/16.
*
* @author Tony Chemit - [email protected]
*/
public class ValidatorsCache {
private final static ValidatorsCache instance = new ValidatorsCache();
private final Multimap validators = HashMultimap.create();
public static ValidatorsCache get() {
return instance;
}
public synchronized Collection getValidators(ValidatorCacheRequest request) throws IOException {
Log log = request.getLog();
boolean verbose = request.isVerbose();
Path sourceRootPath = request.getSourceRootPath();
List result = new LinkedList<>();
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(request.getUrlClassLoader());
Map, Class>> extraClassMapping = request.getExtraClassMapping();
if (extraClassMapping != null) {
validators.clear();
}
Collection source = loadValidators(log, verbose, extraClassMapping, sourceRootPath);
log.info("Detects " + source.size() + " validator(s) from source directory.");
result.addAll(source);
if (request.getExtraSourceRootPath().isPresent()) {
Collection generated = loadValidators(log, verbose, extraClassMapping, request.getExtraSourceRootPath().get());
log.info("Detects " + generated.size() + " validator(s) from generated directory.");
Collection extends ValidatorInfo> merge = merge(source, generated, extraClassMapping);
log.info("Merge " + merge.size() + " validator(s).");
result.addAll(merge);
}
} finally {
Thread.currentThread().setContextClassLoader(contextClassLoader);
}
log.info("Found " + result.size() + " validator(s).");
return result;
}
private Collection loadValidators(Log log, boolean verbose, Map, Class>> extraClassMapping, Path sourceRootPath) {
String key = sourceRootPath.toFile().getAbsolutePath();
if (!validators.containsKey(key)) {
log.info("Loading validators from " + sourceRootPath);
PathSimpleFileVisitorResult result;
try {
result = new PathSimpleFileVisitor(log, sourceRootPath, verbose).walk();
} catch (IOException e) {
throw new RuntimeException("Can't walk through directory: " + sourceRootPath, e);
}
NuitonValidatorScope[] scopes = result.getEffectiveScopes();
for (ValidatorDescriptor validatorDescriptor : result.descritptors) {
String typeName = validatorDescriptor.getTypeName();
String context = validatorDescriptor.getContext();
Class> type = Objects2.forName(typeName);
if (extraClassMapping != null) {
Class> type2 = extraClassMapping.get(type);
if (type2 == null) {
log.warn("Can't find type for type: " + type.getName());
type2 = type;
}
type = type2;
}
SimpleBeanValidator> validator = SimpleBeanValidator.newValidator(type, context, scopes);
for (NuitonValidatorScope scope : validator.getEffectiveScopes()) {
Set effectiveFields = validator.getEffectiveFields(scope);
ValidatorInfo validatorInfo = new ValidatorInfo(type, context, scope, new LinkedHashSet<>(effectiveFields.stream().sorted().collect(Collectors.toList())));
validators.put(key, validatorInfo);
}
}
}
Collection validatorInfos = validators.get(key);
log.debug("Found " + validatorInfos.size() + " validator(s).");
return validatorInfos;
}
private Collection extends ValidatorInfo> merge(Collection validatorInfos, Collection generatedValidatorInfos, Map, Class>> extraClassMapping) {
List result = new LinkedList<>();
if (extraClassMapping == null) {
for (ValidatorInfo validatorInfo : validatorInfos) {
ValidatorInfo merge = merge(validatorInfo, generatedValidatorInfos);
result.add(merge);
}
return result;
}
ArrayListMultimap, ValidatorInfo> allInfos = ArrayListMultimap.create();
for (ValidatorInfo validatorInfo : validatorInfos) {
allInfos.put(validatorInfo.getType(), validatorInfo);
}
for (ValidatorInfo validatorInfo : generatedValidatorInfos) {
allInfos.put(validatorInfo.getType(), validatorInfo);
}
ArrayListMultimap infosByKey = ArrayListMultimap.create();
for (Map.Entry, Collection> entry : allInfos.asMap().entrySet()) {
String prefix = entry.getKey().getName();
entry.getValue().forEach(i -> infosByKey.put(prefix + Objects.requireNonNull(i).getScope() + i.getContext(), i));
}
for (Collection infos : infosByKey.asMap().values()) {
ValidatorInfo merge = merge(infos);
result.add(merge);
}
return result;
}
private ValidatorInfo merge(ValidatorInfo source, Collection generatedValidatorInfos) {
String context = source.getContext();
Class> type = source.getType();
NuitonValidatorScope scope = source.getScope();
Set fields = new TreeSet<>(source.getFields());
for (ValidatorInfo validatorInfo : generatedValidatorInfos.stream().filter(v -> scope.equals(v.getScope()) && v.getType().isAssignableFrom(type) && context.equals(v.getContext())).collect(Collectors.toList())) {
fields.addAll(validatorInfo.getFields());
}
return new ValidatorInfo(type, context, scope, fields);
}
private ValidatorInfo merge(Collection infos) {
ValidatorInfo source = infos.iterator().next();
String context = source.getContext();
Class> type = source.getType();
NuitonValidatorScope scope = source.getScope();
Set fields = new TreeSet<>(source.getFields());
for (ValidatorInfo validatorInfo : infos) {
fields.addAll(validatorInfo.getFields());
}
return new ValidatorInfo(type, context, scope, fields);
}
public static class ValidatorInfo {
private final Class> type;
private final String context;
private final NuitonValidatorScope scope;
private final Set fields;
ValidatorInfo(Class> type, String context, NuitonValidatorScope scope, Set fields) {
this.type = type;
this.context = context;
this.scope = scope;
this.fields = fields;
}
public String getOrderKey() {
return type.getName() + "-" + context + "-" + scope;
}
public Class> getType() {
return type;
}
public String getContext() {
return context;
}
public NuitonValidatorScope getScope() {
return scope;
}
public Set getFields() {
return fields;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ValidatorInfo that = (ValidatorInfo) o;
return Objects.equals(type, that.type) &&
Objects.equals(context, that.context) &&
scope == that.scope;
}
@Override
public int hashCode() {
return Objects.hash(type, context, scope);
}
@SuppressWarnings("unchecked")
public ValidatorDto toValidatorDto() {
return new ValidatorDto(
(Class) type,
scope,
context,
fields
);
}
}
private static class PathSimpleFileVisitorResult {
private final Set descritptors;
private PathSimpleFileVisitorResult(Set descritptors) {
this.descritptors = descritptors;
}
public NuitonValidatorScope[] getEffectiveScopes() {
EnumSet result = EnumSet.noneOf(NuitonValidatorScope.class);
result.addAll(descritptors.stream().map(ValidatorDescriptor::getScope).collect(Collectors.toList()));
return result.toArray(new NuitonValidatorScope[0]);
}
}
private static class ValidatorDescriptor {
private final String typeName;
private final String context;
private final NuitonValidatorScope scope;
private ValidatorDescriptor(String typeName, String context, NuitonValidatorScope scope) {
this.typeName = typeName;
this.context = context;
this.scope = scope;
}
public String getTypeName() {
return typeName;
}
public String getContext() {
return context;
}
public NuitonValidatorScope getScope() {
return scope;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ValidatorDescriptor that = (ValidatorDescriptor) o;
return Objects.equals(getTypeName(), that.getTypeName()) &&
Objects.equals(getContext(), that.getContext()) &&
getScope() == that.getScope();
}
@Override
public int hashCode() {
return Objects.hash(getTypeName(), getContext(), getScope());
}
}
private static class PathSimpleFileVisitor extends SimpleFileVisitor {
private final Set descritptors = new LinkedHashSet<>();
private final Set scopes = new LinkedHashSet<>();
private final Log log;
private final Path sourceRootPath;
private final boolean verbose;
private String packageName = "";
PathSimpleFileVisitor(Log log, Path sourceRootPath, boolean verbose) {
this.log = log;
this.sourceRootPath = sourceRootPath;
this.verbose = verbose;
for (NuitonValidatorScope scope : NuitonValidatorScope.values()) {
scopes.add(scope.name().toLowerCase());
}
}
public PathSimpleFileVisitorResult walk() throws IOException {
Files.walkFileTree(sourceRootPath, this);
log.info(String.format("Found %d validator context(s).", descritptors.size()));
return new PathSimpleFileVisitorResult(descritptors);
}
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
if (!dir.equals(sourceRootPath)) {
if (!packageName.isEmpty()) {
packageName += ".";
}
packageName += dir.toFile().getName();
}
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) {
if (!dir.equals(sourceRootPath)) {
String name = dir.toFile().getName();
packageName = packageName.substring(0, packageName.length() - name.length());
if (packageName.endsWith(".")) {
packageName = packageName.substring(0, packageName.length() - 1);
}
}
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
String name = file.toFile().getName();
if (name.endsWith("-validation.xml")) {
int i = name.indexOf('-');
String typeName = packageName + "." + name.substring(0, i);
String rest = name.substring(i);
LinkedHashSet contexts = new LinkedHashSet<>();
StringTokenizer tok = new StringTokenizer(rest, "-");
NuitonValidatorScope scope = null;
while (tok.hasMoreTokens()) {
String token = tok.nextToken();
if (scopes.contains(token)) {
scope = NuitonValidatorScope.valueOf(token.toUpperCase());
break;
}
contexts.add(token);
}
String context = String.join("-", contexts);
ValidatorDescriptor descritptor = new ValidatorDescriptor(typeName, context, scope);
boolean add = descritptors.add(descritptor);
if (add) {
if (verbose) {
log.info("Register " + typeName);
}
}
}
return FileVisitResult.CONTINUE;
}
}
}