ソースを参照

SONAR-5121 Refactor the way remediation function is defined on rules in order to remove the static creation and to be able to know which rule is failing when a wrong function is defined.

tags/4.3
Julien Lancelot 10年前
コミット
ba56f1b053
18個のファイルの変更485行の追加353行の削除
  1. 4
    0
      plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/XooRulesDefinition.java
  2. 6
    0
      plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/rule/XooRulesDefinitionTest.java
  3. 7
    100
      sonar-plugin-api/src/main/java/org/sonar/api/server/rule/DebtRemediationFunction.java
  4. 35
    0
      sonar-plugin-api/src/main/java/org/sonar/api/server/rule/DebtRemediationFunctions.java
  5. 128
    0
      sonar-plugin-api/src/main/java/org/sonar/api/server/rule/DefaultDebtRemediationFunction.java
  6. 59
    0
      sonar-plugin-api/src/main/java/org/sonar/api/server/rule/DefaultDebtRemediationFunctions.java
  7. 6
    0
      sonar-plugin-api/src/main/java/org/sonar/api/server/rule/RulesDefinition.java
  8. 1
    1
      sonar-plugin-api/src/test/java/org/sonar/api/batch/rule/DefaultDebtRemediationFunctionTest.java
  9. 0
    146
      sonar-plugin-api/src/test/java/org/sonar/api/server/rule/DebtRemediationFunctionTest.java
  10. 146
    0
      sonar-plugin-api/src/test/java/org/sonar/api/server/rule/DefaultDebtRemediationFunctionTest.java
  11. 3
    3
      sonar-plugin-api/src/test/java/org/sonar/api/server/rule/RulesDefinitionTest.java
  12. 31
    19
      sonar-server/src/main/java/org/sonar/server/debt/DebtRulesXMLImporter.java
  13. 13
    1
      sonar-server/src/main/java/org/sonar/server/rule/DeprecatedRulesDefinition.java
  14. 4
    4
      sonar-server/src/main/java/org/sonar/server/rule/RuleRegistration.java
  15. 28
    22
      sonar-server/src/test/java/org/sonar/server/debt/DebtRulesXMLImporterTest.java
  16. 7
    3
      sonar-server/src/test/java/org/sonar/server/rule/DeprecatedRulesDefinitionTest.java
  17. 7
    5
      sonar-server/src/test/java/org/sonar/server/rule/RuleRegistrationTest.java
  18. 0
    49
      sonar-server/src/test/resources/org/sonar/server/debt/DebtRulesXMLImporterTest/fail_to_import_linear_having_offset.xml

+ 4
- 0
plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/XooRulesDefinition.java ファイルの表示

@@ -51,6 +51,10 @@ public class XooRulesDefinition implements RulesDefinition {
// default severity when the rule is activated on a Quality profile. Default value is MAJOR.
.setSeverity(Severity.MINOR);

x1Rule
.setDebtCharacteristic("INTEGRATION_TESTABILITY")
.setDebtRemediationFunction(x1Rule.debtRemediationFunctions().linearWithOffset("1h", "30min"));

x1Rule.createParam("acceptWhitespace")
.setDefaultValue("false")
.setType(RuleParamType.BOOLEAN)

+ 6
- 0
plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/rule/XooRulesDefinitionTest.java ファイルの表示

@@ -20,6 +20,7 @@
package org.sonar.xoo.rule;

import org.junit.Test;
import org.sonar.api.server.rule.DebtRemediationFunction;
import org.sonar.api.server.rule.RulesDefinition;

import static org.fest.assertions.Assertions.assertThat;
@@ -42,5 +43,10 @@ public class XooRulesDefinitionTest {
assertThat(x1.key()).isEqualTo("x1");
assertThat(x1.tags()).containsOnly("style", "security");
assertThat(x1.htmlDescription()).isNotEmpty();

assertThat(x1.debtCharacteristic()).isEqualTo("INTEGRATION_TESTABILITY");
assertThat(x1.debtRemediationFunction().type()).isEqualTo(DebtRemediationFunction.Type.LINEAR_OFFSET);
assertThat(x1.debtRemediationFunction().factor()).isEqualTo("1h");
assertThat(x1.debtRemediationFunction().offset()).isEqualTo("30min");
}
}

+ 7
- 100
sonar-plugin-api/src/main/java/org/sonar/api/server/rule/DebtRemediationFunction.java ファイルの表示

@@ -20,122 +20,29 @@

package org.sonar.api.server.rule;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.builder.ReflectionToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;

import javax.annotation.CheckForNull;
import javax.annotation.Nullable;

/**
* @since 4.3
*/
public class DebtRemediationFunction {

public static enum Type {
LINEAR, LINEAR_OFFSET, CONSTANT_ISSUE
}
public interface DebtRemediationFunction {

public static class ValidationException extends RuntimeException {
static class ValidationException extends RuntimeException {
public ValidationException(String message) {
super(message);
}
}

private Type type;
private String factor;
private String offset;

private DebtRemediationFunction(Type type, @Nullable String factor, @Nullable String offset) {
this.type = type;
// TODO validate factor and offset format
this.factor = StringUtils.deleteWhitespace(factor);
this.offset = StringUtils.deleteWhitespace(offset);
validate();
}

public static DebtRemediationFunction create(Type type, @Nullable String factor, @Nullable String offset) {
return new DebtRemediationFunction(type, factor, offset);
}

public static DebtRemediationFunction createLinear(String factor) {
return new DebtRemediationFunction(Type.LINEAR, factor, null);
}

public static DebtRemediationFunction createLinearWithOffset(String factor, String offset) {
return new DebtRemediationFunction(Type.LINEAR_OFFSET, factor, offset);
}

public static DebtRemediationFunction createConstantPerIssue(String offset) {
return new DebtRemediationFunction(Type.CONSTANT_ISSUE, null, offset);
static enum Type {
LINEAR, LINEAR_OFFSET, CONSTANT_ISSUE
}

public Type type() {
return type;
}
Type type();

@CheckForNull
public String factor() {
return factor;
}
String factor();

@CheckForNull
public String offset() {
return offset;
}

private void validate(){
switch (type) {
case LINEAR:
if (this.factor == null || this.offset != null) {
throw new ValidationException(String.format("%s is invalid, Linear remediation function should only define a factor", this));
}
break;
case LINEAR_OFFSET:
if (this.factor == null || this.offset == null) {
throw new ValidationException(String.format("%s is invalid, Linear with offset remediation function should define both factor and offset", this));
}
break;
case CONSTANT_ISSUE:
if (this.factor != null || this.offset == null) {
throw new ValidationException(String.format("%s is invalid, Constant/issue remediation function should only define an offset", this));
}
break;
default:
throw new IllegalStateException(String.format("Remediation function of %s is unknown", this));
}
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}

DebtRemediationFunction that = (DebtRemediationFunction) o;
return new EqualsBuilder()
.append(type, that.type())
.append(factor, that.factor())
.append(offset, that.offset())
.isEquals();
}
String offset();

@Override
public int hashCode() {
return new HashCodeBuilder(15, 33)
.append(type)
.append(factor)
.append(offset)
.toHashCode();
}

@Override
public String toString() {
return new ReflectionToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE).toString();
}
}

+ 35
- 0
sonar-plugin-api/src/main/java/org/sonar/api/server/rule/DebtRemediationFunctions.java ファイルの表示

@@ -0,0 +1,35 @@
/*
* SonarQube, open source software quality management tool.
* Copyright (C) 2008-2014 SonarSource
* mailto:contact AT sonarsource DOT com
*
* SonarQube is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* SonarQube 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

package org.sonar.api.server.rule;

/**
* Factory of {@link org.sonar.api.server.rule.DebtRemediationFunction}
*
* @since 4.3
*/
public interface DebtRemediationFunctions {

DebtRemediationFunction linear(String factor);

DebtRemediationFunction linearWithOffset(String factor, String offset);

DebtRemediationFunction constantPerIssue(String offset);
}

+ 128
- 0
sonar-plugin-api/src/main/java/org/sonar/api/server/rule/DefaultDebtRemediationFunction.java ファイルの表示

@@ -0,0 +1,128 @@
/*
* SonarQube, open source software quality management tool.
* Copyright (C) 2008-2014 SonarSource
* mailto:contact AT sonarsource DOT com
*
* SonarQube is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* SonarQube 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

package org.sonar.api.server.rule;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.builder.ReflectionToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;

import javax.annotation.CheckForNull;
import javax.annotation.Nullable;

class DefaultDebtRemediationFunction implements DebtRemediationFunction {

private Type type;
private String factor;
private String offset;

private DefaultDebtRemediationFunction(Type type, @Nullable String factor, @Nullable String offset) {
this.type = type;
// TODO validate factor and offset format
this.factor = StringUtils.deleteWhitespace(factor);
this.offset = StringUtils.deleteWhitespace(offset);
validate();
}

static DebtRemediationFunction create(Type type, @Nullable String factor, @Nullable String offset) {
return new DefaultDebtRemediationFunction(type, factor, offset);
}

static DebtRemediationFunction createLinear(String factor) {
return new DefaultDebtRemediationFunction(Type.LINEAR, factor, null);
}

static DebtRemediationFunction createLinearWithOffset(String factor, String offset) {
return new DefaultDebtRemediationFunction(Type.LINEAR_OFFSET, factor, offset);
}

static DebtRemediationFunction createConstantPerIssue(String offset) {
return new DefaultDebtRemediationFunction(Type.CONSTANT_ISSUE, null, offset);
}

public Type type() {
return type;
}

@CheckForNull
public String factor() {
return factor;
}

@CheckForNull
public String offset() {
return offset;
}

private void validate() {
switch (type) {
case LINEAR:
if (this.factor == null || this.offset != null) {
throw new ValidationException(String.format("%s is invalid, Linear remediation function should only define a factor", this));
}
break;
case LINEAR_OFFSET:
if (this.factor == null || this.offset == null) {
throw new ValidationException(String.format("%s is invalid, Linear with offset remediation function should define both factor and offset", this));
}
break;
case CONSTANT_ISSUE:
if (this.factor != null || this.offset == null) {
throw new ValidationException(String.format("%s is invalid, Constant/issue remediation function should only define an offset", this));
}
break;
default:
throw new IllegalStateException(String.format("Remediation function of %s is unknown", this));
}
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}

DebtRemediationFunction that = (DebtRemediationFunction) o;
return new EqualsBuilder()
.append(type, that.type())
.append(factor, that.factor())
.append(offset, that.offset())
.isEquals();
}

@Override
public int hashCode() {
return new HashCodeBuilder(15, 33)
.append(type)
.append(factor)
.append(offset)
.toHashCode();
}

@Override
public String toString() {
return new ReflectionToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE).toString();
}
}

+ 59
- 0
sonar-plugin-api/src/main/java/org/sonar/api/server/rule/DefaultDebtRemediationFunctions.java ファイルの表示

@@ -0,0 +1,59 @@
/*
* SonarQube, open source software quality management tool.
* Copyright (C) 2008-2014 SonarSource
* mailto:contact AT sonarsource DOT com
*
* SonarQube is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* SonarQube 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

package org.sonar.api.server.rule;

import org.sonar.api.utils.MessageException;

import javax.annotation.Nullable;

class DefaultDebtRemediationFunctions implements DebtRemediationFunctions {

private final String repoKey, key;

DefaultDebtRemediationFunctions(String repoKey, String key) {
this.repoKey = repoKey;
this.key = key;
}

@Override
public DebtRemediationFunction linear(String factor) {
return create(DefaultDebtRemediationFunction.Type.LINEAR, factor, null);
}

@Override
public DebtRemediationFunction linearWithOffset(String factor, String offset) {
return create(DefaultDebtRemediationFunction.Type.LINEAR_OFFSET, factor, offset);
}

@Override
public DebtRemediationFunction constantPerIssue(String offset) {
return create(DefaultDebtRemediationFunction.Type.CONSTANT_ISSUE, null, offset);
}

private DebtRemediationFunction create(DefaultDebtRemediationFunction.Type type, @Nullable String factor, @Nullable String offset) {
try {
return DefaultDebtRemediationFunction.create(type, factor, offset);
} catch (DefaultDebtRemediationFunction.ValidationException e) {
throw MessageException.of(String.format("The rule '%s:%s' is invalid : %s ", this.repoKey, this.key, e.getMessage()));
}
}

}

+ 6
- 0
sonar-plugin-api/src/main/java/org/sonar/api/server/rule/RulesDefinition.java ファイルの表示

@@ -317,10 +317,12 @@ public interface RulesDefinition extends ServerExtension {
private String effortToFixDescription;
private final Set<String> tags = Sets.newTreeSet();
private final Map<String, NewParam> paramsByKey = Maps.newHashMap();
private final DefaultDebtRemediationFunctions functions;

private NewRule(String repoKey, String key) {
this.repoKey = repoKey;
this.key = key;
this.functions = new DefaultDebtRemediationFunctions(repoKey, key);
}

public String key() {
@@ -379,6 +381,10 @@ public interface RulesDefinition extends ServerExtension {
return this;
}

public DebtRemediationFunctions debtRemediationFunctions() {
return functions;
}

public NewRule setDebtRemediationFunction(@Nullable DebtRemediationFunction debtRemediationFunction) {
this.debtRemediationFunction = debtRemediationFunction;
return this;

sonar-plugin-api/src/test/java/org/sonar/api/batch/rule/DebtRemediationFunctionTest.java → sonar-plugin-api/src/test/java/org/sonar/api/batch/rule/DefaultDebtRemediationFunctionTest.java ファイルの表示

@@ -25,7 +25,7 @@ import org.sonar.api.utils.Duration;

import static org.fest.assertions.Assertions.assertThat;

public class DebtRemediationFunctionTest {
public class DefaultDebtRemediationFunctionTest {

@Test
public void create_linear() throws Exception {

+ 0
- 146
sonar-plugin-api/src/test/java/org/sonar/api/server/rule/DebtRemediationFunctionTest.java ファイルの表示

@@ -1,146 +0,0 @@
/*
* SonarQube, open source software quality management tool.
* Copyright (C) 2008-2014 SonarSource
* mailto:contact AT sonarsource DOT com
*
* SonarQube is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* SonarQube 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

package org.sonar.api.server.rule;

import org.junit.Test;

import static org.fest.assertions.Assertions.assertThat;
import static org.fest.assertions.Fail.fail;

public class DebtRemediationFunctionTest {

@Test
public void create_linear() throws Exception {
DebtRemediationFunction function = DebtRemediationFunction.createLinear("10h");
assertThat(function.type()).isEqualTo(DebtRemediationFunction.Type.LINEAR);
assertThat(function.factor()).isEqualTo("10h");
assertThat(function.offset()).isNull();
}

@Test
public void create_linear_with_offset() throws Exception {
DebtRemediationFunction function = DebtRemediationFunction.createLinearWithOffset("10h", "5min");
assertThat(function.type()).isEqualTo(DebtRemediationFunction.Type.LINEAR_OFFSET);
assertThat(function.factor()).isEqualTo("10h");
assertThat(function.offset()).isEqualTo("5min");
}

@Test
public void create_constant_per_issue() throws Exception {
DebtRemediationFunction function = DebtRemediationFunction.createConstantPerIssue("10h");
assertThat(function.type()).isEqualTo(DebtRemediationFunction.Type.CONSTANT_ISSUE);
assertThat(function.factor()).isNull();
assertThat(function.offset()).isEqualTo("10h");
}

@Test
public void sanitize_remediation_factor_and_offset() {
DebtRemediationFunction function = DebtRemediationFunction.create(DebtRemediationFunction.Type.LINEAR_OFFSET, " 1 h ", " 10 mi n");

assertThat(function.factor()).isEqualTo("1h");
assertThat(function.offset()).isEqualTo("10min");
}

@Test
public void fail_to_create_linear_when_no_factor() throws Exception {
try {
DebtRemediationFunction.create(DebtRemediationFunction.Type.LINEAR, null, "10h");
fail();
} catch(Exception e) {
assertThat(e).isInstanceOf(DebtRemediationFunction.ValidationException.class);
}
}

@Test
public void fail_to_create_linear_when_offset() throws Exception {
try {
DebtRemediationFunction.create(DebtRemediationFunction.Type.LINEAR, "5min", "10h");
fail();
} catch(Exception e) {
assertThat(e).isInstanceOf(DebtRemediationFunction.ValidationException.class);
}
}

@Test
public void fail_to_create_constant_per_issue_when_no_offset() throws Exception {
try {
DebtRemediationFunction.create(DebtRemediationFunction.Type.CONSTANT_ISSUE, "10h", null);
fail();
} catch(Exception e) {
assertThat(e).isInstanceOf(DebtRemediationFunction.ValidationException.class);
}
}

@Test
public void fail_to_create_constant_per_issue_when_factor() throws Exception {
try {
DebtRemediationFunction.create(DebtRemediationFunction.Type.CONSTANT_ISSUE, "5min", "10h");
fail();
} catch(Exception e) {
assertThat(e).isInstanceOf(DebtRemediationFunction.ValidationException.class);
}
}

@Test
public void fail_to_create_linear_with_offset_when_no_factor() throws Exception {
try {
DebtRemediationFunction.create(DebtRemediationFunction.Type.LINEAR_OFFSET, null, "10h");
fail();
} catch(Exception e) {
assertThat(e).isInstanceOf(DebtRemediationFunction.ValidationException.class);
}
}

@Test
public void fail_to_create_linear_with_offset_when_no_offset() throws Exception {
try {
DebtRemediationFunction.create(DebtRemediationFunction.Type.LINEAR_OFFSET, "5min", null);
fail();
} catch(Exception e) {
assertThat(e).isInstanceOf(DebtRemediationFunction.ValidationException.class);
}
}

@Test
public void test_equals_and_hashcode() throws Exception {
DebtRemediationFunction function = DebtRemediationFunction.createLinearWithOffset("10h", "5min");
DebtRemediationFunction functionWithSameValue = DebtRemediationFunction.createLinearWithOffset("10h", "5min");
DebtRemediationFunction functionWithDifferentType = DebtRemediationFunction.createConstantPerIssue("5min");

assertThat(function).isEqualTo(function);
assertThat(function).isEqualTo(functionWithSameValue);
assertThat(function).isNotEqualTo(functionWithDifferentType);
assertThat(function).isNotEqualTo(DebtRemediationFunction.createLinearWithOffset("11h", "5min"));
assertThat(function).isNotEqualTo(DebtRemediationFunction.createLinearWithOffset("10h", "6min"));
assertThat(function).isNotEqualTo(DebtRemediationFunction.createLinear("10h"));
assertThat(function).isNotEqualTo(DebtRemediationFunction.createConstantPerIssue("6min"));

assertThat(function.hashCode()).isEqualTo(function.hashCode());
assertThat(function.hashCode()).isEqualTo(functionWithSameValue.hashCode());
assertThat(function.hashCode()).isNotEqualTo(functionWithDifferentType.hashCode());
}

@Test
public void test_to_string() throws Exception {
assertThat(DebtRemediationFunction.createLinearWithOffset("10h", "5min").toString()).isNotNull();
}

}

+ 146
- 0
sonar-plugin-api/src/test/java/org/sonar/api/server/rule/DefaultDebtRemediationFunctionTest.java ファイルの表示

@@ -0,0 +1,146 @@
/*
* SonarQube, open source software quality management tool.
* Copyright (C) 2008-2014 SonarSource
* mailto:contact AT sonarsource DOT com
*
* SonarQube is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* SonarQube 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

package org.sonar.api.server.rule;

import org.junit.Test;

import static org.fest.assertions.Assertions.assertThat;
import static org.fest.assertions.Fail.fail;

public class DefaultDebtRemediationFunctionTest {

@Test
public void create_linear() throws Exception {
DebtRemediationFunction function = DefaultDebtRemediationFunction.createLinear("10h");
assertThat(function.type()).isEqualTo(DefaultDebtRemediationFunction.Type.LINEAR);
assertThat(function.factor()).isEqualTo("10h");
assertThat(function.offset()).isNull();
}

@Test
public void create_linear_with_offset() throws Exception {
DebtRemediationFunction function = DefaultDebtRemediationFunction.createLinearWithOffset("10h", "5min");
assertThat(function.type()).isEqualTo(DefaultDebtRemediationFunction.Type.LINEAR_OFFSET);
assertThat(function.factor()).isEqualTo("10h");
assertThat(function.offset()).isEqualTo("5min");
}

@Test
public void create_constant_per_issue() throws Exception {
DebtRemediationFunction function = DefaultDebtRemediationFunction.createConstantPerIssue("10h");
assertThat(function.type()).isEqualTo(DefaultDebtRemediationFunction.Type.CONSTANT_ISSUE);
assertThat(function.factor()).isNull();
assertThat(function.offset()).isEqualTo("10h");
}

@Test
public void sanitize_remediation_factor_and_offset() {
DebtRemediationFunction function = DefaultDebtRemediationFunction.create(DefaultDebtRemediationFunction.Type.LINEAR_OFFSET, " 1 h ", " 10 mi n");

assertThat(function.factor()).isEqualTo("1h");
assertThat(function.offset()).isEqualTo("10min");
}

@Test
public void fail_to_create_linear_when_no_factor() throws Exception {
try {
DefaultDebtRemediationFunction.create(DefaultDebtRemediationFunction.Type.LINEAR, null, "10h");
fail();
} catch (Exception e) {
assertThat(e).isInstanceOf(DefaultDebtRemediationFunction.ValidationException.class);
}
}

@Test
public void fail_to_create_linear_when_offset() throws Exception {
try {
DefaultDebtRemediationFunction.create(DefaultDebtRemediationFunction.Type.LINEAR, "5min", "10h");
fail();
} catch (Exception e) {
assertThat(e).isInstanceOf(DefaultDebtRemediationFunction.ValidationException.class);
}
}

@Test
public void fail_to_create_constant_per_issue_when_no_offset() throws Exception {
try {
DefaultDebtRemediationFunction.create(DefaultDebtRemediationFunction.Type.CONSTANT_ISSUE, "10h", null);
fail();
} catch (Exception e) {
assertThat(e).isInstanceOf(DefaultDebtRemediationFunction.ValidationException.class);
}
}

@Test
public void fail_to_create_constant_per_issue_when_factor() throws Exception {
try {
DefaultDebtRemediationFunction.create(DefaultDebtRemediationFunction.Type.CONSTANT_ISSUE, "5min", "10h");
fail();
} catch (Exception e) {
assertThat(e).isInstanceOf(DefaultDebtRemediationFunction.ValidationException.class);
}
}

@Test
public void fail_to_create_linear_with_offset_when_no_factor() throws Exception {
try {
DefaultDebtRemediationFunction.create(DefaultDebtRemediationFunction.Type.LINEAR_OFFSET, null, "10h");
fail();
} catch (Exception e) {
assertThat(e).isInstanceOf(DefaultDebtRemediationFunction.ValidationException.class);
}
}

@Test
public void fail_to_create_linear_with_offset_when_no_offset() throws Exception {
try {
DefaultDebtRemediationFunction.create(DefaultDebtRemediationFunction.Type.LINEAR_OFFSET, "5min", null);
fail();
} catch (Exception e) {
assertThat(e).isInstanceOf(DefaultDebtRemediationFunction.ValidationException.class);
}
}

@Test
public void test_equals_and_hashcode() throws Exception {
DebtRemediationFunction function = DefaultDebtRemediationFunction.createLinearWithOffset("10h", "5min");
DebtRemediationFunction functionWithSameValue = DefaultDebtRemediationFunction.createLinearWithOffset("10h", "5min");
DebtRemediationFunction functionWithDifferentType = DefaultDebtRemediationFunction.createConstantPerIssue("5min");

assertThat(function).isEqualTo(function);
assertThat(function).isEqualTo(functionWithSameValue);
assertThat(function).isNotEqualTo(functionWithDifferentType);
assertThat(function).isNotEqualTo(DefaultDebtRemediationFunction.createLinearWithOffset("11h", "5min"));
assertThat(function).isNotEqualTo(DefaultDebtRemediationFunction.createLinearWithOffset("10h", "6min"));
assertThat(function).isNotEqualTo(DefaultDebtRemediationFunction.createLinear("10h"));
assertThat(function).isNotEqualTo(DefaultDebtRemediationFunction.createConstantPerIssue("6min"));

assertThat(function.hashCode()).isEqualTo(function.hashCode());
assertThat(function.hashCode()).isEqualTo(functionWithSameValue.hashCode());
assertThat(function.hashCode()).isNotEqualTo(functionWithDifferentType.hashCode());
}

@Test
public void test_to_string() throws Exception {
assertThat(DefaultDebtRemediationFunction.createLinearWithOffset("10h", "5min").toString()).isNotNull();
}

}

+ 3
- 3
sonar-plugin-api/src/test/java/org/sonar/api/server/rule/RulesDefinitionTest.java ファイルの表示

@@ -71,7 +71,7 @@ public class RulesDefinitionTest {
.setInternalKey("/something")
.setStatus(RuleStatus.BETA)
.setDebtCharacteristic("COMPILER")
.setDebtRemediationFunction(DebtRemediationFunction.create(DebtRemediationFunction.Type.LINEAR_OFFSET, "1h", "10min"))
.setDebtRemediationFunction(DefaultDebtRemediationFunction.create(DefaultDebtRemediationFunction.Type.LINEAR_OFFSET, "1h", "10min"))
.setEffortToFixDescription("squid.S115.effortToFix")
.setTags("one", "two")
.addTags("two", "three", "four");
@@ -92,7 +92,7 @@ public class RulesDefinitionTest {
assertThat(npeRule.template()).isFalse();
assertThat(npeRule.status()).isEqualTo(RuleStatus.BETA);
assertThat(npeRule.debtCharacteristic()).isEqualTo("COMPILER");
assertThat(npeRule.debtRemediationFunction()).isEqualTo(DebtRemediationFunction.create(DebtRemediationFunction.Type.LINEAR_OFFSET, "1h", "10min"));
assertThat(npeRule.debtRemediationFunction()).isEqualTo(DefaultDebtRemediationFunction.create(DefaultDebtRemediationFunction.Type.LINEAR_OFFSET, "1h", "10min"));
assertThat(npeRule.effortToFixDescription()).isEqualTo("squid.S115.effortToFix");
assertThat(npeRule.toString()).isEqualTo("[repository=findbugs, key=NPE]");
assertThat(npeRule.repository()).isSameAs(findbugs);
@@ -312,7 +312,7 @@ public class RulesDefinitionTest {
RulesDefinition.NewRepository newRepository = context.createRepository("findbugs", "java");
newRepository.createRule("NPE").setName("NPE").setHtmlDescription("Detect <code>java.lang.NullPointerException</code>")
.setDebtCharacteristic("")
.setDebtRemediationFunction(DebtRemediationFunction.create(DebtRemediationFunction.Type.LINEAR_OFFSET, "1h", "10min"));
.setDebtRemediationFunction(DefaultDebtRemediationFunction.create(DefaultDebtRemediationFunction.Type.LINEAR_OFFSET, "1h", "10min"));
try {
newRepository.done();
fail();

+ 31
- 19
sonar-server/src/main/java/org/sonar/server/debt/DebtRulesXMLImporter.java ファイルの表示

@@ -35,7 +35,6 @@ import org.sonar.api.ServerExtension;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.server.rule.DebtRemediationFunction;
import org.sonar.api.utils.Duration;
import org.sonar.api.utils.MessageException;

import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
@@ -138,7 +137,7 @@ public class DebtRulesXMLImporter implements ServerExtension {
}
}
if (StringUtils.isNotBlank(ruleRepositoryKey) && StringUtils.isNotBlank(ruleKey)) {
return processRule(RuleKey.of(ruleRepositoryKey, ruleKey), properties);
return createRule(RuleKey.of(ruleRepositoryKey, ruleKey), properties);
}
return null;
}
@@ -169,15 +168,6 @@ public class DebtRulesXMLImporter implements ServerExtension {
return new Property(key, value, textValue);
}

@CheckForNull
private RuleDebt processRule(RuleKey ruleKey, Properties properties) {
try {
return createRule(ruleKey, properties);
} catch (DebtRemediationFunction.ValidationException e) {
throw MessageException.of(String.format("Rule '%s' is invalid : %s", ruleKey, e.getMessage()));
}
}

@CheckForNull
private RuleDebt createRule(RuleKey ruleKey, Properties properties) {
Property function = properties.function();
@@ -197,13 +187,13 @@ public class DebtRulesXMLImporter implements ServerExtension {
private RuleDebt createRuleDebt(RuleKey ruleKey, String function, @Nullable String factor, @Nullable String offset) {
if ("linear_threshold".equals(function) && factor != null) {
LOG.warn(String.format("Linear with threshold function is no longer used, remediation function of '%s' is replaced by linear.", ruleKey));
return new RuleDebt().setRuleKey(ruleKey).setFunction(DebtRemediationFunction.createLinear(factor));
return new RuleDebt().setRuleKey(ruleKey).setType(DebtRemediationFunction.Type.LINEAR).setFactor(factor);
} else if ("constant_resource".equals(function)) {
LOG.warn(String.format("Constant/file function is no longer used, technical debt definitions on '%s' are ignored.", ruleKey));
} else if (DebtRemediationFunction.Type.CONSTANT_ISSUE.name().equalsIgnoreCase(function) && factor != null && offset == null) {
return new RuleDebt().setRuleKey(ruleKey).setFunction(DebtRemediationFunction.createConstantPerIssue(factor));
return new RuleDebt().setRuleKey(ruleKey).setType(DebtRemediationFunction.Type.CONSTANT_ISSUE).setOffset(factor);
} else {
return new RuleDebt().setRuleKey(ruleKey).setFunction(DebtRemediationFunction.create(DebtRemediationFunction.Type.valueOf(function.toUpperCase()), factor, offset));
return new RuleDebt().setRuleKey(ruleKey).setType(DebtRemediationFunction.Type.valueOf(function.toUpperCase())).setFactor(factor).setOffset(offset);
}
return null;
}
@@ -279,7 +269,9 @@ public class DebtRulesXMLImporter implements ServerExtension {
public static class RuleDebt {
private RuleKey ruleKey;
private String characteristicKey;
private DebtRemediationFunction function;
private DebtRemediationFunction.Type type;
private String factor;
private String offset;

public RuleKey ruleKey() {
return ruleKey;
@@ -299,12 +291,32 @@ public class DebtRulesXMLImporter implements ServerExtension {
return this;
}

public DebtRemediationFunction function() {
return function;
public DebtRemediationFunction.Type type() {
return type;
}

public RuleDebt setType(DebtRemediationFunction.Type type) {
this.type = type;
return this;
}

@CheckForNull
public String factor() {
return factor;
}

public RuleDebt setFactor(@Nullable String factor) {
this.factor = factor;
return this;
}

@CheckForNull
public String offset() {
return offset;
}

public RuleDebt setFunction(DebtRemediationFunction function) {
this.function = function;
public RuleDebt setOffset(@Nullable String offset) {
this.offset = offset;
return this;
}
}

+ 13
- 1
sonar-server/src/main/java/org/sonar/server/rule/DeprecatedRulesDefinition.java ファイルの表示

@@ -105,7 +105,19 @@ public class DeprecatedRulesDefinition implements RulesDefinition {
DebtRulesXMLImporter.RuleDebt ruleDebt = findRequirement(ruleDebts, repoKey, ruleKey);
if (ruleDebt != null) {
newRule.setDebtCharacteristic(ruleDebt.characteristicKey());
newRule.setDebtRemediationFunction(ruleDebt.function());
switch (ruleDebt.type()) {
case LINEAR :
newRule.setDebtRemediationFunction(newRule.debtRemediationFunctions().linear(ruleDebt.factor()));
break;
case LINEAR_OFFSET:
newRule.setDebtRemediationFunction(newRule.debtRemediationFunctions().linearWithOffset(ruleDebt.factor(), ruleDebt.offset()));
break;
case CONSTANT_ISSUE:
newRule.setDebtRemediationFunction(newRule.debtRemediationFunctions().constantPerIssue(ruleDebt.offset()));
break;
default :
throw new IllegalArgumentException(String.format("The type '%s' is unknown", ruleDebt.type()));
}
}
}


+ 4
- 4
sonar-server/src/main/java/org/sonar/server/rule/RuleRegistration.java ファイルの表示

@@ -459,11 +459,11 @@ public class RuleRegistration implements Startable {
*/
private void removeActiveRulesOnStillExistingRepositories(List<RuleDto> removedRules, RulesDefinition.Context context) {
List<String> repositoryKeys = newArrayList(Iterables.transform(context.repositories(), new Function<RulesDefinition.Repository, String>() {
@Override
public String apply(RulesDefinition.Repository input) {
return input.key();
@Override
public String apply(RulesDefinition.Repository input) {
return input.key();
}
}
}
));

for (RuleDto rule : removedRules) {

+ 28
- 22
sonar-server/src/test/java/org/sonar/server/debt/DebtRulesXMLImporterTest.java ファイルの表示

@@ -25,7 +25,6 @@ import com.google.common.io.Resources;
import org.junit.Test;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.server.rule.DebtRemediationFunction;
import org.sonar.api.utils.MessageException;

import java.io.IOException;
import java.util.List;
@@ -55,7 +54,9 @@ public class DebtRulesXMLImporterTest {
DebtRulesXMLImporter.RuleDebt ruleDebt = results.get(0);
assertThat(ruleDebt.characteristicKey()).isEqualTo("MEMORY_EFFICIENCY");
assertThat(ruleDebt.ruleKey()).isEqualTo(RuleKey.of("checkstyle", "Regexp"));
assertThat(ruleDebt.function()).isEqualTo(DebtRemediationFunction.createLinear("3h"));
assertThat(ruleDebt.type()).isEqualTo(DebtRemediationFunction.Type.LINEAR);
assertThat(ruleDebt.factor()).isEqualTo("3h");
assertThat(ruleDebt.offset()).isNull();
}

@Test
@@ -68,7 +69,9 @@ public class DebtRulesXMLImporterTest {
DebtRulesXMLImporter.RuleDebt ruleDebt = results.get(0);
assertThat(ruleDebt.characteristicKey()).isEqualTo("MEMORY_EFFICIENCY");
assertThat(ruleDebt.ruleKey()).isEqualTo(RuleKey.of("checkstyle", "Regexp"));
assertThat(ruleDebt.function()).isEqualTo(DebtRemediationFunction.createLinear("3h"));
assertThat(ruleDebt.type()).isEqualTo(DebtRemediationFunction.Type.LINEAR);
assertThat(ruleDebt.factor()).isEqualTo("3h");
assertThat(ruleDebt.offset()).isNull();
}

@Test
@@ -80,7 +83,9 @@ public class DebtRulesXMLImporterTest {

DebtRulesXMLImporter.RuleDebt ruleDebt = results.get(0);
assertThat(ruleDebt.characteristicKey()).isEqualTo("MEMORY_EFFICIENCY");
assertThat(ruleDebt.function()).isEqualTo(DebtRemediationFunction.createLinearWithOffset("3h", "1min"));
assertThat(ruleDebt.type()).isEqualTo(DebtRemediationFunction.Type.LINEAR_OFFSET);
assertThat(ruleDebt.factor()).isEqualTo("3h");
assertThat(ruleDebt.offset()).isEqualTo("1min");
}

@Test
@@ -92,7 +97,9 @@ public class DebtRulesXMLImporterTest {

DebtRulesXMLImporter.RuleDebt ruleDebt = results.get(0);
assertThat(ruleDebt.characteristicKey()).isEqualTo("MEMORY_EFFICIENCY");
assertThat(ruleDebt.function()).isEqualTo(DebtRemediationFunction.createConstantPerIssue("3d"));
assertThat(ruleDebt.type()).isEqualTo(DebtRemediationFunction.Type.CONSTANT_ISSUE);
assertThat(ruleDebt.factor()).isNull();
assertThat(ruleDebt.offset()).isEqualTo("3d");
}

@Test
@@ -104,7 +111,9 @@ public class DebtRulesXMLImporterTest {

DebtRulesXMLImporter.RuleDebt ruleDebt = results.get(0);
assertThat(ruleDebt.characteristicKey()).isEqualTo("MEMORY_EFFICIENCY");
assertThat(ruleDebt.function()).isEqualTo(DebtRemediationFunction.createLinearWithOffset("3d", "1d"));
assertThat(ruleDebt.type()).isEqualTo(DebtRemediationFunction.Type.LINEAR_OFFSET);
assertThat(ruleDebt.factor()).isEqualTo("3d");
assertThat(ruleDebt.offset()).isEqualTo("1d");
}

@Test
@@ -116,7 +125,9 @@ public class DebtRulesXMLImporterTest {

DebtRulesXMLImporter.RuleDebt ruleDebt = results.get(0);
assertThat(ruleDebt.characteristicKey()).isEqualTo("MEMORY_EFFICIENCY");
assertThat(ruleDebt.function()).isEqualTo(DebtRemediationFunction.createLinear("3min"));
assertThat(ruleDebt.type()).isEqualTo(DebtRemediationFunction.Type.LINEAR);
assertThat(ruleDebt.factor()).isEqualTo("3min");
assertThat(ruleDebt.offset()).isNull();
}

@Test
@@ -128,7 +139,9 @@ public class DebtRulesXMLImporterTest {

DebtRulesXMLImporter.RuleDebt ruleDebt = results.get(0);
assertThat(ruleDebt.characteristicKey()).isEqualTo("MEMORY_EFFICIENCY");
assertThat(ruleDebt.function()).isEqualTo(DebtRemediationFunction.createLinear("3h"));
assertThat(ruleDebt.type()).isEqualTo(DebtRemediationFunction.Type.LINEAR);
assertThat(ruleDebt.factor()).isEqualTo("3h");
assertThat(ruleDebt.offset()).isNull();
}

@Test
@@ -140,7 +153,9 @@ public class DebtRulesXMLImporterTest {

DebtRulesXMLImporter.RuleDebt ruleDebt = results.get(0);
assertThat(ruleDebt.characteristicKey()).isEqualTo("MEMORY_EFFICIENCY");
assertThat(ruleDebt.function()).isEqualTo(DebtRemediationFunction.createConstantPerIssue("3h"));
assertThat(ruleDebt.type()).isEqualTo(DebtRemediationFunction.Type.CONSTANT_ISSUE);
assertThat(ruleDebt.factor()).isNull();
assertThat(ruleDebt.offset()).isEqualTo("3h");
}

@Test
@@ -169,7 +184,9 @@ public class DebtRulesXMLImporterTest {
DebtRulesXMLImporter.RuleDebt ruleDebt = results.get(0);
assertThat(ruleDebt.characteristicKey()).isEqualTo("MEMORY_EFFICIENCY");
assertThat(ruleDebt.ruleKey()).isEqualTo(RuleKey.of("checkstyle", "Regexp"));
assertThat(ruleDebt.function()).isEqualTo(DebtRemediationFunction.createLinear("3h"));
assertThat(ruleDebt.type()).isEqualTo(org.sonar.api.server.rule.DebtRemediationFunction.Type.LINEAR);
assertThat(ruleDebt.factor()).isEqualTo("3h");
assertThat(ruleDebt.offset()).isNull();
}

@Test
@@ -179,17 +196,6 @@ public class DebtRulesXMLImporterTest {
assertThat(results).isEmpty();
}

@Test
public void fail_to_import_linear_having_offset() throws Exception {
String xml = getFileContent("fail_to_import_linear_having_offset.xml");
try {
importer.importXML(xml);
fail();
} catch (Exception e) {
assertThat(e).isInstanceOf(MessageException.class);
}
}

@Test
public void fail_on_bad_xml() {
String xml = getFileContent("fail_on_bad_xml.xml");
@@ -197,7 +203,7 @@ public class DebtRulesXMLImporterTest {
try {
new DebtCharacteristicsXMLImporter().importXML(xml);
fail();
} catch (Exception e){
} catch (Exception e) {
assertThat(e).isInstanceOf(IllegalStateException.class);
}
}

+ 7
- 3
sonar-server/src/test/java/org/sonar/server/rule/DeprecatedRulesDefinitionTest.java ファイルの表示

@@ -158,8 +158,10 @@ public class DeprecatedRulesDefinitionTest {
new DebtRulesXMLImporter.RuleDebt()
.setCharacteristicKey("MEMORY_EFFICIENCY")
.setRuleKey(RuleKey.of("checkstyle", "ConstantName"))
.setFunction(DebtRemediationFunction.createLinearWithOffset("1d", "10min")
));
.setType(DebtRemediationFunction.Type.LINEAR_OFFSET)
.setFactor("1d")
.setOffset("10min")
);

Reader javaModelReader = mock(Reader.class);
when(debtModelRepository.createReaderForXMLFile("java")).thenReturn(javaModelReader);
@@ -176,7 +178,9 @@ public class DeprecatedRulesDefinitionTest {
assertThat(rule).isNotNull();
assertThat(rule.key()).isEqualTo("ConstantName");
assertThat(rule.debtCharacteristic()).isEqualTo("MEMORY_EFFICIENCY");
assertThat(rule.debtRemediationFunction()).isEqualTo(DebtRemediationFunction.createLinearWithOffset("1d", "10min"));
assertThat(rule.debtRemediationFunction().type()).isEqualTo(DebtRemediationFunction.Type.LINEAR_OFFSET);
assertThat(rule.debtRemediationFunction().factor()).isEqualTo("1d");
assertThat(rule.debtRemediationFunction().offset()).isEqualTo("10min");
}

}

+ 7
- 5
sonar-server/src/test/java/org/sonar/server/rule/RuleRegistrationTest.java ファイルの表示

@@ -24,7 +24,6 @@ import org.junit.Before;
import org.junit.Test;
import org.sonar.api.rule.RuleStatus;
import org.sonar.api.rule.Severity;
import org.sonar.api.server.rule.DebtRemediationFunction;
import org.sonar.api.server.rule.RulesDefinition;
import org.sonar.api.utils.DateUtils;
import org.sonar.api.utils.MessageException;
@@ -268,7 +267,8 @@ public class RuleRegistrationTest extends AbstractDaoTestCase {
public void insert_extended_repositories() {
task = new RuleRegistration(new RuleDefinitionsLoader(mock(RuleRepositories.class), new RulesDefinition[]{
new FindbugsRepository(), new FbContribRepository()}),
profilesManager, ruleRegistry, esRuleTags, ruleTagOperations, myBatis, ruleDao, ruleTagDao, activeRuleDao, characteristicDao, mock(RegisterDebtModel.class));
profilesManager, ruleRegistry, esRuleTags, ruleTagOperations, myBatis, ruleDao, ruleTagDao, activeRuleDao, characteristicDao, mock(RegisterDebtModel.class)
);

setupData("empty");
task.start();
@@ -285,11 +285,13 @@ public class RuleRegistrationTest extends AbstractDaoTestCase {
.setName("One")
.setHtmlDescription("Description of One")
.setSeverity(Severity.BLOCKER)
.setDebtCharacteristic("MEMORY_EFFICIENCY")
.setDebtRemediationFunction(DebtRemediationFunction.createLinearWithOffset("5d", "10h"))
.setEffortToFixDescription("squid.S115.effortToFix")
.setInternalKey("config1")
.setTags("tag1", "tag3", "tag5");

rule1.setDebtCharacteristic("MEMORY_EFFICIENCY")
.setDebtRemediationFunction(rule1.debtRemediationFunctions().linearWithOffset("5d", "10h"))
.setEffortToFixDescription("squid.S115.effortToFix");

rule1.createParam("param1").setDescription("parameter one").setDefaultValue("default value one");
rule1.createParam("param2").setDescription("parameter two").setDefaultValue("default value two");


+ 0
- 49
sonar-server/src/test/resources/org/sonar/server/debt/DebtRulesXMLImporterTest/fail_to_import_linear_having_offset.xml ファイルの表示

@@ -1,49 +0,0 @@
<!--
~ SonarQube, open source software quality management tool.
~ Copyright (C) 2008-2014 SonarSource
~ mailto:contact AT sonarsource DOT com
~
~ SonarQube is free software; you can redistribute it and/or
~ modify it under the terms of the GNU Lesser General Public
~ License as published by the Free Software Foundation; either
~ version 3 of the License, or (at your option) any later version.
~
~ SonarQube 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
~ Lesser General Public License for more details.
~
~ You should have received a copy of the GNU Lesser General Public License
~ along with this program; if not, write to the Free Software Foundation,
~ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-->

<sqale>
<chc>
<key>USABILITY</key>
<name>Usability</name>
<desc>Estimate usability</desc>
</chc>
<chc>
<key>EFFICIENCY</key>
<name>Efficiency</name>
<chc>
<key>MEMORY_EFFICIENCY</key>
<name>Memory use</name>
<chc>
<rule-repo>checkstyle</rule-repo>
<rule-key>Regexp</rule-key>
<prop>
<key>offset</key>
<val>3.0</val>
<txt>h</txt>
</prop>
<prop>
<key>remediationFunction</key>
<txt>linear</txt>
</prop>
</chc>
</chc>
</chc>

</sqale>

読み込み中…
キャンセル
保存