mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-04 20:45:52 +01:00
CLI Support for repository actions (#1987)
To make SCM-Manager more accessible and to make it easier using scripts against the server, we created a command line interface. This command line interface can be used to perform the default actions like create, modify and delete repositories. It is also very flexible and can be extended by plugins. The CLI already supports internationalization, help texts, input validation, loose and table-like templates and nested subcommands. Check the cli guidelines to learn how add new cli commands. Co-authored-by: Sebastian Sdorra <sebastian.sdorra@cloudogu.com>
This commit is contained in:
@@ -65,6 +65,9 @@ dependencies {
|
||||
api libraries.jaxRs
|
||||
testImplementation libraries.resteasyCore
|
||||
|
||||
// cli
|
||||
api libraries.picocli
|
||||
|
||||
// json
|
||||
api libraries.jacksonCore
|
||||
api libraries.jacksonAnnotations
|
||||
@@ -109,6 +112,7 @@ dependencies {
|
||||
testImplementation libraries.junitJupiterApi
|
||||
testImplementation libraries.junitJupiterParams
|
||||
testRuntimeOnly libraries.junitJupiterEngine
|
||||
testImplementation libraries.junitPioneer
|
||||
|
||||
// shiro
|
||||
testImplementation libraries.shiroExtension
|
||||
|
||||
@@ -32,6 +32,7 @@ commons-beanutils:commons-beanutils:1.9.4=annotationProcessor,annotationProcesso
|
||||
commons-collections:commons-collections:3.2.2=annotationProcessor,annotationProcessorCopy,compileClasspath,compileClasspathCopy,default,defaultCopy,runtimeClasspath,runtimeClasspathCopy,testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy
|
||||
commons-lang:commons-lang:2.6=compileClasspath,compileClasspathCopy,default,defaultCopy,runtimeClasspath,runtimeClasspathCopy,testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy
|
||||
de.otto.edison:edison-hal:2.1.0=compileClasspath,compileClasspathCopy,default,defaultCopy,runtimeClasspath,runtimeClasspathCopy,testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy
|
||||
info.picocli:picocli:4.6.3=annotationProcessor,annotationProcessorCopy,compileClasspath,compileClasspathCopy,default,defaultCopy,runtimeClasspath,runtimeClasspathCopy,testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy
|
||||
io.micrometer:micrometer-core:1.6.4=compileClasspath,compileClasspathCopy,default,defaultCopy,runtimeClasspath,runtimeClasspathCopy,testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy
|
||||
io.smallrye.common:smallrye-common-annotation:1.6.0=testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy
|
||||
io.smallrye.common:smallrye-common-classloader:1.6.0=testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy
|
||||
@@ -85,13 +86,21 @@ org.jboss.resteasy:resteasy-core:4.7.5.Final=testCompileClasspath,testCompileCla
|
||||
org.jboss.spec.javax.annotation:jboss-annotations-api_1.3_spec:2.0.1.Final=testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy
|
||||
org.jboss.spec.javax.ws.rs:jboss-jaxrs-api_2.1_spec:2.0.1.Final=testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy
|
||||
org.jboss.spec.javax.xml.bind:jboss-jaxb-api_2.3_spec:2.0.0.Final=testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy
|
||||
org.junit.jupiter:junit-jupiter-api:5.7.0=testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy
|
||||
org.junit.jupiter:junit-jupiter-engine:5.7.0=testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy
|
||||
org.junit.jupiter:junit-jupiter-params:5.7.0=testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy
|
||||
org.junit.platform:junit-platform-commons:1.7.0=testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy
|
||||
org.junit.platform:junit-platform-engine:1.7.0=testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy
|
||||
org.junit.vintage:junit-vintage-engine:5.7.0=testRuntimeClasspath,testRuntimeClasspathCopy
|
||||
org.junit:junit-bom:5.7.0=testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy
|
||||
org.junit-pioneer:junit-pioneer:1.6.2=testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy
|
||||
org.junit.jupiter:junit-jupiter-api:5.7.0=testCompileClasspath,testCompileClasspathCopy
|
||||
org.junit.jupiter:junit-jupiter-api:5.7.2=testRuntimeClasspath,testRuntimeClasspathCopy
|
||||
org.junit.jupiter:junit-jupiter-engine:5.7.0=testCompileClasspath,testCompileClasspathCopy
|
||||
org.junit.jupiter:junit-jupiter-engine:5.7.2=testRuntimeClasspath,testRuntimeClasspathCopy
|
||||
org.junit.jupiter:junit-jupiter-params:5.7.0=testCompileClasspath,testCompileClasspathCopy
|
||||
org.junit.jupiter:junit-jupiter-params:5.7.2=testRuntimeClasspath,testRuntimeClasspathCopy
|
||||
org.junit.platform:junit-platform-commons:1.7.0=testCompileClasspath,testCompileClasspathCopy
|
||||
org.junit.platform:junit-platform-commons:1.7.2=testRuntimeClasspath,testRuntimeClasspathCopy
|
||||
org.junit.platform:junit-platform-engine:1.7.0=testCompileClasspath,testCompileClasspathCopy
|
||||
org.junit.platform:junit-platform-engine:1.7.2=testRuntimeClasspath,testRuntimeClasspathCopy
|
||||
org.junit.platform:junit-platform-launcher:1.7.2=testRuntimeClasspath,testRuntimeClasspathCopy
|
||||
org.junit.vintage:junit-vintage-engine:5.7.2=testRuntimeClasspath,testRuntimeClasspathCopy
|
||||
org.junit:junit-bom:5.7.0=testCompileClasspath,testCompileClasspathCopy
|
||||
org.junit:junit-bom:5.7.2=testRuntimeClasspath,testRuntimeClasspathCopy
|
||||
org.latencyutils:LatencyUtils:2.0.3=default,defaultCopy,runtimeClasspath,runtimeClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy
|
||||
org.mapstruct:mapstruct-jdk8:1.3.1.Final=annotationProcessor,annotationProcessorCopy,compileClasspath,compileClasspathCopy,default,defaultCopy,runtimeClasspath,runtimeClasspathCopy,testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy
|
||||
org.mapstruct:mapstruct-processor:1.3.1.Final=annotationProcessor,annotationProcessorCopy
|
||||
|
||||
68
scm-core/src/main/java/sonia/scm/cli/CliContext.java
Normal file
68
scm-core/src/main/java/sonia/scm/cli/CliContext.java
Normal file
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.cli;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Context for the CLI client which is used by the CLI commands
|
||||
* @since 2.33.0
|
||||
*/
|
||||
public interface CliContext {
|
||||
/**
|
||||
* This is the {@link PrintWriter} which writes to the stdout channel of the client terminal.
|
||||
* Use this channel for "normal" messages, for errors use {@link CliContext#getStderr()}.
|
||||
* @return writer for stdout
|
||||
*/
|
||||
PrintWriter getStdout();
|
||||
|
||||
/**
|
||||
* This is the {@link PrintWriter} which writes to the stderr channel of the client terminal.
|
||||
* Use this channel for error messages, for "normal" messages use {@link CliContext#getStdout()}.
|
||||
* @return writer for stderr
|
||||
*/
|
||||
PrintWriter getStderr();
|
||||
|
||||
/**
|
||||
* Returns an {@link InputStream} which represents the stdin of the client terminal.
|
||||
* @return the stdin channel of the client terminal
|
||||
*/
|
||||
InputStream getStdin();
|
||||
|
||||
/**
|
||||
* Sets the exit code for the current command execution and stops the execution.
|
||||
* @param exitcode exit code which will be return to the client terminal
|
||||
* @see {@link ExitCode}
|
||||
*/
|
||||
void exit(int exitcode);
|
||||
|
||||
/**
|
||||
* Returns the {@link Locale} of the client terminal.
|
||||
* @return locale of the client terminal
|
||||
*/
|
||||
Locale getLocale();
|
||||
}
|
||||
41
scm-core/src/main/java/sonia/scm/cli/CliException.java
Normal file
41
scm-core/src/main/java/sonia/scm/cli/CliException.java
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.cli;
|
||||
|
||||
/**
|
||||
* Parent class for command line exceptions.
|
||||
* @since 2.33.0
|
||||
*/
|
||||
public class CliException extends RuntimeException {
|
||||
|
||||
public CliException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public CliException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
}
|
||||
117
scm-core/src/main/java/sonia/scm/cli/CommandValidator.java
Normal file
117
scm-core/src/main/java/sonia/scm/cli/CommandValidator.java
Normal file
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.cli;
|
||||
|
||||
import picocli.CommandLine;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.validation.ConstraintValidatorFactory;
|
||||
import javax.validation.ConstraintViolation;
|
||||
import javax.validation.MessageInterpolator;
|
||||
import javax.validation.Validation;
|
||||
import javax.validation.Validator;
|
||||
import javax.validation.ValidatorFactory;
|
||||
import java.util.Locale;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* This the command validator which should be used to validate CLI commands with Bean validation.
|
||||
* @see <a href="https://beanvalidation.org/2.0/spec/">Bean validation spec</a>
|
||||
* @since 2.33.0
|
||||
*/
|
||||
// We need to hide this because it is not a real command but a mixin.
|
||||
// The command annotation is required for picocli to resolve this properly.
|
||||
public final class CommandValidator {
|
||||
|
||||
private final CliContext context;
|
||||
private final Validator validator;
|
||||
|
||||
@CommandLine.Spec(CommandLine.Spec.Target.MIXEE)
|
||||
private CommandLine.Model.CommandSpec spec;
|
||||
|
||||
// We need an option to trick PicoCli into accepting our mixin
|
||||
@CommandLine.Option(names = "--hidden-flag", hidden = true)
|
||||
private boolean hiddenFlag ;
|
||||
|
||||
@Inject
|
||||
public CommandValidator(CliContext context, ConstraintValidatorFactory constraintValidatorFactory) {
|
||||
this.context = context;
|
||||
this.validator = createValidator(constraintValidatorFactory);
|
||||
}
|
||||
|
||||
private Validator createValidator(ConstraintValidatorFactory constraintValidatorFactory) {
|
||||
ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
|
||||
|
||||
return validatorFactory.usingContext()
|
||||
.constraintValidatorFactory(constraintValidatorFactory)
|
||||
.messageInterpolator(new LocaleSpecificMessageInterpolator(validatorFactory.getMessageInterpolator(), context.getLocale()))
|
||||
.getValidator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute validation and exit the command on validation failure
|
||||
*/
|
||||
public void validate() {
|
||||
Set<ConstraintViolation<Object>> violations = validator.validate(spec.userObject());
|
||||
|
||||
if (!violations.isEmpty()) {
|
||||
StringBuilder errorMsg = new StringBuilder();
|
||||
for (ConstraintViolation<Object> violation : violations) {
|
||||
errorMsg.append(evaluateErrorTemplate()).append(violation.getMessage()).append("\n");
|
||||
}
|
||||
throw new CommandLine.ParameterException(spec.commandLine(), errorMsg.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private String evaluateErrorTemplate() {
|
||||
ResourceBundle bundle = spec.resourceBundle();
|
||||
if (bundle != null && bundle.containsKey("errorLabel")) {
|
||||
return bundle.getString("errorLabel") + ": ";
|
||||
}
|
||||
return "ERROR: ";
|
||||
}
|
||||
|
||||
private static class LocaleSpecificMessageInterpolator implements MessageInterpolator {
|
||||
|
||||
private final MessageInterpolator defaultMessageInterpolator;
|
||||
private final Locale defaultLocale;
|
||||
|
||||
private LocaleSpecificMessageInterpolator(MessageInterpolator defaultMessageInterpolator, Locale defaultLocale) {
|
||||
this.defaultMessageInterpolator = defaultMessageInterpolator;
|
||||
this.defaultLocale = defaultLocale;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String interpolate(String messageTemplate, Context context) {
|
||||
return interpolate(messageTemplate, context, defaultLocale);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String interpolate(String messageTemplate, Context context, Locale locale) {
|
||||
return defaultMessageInterpolator.interpolate(messageTemplate, context, locale);
|
||||
}
|
||||
}
|
||||
}
|
||||
39
scm-core/src/main/java/sonia/scm/cli/ExitCode.java
Normal file
39
scm-core/src/main/java/sonia/scm/cli/ExitCode.java
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.cli;
|
||||
|
||||
/**
|
||||
* @see picocli.CommandLine.ExitCode
|
||||
* @since 2.33.0
|
||||
*/
|
||||
public final class ExitCode {
|
||||
|
||||
public static final int OK = 0;
|
||||
public static final int SERVER_ERROR = 1;
|
||||
public static final int USAGE = 2;
|
||||
public static final int NOT_FOUND = 3;
|
||||
|
||||
private ExitCode() {}
|
||||
}
|
||||
175
scm-core/src/main/java/sonia/scm/cli/Table.java
Normal file
175
scm-core/src/main/java/sonia/scm/cli/Table.java
Normal file
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.cli;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import lombok.Value;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
/**
|
||||
* This table can be used to display table-like command output
|
||||
* @since 2.33.0
|
||||
*/
|
||||
public final class Table implements Iterable<Table.Row> {
|
||||
|
||||
private static final String DEFAULT_LABEL_VALUE_SEPARATOR = ": ";
|
||||
|
||||
private final List<String[]> data = new ArrayList<>();
|
||||
|
||||
@Nullable
|
||||
private final ResourceBundle bundle;
|
||||
|
||||
Table(@Nullable ResourceBundle bundle) {
|
||||
this.bundle = bundle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the table headers.
|
||||
* You can use resource keys which will be translated using the related resource bundle.
|
||||
* @param keys actual names or resource keys for your table header
|
||||
*/
|
||||
public void addHeader(String... keys) {
|
||||
data.add(Arrays.stream(keys).map(this::getLocalizedValue).toArray(String[]::new));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a single row of values to the table.
|
||||
* @param row values for a single table row
|
||||
*/
|
||||
public void addRow(String... row) {
|
||||
data.add(row);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a table entry with two columns separated by {@link #DEFAULT_LABEL_VALUE_SEPARATOR}.
|
||||
* @param label label for the left table column
|
||||
* @param value value for the right table column
|
||||
*/
|
||||
public void addLabelValueRow(String label, String value) {
|
||||
addLabelValueRow(label, value, DEFAULT_LABEL_VALUE_SEPARATOR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a table entry with two columns separated by the given separator.
|
||||
* @param label label for the left table column
|
||||
* @param value value for the right table column
|
||||
* @param separator separator used to separate the label from the value
|
||||
*/
|
||||
public void addLabelValueRow(String label, String value, String separator) {
|
||||
addRow(getLocalizedValue(label) + separator, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of the table rows.
|
||||
* This is required for the internal table implementation.
|
||||
* @return a list of the table rows
|
||||
*/
|
||||
public List<Row> getRows() {
|
||||
Map<Integer, Integer> maxLength = calculateMaxLength();
|
||||
|
||||
List<Row> rows = new ArrayList<>();
|
||||
for (int r = 0; r < data.size(); r++) {
|
||||
String[] rowArray = data.get(r);
|
||||
|
||||
List<Cell> cells = new ArrayList<>();
|
||||
Row row = new Row(r == 0, r + 1 == data.size(), r, cells);
|
||||
for (int c = 0; c < rowArray.length; c++) {
|
||||
String value = createValueWithLength(Strings.nullToEmpty(rowArray[c]), maxLength.get(c));
|
||||
Cell cell = new Cell(row, c == 0, c + 1 == rowArray.length, c, value);
|
||||
cells.add(cell);
|
||||
}
|
||||
rows.add(row);
|
||||
}
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
private String getLocalizedValue(String key) {
|
||||
if (bundle != null && bundle.containsKey(key)) {
|
||||
return bundle.getString(key);
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
private String createValueWithLength(String value, int length) {
|
||||
if (value.length() < length) {
|
||||
StringBuilder builder = new StringBuilder(value);
|
||||
for (int j = value.length(); j < length; j++) {
|
||||
builder.append(" ");
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private Map<Integer, Integer> calculateMaxLength() {
|
||||
Map<Integer, Integer> maxLength = new HashMap<>();
|
||||
for (String[] row : data) {
|
||||
for (int i = 0; i < row.length; i++) {
|
||||
int currentMaxLength = maxLength.getOrDefault(i, 0);
|
||||
String col = row[i];
|
||||
int length = col != null ? col.length() : 0;
|
||||
if (length > currentMaxLength) {
|
||||
maxLength.put(i, length);
|
||||
}
|
||||
}
|
||||
}
|
||||
return maxLength;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Row> iterator() {
|
||||
return getRows().iterator();
|
||||
}
|
||||
|
||||
@Value
|
||||
public class Cell {
|
||||
|
||||
Row row;
|
||||
boolean first;
|
||||
boolean last;
|
||||
int index;
|
||||
String value;
|
||||
|
||||
}
|
||||
|
||||
@Value
|
||||
public class Row {
|
||||
|
||||
boolean first;
|
||||
boolean last;
|
||||
int index;
|
||||
List<Cell> cols;
|
||||
|
||||
}
|
||||
}
|
||||
166
scm-core/src/main/java/sonia/scm/cli/TemplateRenderer.java
Normal file
166
scm-core/src/main/java/sonia/scm/cli/TemplateRenderer.java
Normal file
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.cli;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.MoreObjects;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import picocli.CommandLine;
|
||||
import sonia.scm.template.Template;
|
||||
import sonia.scm.template.TemplateEngine;
|
||||
import sonia.scm.template.TemplateEngineFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringReader;
|
||||
import java.util.AbstractMap;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.Set;
|
||||
import java.util.function.UnaryOperator;
|
||||
|
||||
/**
|
||||
* This is the default template renderer which should be used to write templated content to the channels of the CLI connection.
|
||||
* @since 2.33.0
|
||||
*/
|
||||
public class TemplateRenderer {
|
||||
|
||||
@CommandLine.Option(names = {"--template", "-t"}, paramLabel = "TEMPLATE", descriptionKey = "scm.templateRenderer.template")
|
||||
private String template;
|
||||
|
||||
private static final String DEFAULT_ERROR_TEMPLATE = String.join("\n",
|
||||
"{{i18n.errorCommandFailed}}", "{{i18n.errorUnknownError}}:",
|
||||
"{{error}}"
|
||||
);
|
||||
|
||||
private final CliContext context;
|
||||
private final TemplateEngine templateEngine;
|
||||
@CommandLine.Spec(CommandLine.Spec.Target.MIXEE)
|
||||
private CommandLine.Model.CommandSpec spec;
|
||||
|
||||
@Inject
|
||||
public TemplateRenderer(CliContext context, TemplateEngineFactory templateEngineFactory) {
|
||||
this.context = context;
|
||||
this.templateEngine = templateEngineFactory.getDefaultEngine();
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes templated content to the stdout channel
|
||||
* @param template the mustache template
|
||||
* @param model the model which should be used for templating
|
||||
*/
|
||||
public void renderToStdout(String template, Map<String, Object> model) {
|
||||
exec(context.getStdout(), template, model);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes templated content to the stderr channel
|
||||
* @param template the mustache template
|
||||
* @param model the model which should be used for templating
|
||||
*/
|
||||
public void renderToStderr(String template, Map<String, Object> model) {
|
||||
exec(context.getStderr(), template, model);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes an error to the stderr channel using the default error template
|
||||
* @param error the error which should be used for templating
|
||||
*/
|
||||
public void renderDefaultError(String error) {
|
||||
exec(context.getStderr(), DEFAULT_ERROR_TEMPLATE, ImmutableMap.of("error", error));
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the exception message to the stderr channel using the default error template
|
||||
* @param exception the exception which should be used for templating
|
||||
*/
|
||||
public void renderDefaultError(Exception exception) {
|
||||
renderDefaultError(exception.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the table which should be used to template table-like content.
|
||||
* @return table for templating content
|
||||
*/
|
||||
public Table createTable() {
|
||||
return new Table(spec.resourceBundle());
|
||||
}
|
||||
|
||||
private void exec(PrintWriter stream, String defaultTemplate, Map<String, Object> model) {
|
||||
try {
|
||||
Template tpl = templateEngine.getTemplate(getClass().getName(), new StringReader(MoreObjects.firstNonNull(template, defaultTemplate)));
|
||||
tpl.execute(stream, createModel(model));
|
||||
stream.flush();
|
||||
} catch (IOException e) {
|
||||
throw new TemplateRenderingException("failed to render template", e);
|
||||
}
|
||||
}
|
||||
|
||||
private Object createModel(Map<String, Object> model) {
|
||||
Map<String, Object> finalModel = new HashMap<>(model);
|
||||
finalModel.put("lf", "\n");
|
||||
UnaryOperator<String> upper = value -> value.toUpperCase(context.getLocale());
|
||||
finalModel.put("upper", upper);
|
||||
|
||||
ResourceBundle resourceBundle = spec.resourceBundle();
|
||||
if (resourceBundle != null) {
|
||||
finalModel.put("i18n", new I18n(resourceBundle));
|
||||
}
|
||||
return Collections.unmodifiableMap(finalModel);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void setSpec(CommandLine.Model.CommandSpec spec) {
|
||||
this.spec = spec;
|
||||
}
|
||||
|
||||
@SuppressWarnings("java:S2160") // Do not need equals or hashcode
|
||||
private static class I18n extends AbstractMap<String, String> {
|
||||
|
||||
private final ResourceBundle resourceBundle;
|
||||
|
||||
I18n(ResourceBundle resourceBundle) {
|
||||
this.resourceBundle = resourceBundle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String get(Object key) {
|
||||
return resourceBundle.getString(key.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsKey(Object key) {
|
||||
return resourceBundle.containsKey(key.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Entry<String, String>> entrySet() {
|
||||
throw new UnsupportedOperationException("Should not be used");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.cli;
|
||||
|
||||
/**
|
||||
* Exception is thrown if {@link TemplateRenderer} could not render the template.
|
||||
* @since 2.33.0
|
||||
*/
|
||||
public class TemplateRenderingException extends CliException {
|
||||
|
||||
public TemplateRenderingException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.plugin;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.ToString;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import java.util.HashSet;
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
@NoArgsConstructor(access = AccessLevel.PACKAGE)
|
||||
public class NamedClassElement extends ClassElement {
|
||||
private String name;
|
||||
|
||||
public NamedClassElement(String name, String clazz) {
|
||||
super(clazz, null, new HashSet<>());
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
@@ -58,6 +58,9 @@ public class ScmModule {
|
||||
@XmlElement(name = "rest-resource")
|
||||
private Set<ClassElement> restResources;
|
||||
|
||||
@XmlElement(name = "cli-command")
|
||||
private Set<NamedClassElement> cliCommands;
|
||||
|
||||
@XmlElement(name = "mapper")
|
||||
private Set<ClassElement> mappers;
|
||||
|
||||
@@ -87,6 +90,10 @@ public class ScmModule {
|
||||
return nonNull(restResources);
|
||||
}
|
||||
|
||||
public Iterable<NamedClassElement> getCliCommands() {
|
||||
return nonNull(cliCommands);
|
||||
}
|
||||
|
||||
public Iterable<ClassElement> getMappers() {
|
||||
return nonNull(mappers);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.repository;
|
||||
|
||||
import javax.validation.Constraint;
|
||||
import javax.validation.Payload;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Validates the name of a repository.
|
||||
* @since 2.33.0
|
||||
*/
|
||||
@Documented
|
||||
@Target(ElementType.FIELD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Constraint(validatedBy = RepositoryNameConstrainValidator.class)
|
||||
public @interface RepositoryName {
|
||||
|
||||
String message() default "{sonia.scm.repository.RepositoryName.message}";
|
||||
Class<?>[] groups() default { };
|
||||
Class<? extends Payload>[] payload() default { };
|
||||
|
||||
/**
|
||||
* Specify namespace prefix validation. Default is {@link Namespace#NONE}.
|
||||
*
|
||||
* @return namespace validation
|
||||
*/
|
||||
Namespace namespace() default Namespace.NONE;
|
||||
|
||||
/**
|
||||
* Options to control the namespace prefix validation.
|
||||
*/
|
||||
enum Namespace {
|
||||
/**
|
||||
* The repository name does not contain a namespace prefix.
|
||||
*/
|
||||
NONE,
|
||||
|
||||
/**
|
||||
* The repository name can contain a namespace prefix.
|
||||
*/
|
||||
OPTIONAL,
|
||||
|
||||
/**
|
||||
* The repository name must start with a namespace prefix.
|
||||
*/
|
||||
REQUIRED;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.repository;
|
||||
|
||||
import sonia.scm.util.ValidationUtil;
|
||||
|
||||
import javax.validation.ConstraintValidator;
|
||||
import javax.validation.ConstraintValidatorContext;
|
||||
|
||||
public class RepositoryNameConstrainValidator implements ConstraintValidator<RepositoryName, String> {
|
||||
|
||||
private RepositoryName.Namespace namespace;
|
||||
|
||||
@Override
|
||||
public void initialize(RepositoryName constraintAnnotation) {
|
||||
namespace = constraintAnnotation.namespace();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid(String value, ConstraintValidatorContext context) {
|
||||
String[] parts = value.split("/");
|
||||
if (namespace == RepositoryName.Namespace.REQUIRED) {
|
||||
if (parts.length == 2) {
|
||||
return ValidationUtil.isRepositoryNameValid(parts[1]);
|
||||
}
|
||||
return false;
|
||||
} else if (namespace == RepositoryName.Namespace.OPTIONAL) {
|
||||
if (parts.length == 2) {
|
||||
return ValidationUtil.isRepositoryNameValid(parts[1]);
|
||||
} else if (parts.length == 1) {
|
||||
return ValidationUtil.isRepositoryNameValid(parts[0]);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return ValidationUtil.isRepositoryNameValid(value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.repository;
|
||||
|
||||
import javax.validation.Constraint;
|
||||
import javax.validation.Payload;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Validates the type of repository. Only configured and enabled repository types are valid.
|
||||
*
|
||||
* @since 2.33.0
|
||||
*/
|
||||
@Documented
|
||||
@Target(ElementType.FIELD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Constraint(validatedBy = RepositoryTypeConstraintValidator.class)
|
||||
public @interface RepositoryTypeConstraint {
|
||||
String message() default "{sonia.scm.repository.RepositoryTypeConstraint.message}";
|
||||
Class<?>[] groups() default { };
|
||||
Class<? extends Payload>[] payload() default { };
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.repository;
|
||||
|
||||
import sonia.scm.Type;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.validation.ConstraintValidator;
|
||||
import javax.validation.ConstraintValidatorContext;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Validator for {@link RepositoryTypeConstraint}.
|
||||
*
|
||||
* @since 2.33.0
|
||||
*/
|
||||
public class RepositoryTypeConstraintValidator implements ConstraintValidator<RepositoryTypeConstraint, String> {
|
||||
|
||||
private final RepositoryManager repositoryManager;
|
||||
|
||||
@Inject
|
||||
public RepositoryTypeConstraintValidator(RepositoryManager repositoryManager) {
|
||||
this.repositoryManager = repositoryManager;
|
||||
}
|
||||
|
||||
public RepositoryManager getRepositoryManager() {
|
||||
return repositoryManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid(String type, ConstraintValidatorContext context) {
|
||||
if (!isSupportedType(type)) {
|
||||
context.disableDefaultConstraintViolation();
|
||||
context.buildConstraintViolationWithTemplate(createMessage(context)).addConstraintViolation();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isSupportedType(String type) {
|
||||
return repositoryManager.getConfiguredTypes()
|
||||
.stream().anyMatch(t -> t.getName().equalsIgnoreCase(type));
|
||||
}
|
||||
|
||||
private String createMessage(ConstraintValidatorContext context) {
|
||||
String message = context.getDefaultConstraintMessageTemplate();
|
||||
return message + " " + commaSeparatedTypes();
|
||||
}
|
||||
|
||||
private String commaSeparatedTypes() {
|
||||
return repositoryManager.getConfiguredTypes()
|
||||
.stream()
|
||||
.map(Type::getName)
|
||||
.collect(Collectors.joining(", "));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.repository.cli;
|
||||
|
||||
import picocli.CommandLine;
|
||||
|
||||
@CommandLine.Command(name = "repo")
|
||||
public class RepositoryCommand {}
|
||||
26
scm-core/src/main/resources/ValidationMessages.properties
Normal file
26
scm-core/src/main/resources/ValidationMessages.properties
Normal file
@@ -0,0 +1,26 @@
|
||||
#
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in all
|
||||
# copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
#
|
||||
|
||||
sonia.scm.repository.RepositoryTypeConstraint.message = Invalid repository type, please use one of the following:
|
||||
sonia.scm.repository.RepositoryName.message = Invalid repository name
|
||||
26
scm-core/src/main/resources/ValidationMessages_de.properties
Normal file
26
scm-core/src/main/resources/ValidationMessages_de.properties
Normal file
@@ -0,0 +1,26 @@
|
||||
#
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in all
|
||||
# copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
#
|
||||
|
||||
sonia.scm.repository.RepositoryTypeConstraint.message = Ung<EFBFBD>ltiger Repository-Typ, bitte verwenden Sie einen der folgenden:
|
||||
sonia.scm.repository.RepositoryName.message = Ung<EFBFBD>ltiger Repository Name
|
||||
126
scm-core/src/test/java/sonia/scm/cli/CommandValidatorTest.java
Normal file
126
scm-core/src/test/java/sonia/scm/cli/CommandValidatorTest.java
Normal file
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.cli;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import picocli.CommandLine;
|
||||
|
||||
import javax.validation.ConstraintValidator;
|
||||
import javax.validation.ConstraintValidatorFactory;
|
||||
import javax.validation.constraints.Email;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.util.Locale;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class CommandValidatorTest {
|
||||
|
||||
@Mock
|
||||
private CliContext context;
|
||||
|
||||
@Test
|
||||
void shouldValidateCommand() {
|
||||
ResourceBundle resourceBundle = ResourceBundle.getBundle("sonia.scm.cli.i18n", Locale.ENGLISH);
|
||||
when(context.getLocale()).thenReturn(Locale.ENGLISH);
|
||||
CommandLine commandLine = new CommandLine(Command.class, new TestingCommandFactory());
|
||||
commandLine.setResourceBundle(resourceBundle);
|
||||
StringWriter stringWriter = new StringWriter();
|
||||
commandLine.setErr(new PrintWriter(stringWriter));
|
||||
|
||||
commandLine.execute("--mail=test");
|
||||
|
||||
assertThat(stringWriter.toString()).contains("ERROR: must be a well-formed email address");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldValidateCommandWithGermanLocale() {
|
||||
ResourceBundle resourceBundle = ResourceBundle.getBundle("sonia.scm.cli.i18n", Locale.GERMAN);
|
||||
when(context.getLocale()).thenReturn(Locale.GERMAN);
|
||||
CommandLine commandLine = new CommandLine(Command.class, new TestingCommandFactory());
|
||||
commandLine.setResourceBundle(resourceBundle);
|
||||
StringWriter stringWriter = new StringWriter();
|
||||
commandLine.setErr(new PrintWriter(stringWriter));
|
||||
|
||||
commandLine.execute("--mail=test");
|
||||
|
||||
assertThat(stringWriter.toString()).contains("FEHLER: muss eine korrekt formatierte E-Mail-Adresse sein");
|
||||
}
|
||||
|
||||
@CommandLine.Command
|
||||
public static class Command implements Runnable {
|
||||
|
||||
@CommandLine.Mixin
|
||||
private CommandValidator commandValidator;
|
||||
|
||||
@Email
|
||||
@CommandLine.Option(names = "--mail")
|
||||
private String mail;
|
||||
|
||||
public Command(CliContext context) {
|
||||
commandValidator = new CommandValidator(context, new TestingConstraintValidatorFactory());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
commandValidator.validate();
|
||||
}
|
||||
}
|
||||
|
||||
class TestingCommandFactory implements CommandLine.IFactory {
|
||||
|
||||
@Override
|
||||
public <K> K create(Class<K> cls) throws Exception {
|
||||
try {
|
||||
return cls.getConstructor(CliContext.class).newInstance(context);
|
||||
} catch (Exception e) {
|
||||
return CommandLine.defaultFactory().create(cls);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class TestingConstraintValidatorFactory implements ConstraintValidatorFactory {
|
||||
|
||||
@Override
|
||||
public <T extends ConstraintValidator<?, ?>> T getInstance(Class<T> key) {
|
||||
try {
|
||||
return key.getConstructor().newInstance();
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Failed to create constraint validator");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void releaseInstance(ConstraintValidator<?, ?> instance) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
109
scm-core/src/test/java/sonia/scm/cli/TemplateRendererTest.java
Normal file
109
scm-core/src/test/java/sonia/scm/cli/TemplateRendererTest.java
Normal file
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.cli;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import picocli.CommandLine;
|
||||
import sonia.scm.template.Template;
|
||||
import sonia.scm.template.TemplateEngine;
|
||||
import sonia.scm.template.TemplateEngineFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringReader;
|
||||
import java.io.StringWriter;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class TemplateRendererTest {
|
||||
|
||||
@Mock
|
||||
private CliContext context;
|
||||
@Mock
|
||||
private TemplateEngineFactory templateEngineFactory;
|
||||
@Mock
|
||||
private TemplateEngine engine;
|
||||
@Mock
|
||||
private Template template;
|
||||
|
||||
@Test
|
||||
void shouldTemplateContentToStdout() throws IOException {
|
||||
when(context.getStdout()).thenReturn(new PrintWriter(new StringWriter()));
|
||||
when(templateEngineFactory.getDefaultEngine()).thenReturn(engine);
|
||||
when(engine.getTemplate(any(), any(StringReader.class))).thenReturn(template);
|
||||
TemplateRenderer templateRenderer = new TemplateRenderer(context, templateEngineFactory);
|
||||
templateRenderer.setSpec(CommandLine.Model.CommandSpec.create());
|
||||
|
||||
templateRenderer.renderToStdout(":{{test}}!", ImmutableMap.of("test", "test_output"));
|
||||
|
||||
verify(template).execute(any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRenderErrorToStderr() throws IOException {
|
||||
when(context.getStderr()).thenReturn(new PrintWriter(new StringWriter()));
|
||||
when(templateEngineFactory.getDefaultEngine()).thenReturn(engine);
|
||||
when(engine.getTemplate(any(), any(StringReader.class))).thenReturn(template);
|
||||
TemplateRenderer templateRenderer = new TemplateRenderer(context, templateEngineFactory);
|
||||
templateRenderer.setSpec(CommandLine.Model.CommandSpec.create());
|
||||
|
||||
templateRenderer.renderToStderr(":{{error}}!", ImmutableMap.of("error", "testerror"));
|
||||
|
||||
verify(template).execute(any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRenderDefaultErrorToStderr() throws IOException {
|
||||
when(context.getStderr()).thenReturn(new PrintWriter(new StringWriter()));
|
||||
when(templateEngineFactory.getDefaultEngine()).thenReturn(engine);
|
||||
when(engine.getTemplate(any(), any(StringReader.class))).thenReturn(template);
|
||||
TemplateRenderer templateRenderer = new TemplateRenderer(context, templateEngineFactory);
|
||||
templateRenderer.setSpec(CommandLine.Model.CommandSpec.create());
|
||||
|
||||
templateRenderer.renderDefaultError("testerror");
|
||||
|
||||
verify(template).execute(any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRenderExceptionToStderr() throws IOException {
|
||||
when(context.getStderr()).thenReturn(new PrintWriter(new StringWriter()));
|
||||
when(templateEngineFactory.getDefaultEngine()).thenReturn(engine);
|
||||
when(engine.getTemplate(any(), any(StringReader.class))).thenReturn(template);
|
||||
TemplateRenderer templateRenderer = new TemplateRenderer(context, templateEngineFactory);
|
||||
templateRenderer.setSpec(CommandLine.Model.CommandSpec.create());
|
||||
|
||||
templateRenderer.renderDefaultError(new RuntimeException("test"));
|
||||
|
||||
verify(template).execute(any(), any());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.repository;
|
||||
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junitpioneer.jupiter.DefaultLocale;
|
||||
|
||||
import javax.validation.ConstraintViolation;
|
||||
import javax.validation.Validation;
|
||||
import javax.validation.Validator;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static sonia.scm.repository.RepositoryName.Namespace.OPTIONAL;
|
||||
import static sonia.scm.repository.RepositoryName.Namespace.REQUIRED;
|
||||
|
||||
class RepositoryNameConstrainValidatorTest {
|
||||
|
||||
@Nested
|
||||
class WithoutNamespace {
|
||||
|
||||
@Test
|
||||
void shouldPassValidation() {
|
||||
assertThat(validate(new NamespaceNone("scm-manager"))).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DefaultLocale("en")
|
||||
void shouldFailValidation() {
|
||||
assertThat(validate(new NamespaceNone("scm\\manager"))).hasSize(1).allSatisfy(
|
||||
violation -> assertThat(violation.getMessage()).isEqualTo("Invalid repository name")
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DefaultLocale("de")
|
||||
void shouldFailValidationWithGermanMessage() {
|
||||
assertThat(validate(new NamespaceNone("scm\\manager"))).hasSize(1).allSatisfy(
|
||||
violation -> assertThat(violation.getMessage()).isEqualTo("Ungültiger Repository Name")
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DefaultLocale("en")
|
||||
void shouldFailWithSlashAndDisabledWithNamespaceOption() {
|
||||
assertThat(validate(new NamespaceNone("scm/manager"))).isNotEmpty();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Nested
|
||||
class WithRequiredNamespace {
|
||||
|
||||
@Test
|
||||
void shouldPassValidation() {
|
||||
assertThat(validate(new RequiredNamespace("scm/manager"))).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFailWithMoreThanOneSlash() {
|
||||
assertThat(validate(new RequiredNamespace("scm/mana/ger"))).isNotEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFailWithOutNamespace() {
|
||||
assertThat(validate(new RequiredNamespace("scm-manager"))).isNotEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFailWithInvalidName() {
|
||||
assertThat(validate(new RequiredNamespace("scm/ma\\nager"))).isNotEmpty();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Nested
|
||||
class WithOptionalNamespace {
|
||||
|
||||
@Test
|
||||
void shouldPassValidationWithoutNamespace() {
|
||||
assertThat(validate(new OptionalNamespace("scm-manager"))).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldPassValidationWithNamespace() {
|
||||
assertThat(validate(new OptionalNamespace("scm/manager"))).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFailWithMoreThanOneSlash() {
|
||||
assertThat(validate(new OptionalNamespace("scm/mana/ger"))).isNotEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFailWithInvalidName() {
|
||||
assertThat(validate(new OptionalNamespace("scm/ma\\nager"))).isNotEmpty();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private <T> Set<ConstraintViolation<T>> validate(T object) {
|
||||
return validator().validate(object);
|
||||
}
|
||||
|
||||
private Validator validator() {
|
||||
return Validation.buildDefaultValidatorFactory().getValidator();
|
||||
}
|
||||
|
||||
public static class NamespaceNone {
|
||||
|
||||
@RepositoryName
|
||||
private final String name;
|
||||
|
||||
public NamespaceNone(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
public static class RequiredNamespace {
|
||||
|
||||
@RepositoryName(namespace = REQUIRED)
|
||||
private final String name;
|
||||
|
||||
public RequiredNamespace(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
public static class OptionalNamespace {
|
||||
|
||||
@RepositoryName(namespace = OPTIONAL)
|
||||
private final String name;
|
||||
|
||||
public OptionalNamespace(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.repository;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junitpioneer.jupiter.DefaultLocale;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import javax.validation.ConstraintValidator;
|
||||
import javax.validation.ConstraintValidatorFactory;
|
||||
import javax.validation.ConstraintViolation;
|
||||
import javax.validation.Validation;
|
||||
import javax.validation.Validator;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class RepositoryTypeConstraintValidatorTest {
|
||||
|
||||
@Mock
|
||||
private RepositoryManager repositoryManager;
|
||||
|
||||
@Test
|
||||
@DefaultLocale("en")
|
||||
void shouldFailValidation() {
|
||||
mockRepositoryTypes("git", "hg");
|
||||
|
||||
Validator validator = validator();
|
||||
Set<ConstraintViolation<Repo>> violations = validator.validate(new Repo("svn"));
|
||||
assertThat(violations).hasSize(1).allSatisfy(
|
||||
violation -> assertThat(violation.getMessage()).contains("Invalid repository type")
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DefaultLocale("de")
|
||||
void shouldFailValidationWithGermanMessage() {
|
||||
mockRepositoryTypes("svn", "hg");
|
||||
|
||||
Validator validator = validator();
|
||||
Set<ConstraintViolation<Repo>> violations = validator.validate(new Repo("git"));
|
||||
assertThat(violations).hasSize(1).allSatisfy(
|
||||
violation -> assertThat(violation.getMessage()).contains("Ungültiger Repository-Typ")
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DefaultLocale("en")
|
||||
void shouldPassValidation() {
|
||||
mockRepositoryTypes("svn", "git", "hg");
|
||||
|
||||
Validator validator = validator();
|
||||
Set<ConstraintViolation<Repo>> violations = validator.validate(new Repo("hg"));
|
||||
assertThat(violations).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DefaultLocale("en")
|
||||
void shouldAddAvailableTypesToMessage() {
|
||||
mockRepositoryTypes("git", "hg", "svn");
|
||||
|
||||
Validator validator = validator();
|
||||
Set<ConstraintViolation<Repo>> violations = validator.validate(new Repo("unknown"));
|
||||
assertThat(violations).hasSize(1).allSatisfy(
|
||||
violation -> assertThat(violation.getMessage()).contains("git, hg, svn")
|
||||
);
|
||||
}
|
||||
|
||||
private Validator validator() {
|
||||
return Validation.buildDefaultValidatorFactory()
|
||||
.usingContext()
|
||||
.constraintValidatorFactory(new TestingConstraintValidatorFactory(repositoryManager))
|
||||
.getValidator();
|
||||
}
|
||||
|
||||
private void mockRepositoryTypes(String... types) {
|
||||
List<RepositoryType> repositoryTypes = Arrays.stream(types)
|
||||
.map(t -> new RepositoryType(t, t.toUpperCase(Locale.ENGLISH), Collections.emptySet()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
when(repositoryManager.getConfiguredTypes()).thenReturn(repositoryTypes);
|
||||
}
|
||||
|
||||
public static class Repo {
|
||||
|
||||
@RepositoryTypeConstraint
|
||||
private final String type;
|
||||
|
||||
public Repo(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
}
|
||||
|
||||
public static class TestingConstraintValidatorFactory implements ConstraintValidatorFactory {
|
||||
|
||||
private final RepositoryManager repositoryManager;
|
||||
|
||||
public TestingConstraintValidatorFactory(RepositoryManager repositoryManager) {
|
||||
this.repositoryManager = repositoryManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends ConstraintValidator<?, ?>> T getInstance(Class<T> key) {
|
||||
try {
|
||||
return key.getConstructor(RepositoryManager.class).newInstance(repositoryManager);
|
||||
} catch (Exception ex) {
|
||||
try {
|
||||
return key.getConstructor().newInstance();
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Failed to create constraint", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void releaseInstance(ConstraintValidator<?, ?> instance) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
25
scm-core/src/test/resources/sonia/scm/cli/i18n.properties
Normal file
25
scm-core/src/test/resources/sonia/scm/cli/i18n.properties
Normal file
@@ -0,0 +1,25 @@
|
||||
#
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in all
|
||||
# copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
#
|
||||
|
||||
errorLabel= ERROR
|
||||
25
scm-core/src/test/resources/sonia/scm/cli/i18n_de.properties
Normal file
25
scm-core/src/test/resources/sonia/scm/cli/i18n_de.properties
Normal file
@@ -0,0 +1,25 @@
|
||||
#
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in all
|
||||
# copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
#
|
||||
|
||||
errorLabel= FEHLER
|
||||
Reference in New Issue
Block a user