diff options
author | simonbrandhof <simon.brandhof@gmail.com> | 2010-11-26 12:17:53 +0000 |
---|---|---|
committer | simonbrandhof <simon.brandhof@gmail.com> | 2010-11-26 12:17:53 +0000 |
commit | e4381e4c5a7b6c179c04a8741258049d292fcd1e (patch) | |
tree | 082c632855e0a30626261f1503fe6f083c35ef4d /sonar-batch | |
parent | 33fe1a9edbef6982cd539026aba6b5827edb03b5 (diff) | |
download | sonarqube-e4381e4c5a7b6c179c04a8741258049d292fcd1e.tar.gz sonarqube-e4381e4c5a7b6c179c04a8741258049d292fcd1e.zip |
SONAR-249 fix update of measures when using the delayed mode
Diffstat (limited to 'sonar-batch')
10 files changed, 213 insertions, 70 deletions
diff --git a/sonar-batch/src/main/java/org/sonar/batch/index/DefaultIndex.java b/sonar-batch/src/main/java/org/sonar/batch/index/DefaultIndex.java index d8c411c1285..0418c0b65e3 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/index/DefaultIndex.java +++ b/sonar-batch/src/main/java/org/sonar/batch/index/DefaultIndex.java @@ -22,10 +22,12 @@ package org.sonar.batch.index; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; +import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonar.api.batch.Event; import org.sonar.api.batch.SonarIndex; +import org.sonar.api.database.model.ResourceModel; import org.sonar.api.design.Dependency; import org.sonar.api.measures.*; import org.sonar.api.profiles.RulesProfile; @@ -103,10 +105,6 @@ public final class DefaultIndex extends SonarIndex { this.profile = profile; } - public PersistenceManager getPersistenceManager() { - return persistence; - } - /** * Keep only project stuff */ @@ -136,8 +134,6 @@ public final class DefaultIndex extends SonarIndex { /** * Does nothing if the resource is already registered. - * - * @param resource */ public Resource addResource(Resource resource) { getOrAddBucket(resource); @@ -162,6 +158,7 @@ public final class DefaultIndex extends SonarIndex { LOG.warn("The following resource has not been registered before saving data: " + resource); } + resource.setEffectiveKey(calculateResourceEffectiveKey(currentProject, resource)); bucket = new Bucket(resource); Bucket parentBucket = null; Resource parent = resource.getParent(); @@ -180,6 +177,19 @@ public final class DefaultIndex extends SonarIndex { return bucket; } + static String calculateResourceEffectiveKey(Project project, Resource resource) { + String effectiveKey = resource.getKey(); + if (!StringUtils.equals(Resource.SCOPE_SET, resource.getScope())) { + // not a project nor a library + effectiveKey = new StringBuilder(ResourceModel.KEY_SIZE) + .append(project.getKey()) + .append(':') + .append(resource.getKey()) + .toString(); + } + return effectiveKey; + } + private boolean checkExclusion(Resource resource, Bucket parent) { boolean excluded = (parent != null && parent.isExcluded()) || (resourceFilters != null && resourceFilters.isExcluded(resource)); resource.setExcluded(excluded); diff --git a/sonar-batch/src/main/java/org/sonar/batch/index/DefaultResourcePersister.java b/sonar-batch/src/main/java/org/sonar/batch/index/DefaultResourcePersister.java index 82a3dc0ffe4..e4215524033 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/index/DefaultResourcePersister.java +++ b/sonar-batch/src/main/java/org/sonar/batch/index/DefaultResourcePersister.java @@ -70,7 +70,10 @@ public final class DefaultResourcePersister implements ResourcePersister { } private Snapshot doSaveProject(Project project) { - ResourceModel model = findOrCreateModel(project, project.getKey()); + // temporary hack + project.setEffectiveKey(project.getKey()); + + ResourceModel model = findOrCreateModel(project); model.setLanguageKey(project.getLanguageKey());// ugly, only for projects Snapshot parentSnapshot = null; @@ -111,7 +114,7 @@ public final class DefaultResourcePersister implements ResourcePersister { } private Snapshot doSaveLibrary(Project project, Library library) { - ResourceModel model = findOrCreateModel(library, library.getKey()); + ResourceModel model = findOrCreateModel(library); model = session.save(model); library.setId(model.getId()); // TODO to be removed library.setEffectiveKey(library.getKey()); @@ -150,13 +153,11 @@ public final class DefaultResourcePersister implements ResourcePersister { * Everything except project and library */ private Snapshot doSaveResource(Project project, Resource resource) { - String databaseKey = getDatabaseKey(project, resource); - ResourceModel model = findOrCreateModel(resource, databaseKey); + ResourceModel model = findOrCreateModel(resource); Snapshot projectSnapshot = snapshotsByResource.get(project); model.setRootId(projectSnapshot.getResourceId()); model = session.save(model); resource.setId(model.getId()); // TODO to be removed - resource.setEffectiveKey(databaseKey); Snapshot parentSnapshot = (Snapshot)ObjectUtils.defaultIfNull(getSnapshot(resource.getParent()), projectSnapshot); Snapshot snapshot = new Snapshot(model, parentSnapshot); @@ -177,12 +178,12 @@ public final class DefaultResourcePersister implements ResourcePersister { } - private ResourceModel findOrCreateModel(Resource resource, String databaseKey) { + private ResourceModel findOrCreateModel(Resource resource) { ResourceModel model; try { - model = session.getSingleResult(ResourceModel.class, "key", databaseKey); + model = session.getSingleResult(ResourceModel.class, "key", resource.getEffectiveKey()); if (model == null) { - model = createModel(resource, databaseKey); + model = createModel(resource); } else { mergeModel(model, resource); @@ -190,15 +191,15 @@ public final class DefaultResourcePersister implements ResourcePersister { return model; } catch (NonUniqueResultException e) { - throw new SonarException("The resource '" + databaseKey + "' is duplicated in database."); + throw new SonarException("The resource '" + resource.getEffectiveKey() + "' is duplicated in database."); } } - static ResourceModel createModel(Resource resource, String databaseKey) { + static ResourceModel createModel(Resource resource) { ResourceModel model = new ResourceModel(); model.setEnabled(Boolean.TRUE); model.setDescription(resource.getDescription()); - model.setKey(databaseKey); + model.setKey(resource.getEffectiveKey()); if (resource.getLanguage() != null) { model.setLanguageKey(resource.getLanguage().getKey()); } @@ -232,16 +233,4 @@ public final class DefaultResourcePersister implements ResourcePersister { model.setLanguageKey(resource.getLanguage().getKey()); } } - - static String getDatabaseKey(Project project, Resource resource) { - if (StringUtils.equals(Resource.SCOPE_SET, resource.getScope())) { - // projects + libraries - return resource.getKey(); - } - return new StringBuilder(ResourceModel.KEY_SIZE) - .append(project.getKey()) - .append(':') - .append(resource.getKey()) - .toString(); - } } diff --git a/sonar-batch/src/main/java/org/sonar/batch/index/MeasurePersister.java b/sonar-batch/src/main/java/org/sonar/batch/index/MeasurePersister.java index bd50db6f8b3..75eb3504d1f 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/index/MeasurePersister.java +++ b/sonar-batch/src/main/java/org/sonar/batch/index/MeasurePersister.java @@ -19,7 +19,8 @@ */ package org.sonar.batch.index; -import com.google.common.collect.Sets; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.SetMultimap; import org.apache.commons.lang.math.NumberUtils; import org.slf4j.LoggerFactory; import org.sonar.api.database.DatabaseSession; @@ -32,13 +33,12 @@ import org.sonar.api.resources.Project; import org.sonar.api.resources.Resource; import org.sonar.api.resources.ResourceUtils; -import java.util.Iterator; -import java.util.Set; +import java.util.Map; public final class MeasurePersister { private boolean delayedMode = false; - private Set<MeasureModel> unsavedMeasures = Sets.newLinkedHashSet(); + private SetMultimap<Integer, Measure> unsavedMeasuresBySnapshotId = HashMultimap.create(); private DatabaseSession session; private ResourcePersister resourcePersister; @@ -59,24 +59,20 @@ public final class MeasurePersister { Snapshot snapshot = resourcePersister.saveResource(project, resource); if (snapshot != null) { if (delayedMode && measure.getPersistenceMode().useMemory()) { - MeasureModel model = createModel(measure); - model.setSnapshotId(snapshot.getId()); - unsavedMeasures.add(model); + unsavedMeasuresBySnapshotId.put(snapshot.getId(), measure); - } else if (shouldPersistMeasure(resource, measure)) { - if (measure.getId() != null) { - // update - MeasureModel model = session.reattach(MeasureModel.class, measure.getId()); - model = mergeModel(measure, model); - model.save(session); + } else if (measure.getId() != null) { + // update + MeasureModel model = session.reattach(MeasureModel.class, measure.getId()); + model = mergeModel(measure, model); + model.save(session); - } else { - // insert - MeasureModel model = createModel(measure); - model.setSnapshotId(snapshot.getId()); - model.save(session); - measure.setId(model.getId()); // could be removed - } + } else if (shouldPersistMeasure(resource, measure)) { + // insert + MeasureModel model = createModel(measure); + model.setSnapshotId(snapshot.getId()); + model.save(session); + measure.setId(model.getId()); // could be removed } } } @@ -92,13 +88,14 @@ public final class MeasurePersister { } public void dump() { - LoggerFactory.getLogger(getClass()).debug("{} measures to dump", unsavedMeasures.size()); - for (Iterator<MeasureModel> it = unsavedMeasures.iterator(); it.hasNext();) { - MeasureModel model = it.next(); + LoggerFactory.getLogger(getClass()).debug("{} measures to dump", unsavedMeasuresBySnapshotId.size()); + for (Map.Entry<Integer, Measure> entry : unsavedMeasuresBySnapshotId.entries()) { + MeasureModel model = createModel(entry.getValue()); + model.setSnapshotId(entry.getKey()); model.save(session); - it.remove(); } session.commit(); + unsavedMeasuresBySnapshotId.clear(); } MeasureModel createModel(Measure measure) { diff --git a/sonar-batch/src/main/java/org/sonar/batch/index/ViolationPersister.java b/sonar-batch/src/main/java/org/sonar/batch/index/ViolationPersister.java index 8d893ac2ef8..caf426f0cfd 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/index/ViolationPersister.java +++ b/sonar-batch/src/main/java/org/sonar/batch/index/ViolationPersister.java @@ -62,18 +62,16 @@ public final class ViolationPersister { private Integer getRuleId(Rule rule) { Integer ruleId = ruleIds.get(rule); if (ruleId == null) { - Rule persistedRule = session.getSingleResult(Rule.class, "pluginName", rule.getRepositoryKey(), "key", rule.getKey(), "enabled", true); - if (persistedRule == null) { - throw new SonarException("Rule not found: " + rule); + ruleId = rule.getId(); + if (ruleId == null) { + Rule persistedRule = session.getSingleResult(Rule.class, "pluginName", rule.getRepositoryKey(), "key", rule.getKey(), "enabled", true); + if (persistedRule == null) { + throw new SonarException("Rule not found: " + rule); + } + ruleId = persistedRule.getId(); } - ruleId = persistedRule.getId(); ruleIds.put(rule, ruleId); - } return ruleId; } - - public void clear() { - ruleIds.clear(); - } } diff --git a/sonar-batch/src/test/java/org/sonar/batch/index/DefaultIndexTest.java b/sonar-batch/src/test/java/org/sonar/batch/index/DefaultIndexTest.java index 428a92f37d3..67a0e4e36d6 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/index/DefaultIndexTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/index/DefaultIndexTest.java @@ -19,9 +19,25 @@ */ package org.sonar.batch.index; -import org.junit.Ignore; +import org.junit.Test; +import org.sonar.api.resources.JavaPackage; +import org.sonar.api.resources.Library; +import org.sonar.api.resources.Project; + +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertThat; -@Ignore("to do") public class DefaultIndexTest { + @Test + public void shouldCalculateResourceEffectiveKey() { + Project project = new Project("my_project"); + assertThat(DefaultIndex.calculateResourceEffectiveKey(project, project), is("my_project")); + + JavaPackage javaPackage = new JavaPackage("org.foo"); + assertThat(DefaultIndex.calculateResourceEffectiveKey(project, javaPackage), is("my_project:org.foo")); + + Library library = new Library("junit:junit", "4.7"); + assertThat(DefaultIndex.calculateResourceEffectiveKey(project, library), is("junit:junit")); + } } diff --git a/sonar-batch/src/test/java/org/sonar/batch/index/DefaultResourcePersisterTest.java b/sonar-batch/src/test/java/org/sonar/batch/index/DefaultResourcePersisterTest.java index 8134742fc15..bfd8a913dc6 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/index/DefaultResourcePersisterTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/index/DefaultResourcePersisterTest.java @@ -90,7 +90,7 @@ public class DefaultResourcePersisterTest extends AbstractDbUnitTestCase { ResourcePersister persister = new DefaultResourcePersister(getSession()); persister.saveProject(singleProject); - persister.saveResource(singleProject, new JavaPackage("org.foo")); + persister.saveResource(singleProject, new JavaPackage("org.foo").setEffectiveKey("foo:org.foo")); // check that the directory is attached to the project checkTables("shouldSaveNewDirectory", "projects", "snapshots"); @@ -102,9 +102,9 @@ public class DefaultResourcePersisterTest extends AbstractDbUnitTestCase { ResourcePersister persister = new DefaultResourcePersister(getSession()); persister.saveProject(singleProject); - persister.saveResource(singleProject, new Library("junit:junit", "4.8.2")); - persister.saveResource(singleProject, new Library("junit:junit", "4.8.2"));// do nothing, already saved - persister.saveResource(singleProject, new Library("junit:junit", "3.2")); + persister.saveResource(singleProject, new Library("junit:junit", "4.8.2").setEffectiveKey("junit:junit")); + persister.saveResource(singleProject, new Library("junit:junit", "4.8.2").setEffectiveKey("junit:junit"));// do nothing, already saved + persister.saveResource(singleProject, new Library("junit:junit", "3.2").setEffectiveKey("junit:junit")); checkTables("shouldSaveNewLibrary", "projects", "snapshots"); } @@ -116,8 +116,8 @@ public class DefaultResourcePersisterTest extends AbstractDbUnitTestCase { DefaultResourcePersister persister = new DefaultResourcePersister(getSession()); persister.saveProject(multiModuleProject); persister.saveProject(moduleA); - persister.saveResource(moduleA, new JavaPackage("org.foo")); - persister.saveResource(moduleA, new JavaFile("org.foo.MyClass")); + persister.saveResource(moduleA, new JavaPackage("org.foo").setEffectiveKey("a:org.foo")); + persister.saveResource(moduleA, new JavaFile("org.foo.MyClass").setEffectiveKey("a:org.foo.MyClass")); persister.clear(); assertThat(persister.getSnapshotsByResource().size(), is(2)); diff --git a/sonar-batch/src/test/java/org/sonar/batch/index/MeasurePersisterTest.java b/sonar-batch/src/test/java/org/sonar/batch/index/MeasurePersisterTest.java index bf3b284a1b5..9d0e2b9e599 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/index/MeasurePersisterTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/index/MeasurePersisterTest.java @@ -32,6 +32,8 @@ import org.sonar.api.resources.JavaPackage; import org.sonar.api.resources.Project; import org.sonar.jpa.test.AbstractDbUnitTestCase; +import java.util.List; + import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; import static org.mockito.Matchers.anyObject; @@ -81,6 +83,22 @@ public class MeasurePersisterTest extends AbstractDbUnitTestCase { } @Test + public void shouldAddDelayedMeasureSeveralTimes() { + measurePersister.setDelayedMode(true); + Measure measure = new Measure(ncloc).setValue(200.0); + measurePersister.saveMeasure(project, measure); + + measure.setValue(300.0); + measurePersister.saveMeasure(project, measure); + + measurePersister.dump(); + + List<MeasureModel> coverageMeasures = getSession().getResults(MeasureModel.class, "snapshotId", 3001, "metricId", 1); + assertThat(coverageMeasures.size(), is(1)); + assertThat(coverageMeasures.get(0).getValue(), is(300.0)); + } + + @Test @Ignore("to do") public void shouldInsertDataMeasure() { diff --git a/sonar-batch/src/test/java/org/sonar/batch/index/ViolationPersisterTest.java b/sonar-batch/src/test/java/org/sonar/batch/index/ViolationPersisterTest.java new file mode 100644 index 00000000000..307295c2e29 --- /dev/null +++ b/sonar-batch/src/test/java/org/sonar/batch/index/ViolationPersisterTest.java @@ -0,0 +1,69 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2009 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.batch.index; + +import org.junit.Before; +import org.junit.Test; +import org.sonar.api.database.model.Snapshot; +import org.sonar.api.resources.JavaFile; +import org.sonar.api.resources.Project; +import org.sonar.api.rules.Rule; +import org.sonar.api.rules.RulePriority; +import org.sonar.api.rules.Violation; +import org.sonar.jpa.test.AbstractDbUnitTestCase; + +import static org.mockito.Matchers.anyObject; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class ViolationPersisterTest extends AbstractDbUnitTestCase { + + private ViolationPersister violationPersister; + private Rule rule1 = Rule.create("checkstyle", "com.puppycrawl.tools.checkstyle.checks.header.HeaderCheck", "Check Header"); + private Rule rule2 = Rule.create("checkstyle", "com.puppycrawl.tools.checkstyle.checks.coding.EqualsAvoidNullCheck", "Equals Avoid Null"); + private JavaFile javaFile = new JavaFile("org.foo.Bar"); + + @Before + public void before() { + setupData("shared"); + Snapshot snapshot = getSession().getSingleResult(Snapshot.class, "id", 1000); + ResourcePersister resourcePersister = mock(ResourcePersister.class); + when(resourcePersister.saveResource((Project) anyObject(), eq(javaFile))).thenReturn(snapshot); + violationPersister = new ViolationPersister(getSession(), resourcePersister); + } + + @Test + public void shouldSaveViolations() { + Violation violation1a = Violation.create(rule1, javaFile) + .setPriority(RulePriority.CRITICAL).setLineId(20).setCost(55.6) + .setMessage("the message"); + Violation violation1b = Violation.create(rule1, javaFile) + .setPriority(RulePriority.CRITICAL).setLineId(50).setCost(80.0); + Violation violation2 = Violation.create(rule2, javaFile) + .setPriority(RulePriority.MINOR); + + violationPersister.saveViolation(new Project("project"), violation1a); + violationPersister.saveViolation(new Project("project"), violation1b); + violationPersister.saveViolation(new Project("project"), violation2); + + checkTables("shouldSaveViolations", "rule_failures"); + } +} diff --git a/sonar-batch/src/test/resources/org/sonar/batch/index/ViolationPersisterTest/shared.xml b/sonar-batch/src/test/resources/org/sonar/batch/index/ViolationPersisterTest/shared.xml new file mode 100644 index 00000000000..b9678a48fcc --- /dev/null +++ b/sonar-batch/src/test/resources/org/sonar/batch/index/ViolationPersisterTest/shared.xml @@ -0,0 +1,22 @@ +<dataset> + + <rules_categories id="1" name="Efficiency" description="[null]"/> + <rules_categories id="6" name="Usability" description="[null]"/> + + <rules id="30" name="Check Header" rules_category_id="6" plugin_rule_key="com.puppycrawl.tools.checkstyle.checks.header.HeaderCheck" + plugin_config_key="Checker/Treewalker/HeaderCheck" plugin_name="checkstyle" description="[null]" priority="4" enabled="true" + cardinality="SINGLE" parent_id="[null]"/> + + <rules id="31" name="Equals Avoid Null" rules_category_id="6" plugin_rule_key="com.puppycrawl.tools.checkstyle.checks.coding.EqualsAvoidNullCheck" + plugin_config_key="Checker/TreeWalker/EqualsAvoidNull" plugin_name="checkstyle" description="[null]" priority="4" enabled="true" + cardinality="SINGLE" parent_id="[null]"/> + + <projects id="200" scope="FIL" qualifier="CLA" kee="project:org.foo.Bar" root_id="[null]" + name="Bar" long_name="org.foo.Bar" description="[null]" + enabled="true" language="java" copy_resource_id="[null]" profile_id="[null]"/> + + <snapshots id="1000" project_id="200" parent_snapshot_id="[null]" root_project_id="100" root_snapshot_id="[null]" + scope="FIL" qualifier="CLA" created_at="2008-11-01 13:58:00.00" version="[null]" path="" + status="U" islast="false" depth="3" /> + +</dataset>
\ No newline at end of file diff --git a/sonar-batch/src/test/resources/org/sonar/batch/index/ViolationPersisterTest/shouldSaveViolations-result.xml b/sonar-batch/src/test/resources/org/sonar/batch/index/ViolationPersisterTest/shouldSaveViolations-result.xml new file mode 100644 index 00000000000..1e78b98346e --- /dev/null +++ b/sonar-batch/src/test/resources/org/sonar/batch/index/ViolationPersisterTest/shouldSaveViolations-result.xml @@ -0,0 +1,24 @@ +<dataset> + <rules_categories id="1" name="Efficiency" description="[null]"/> + <rules_categories id="6" name="Usability" description="[null]"/> + + <rules id="30" name="Check Header" rules_category_id="6" plugin_rule_key="com.puppycrawl.tools.checkstyle.checks.header.HeaderCheck" + plugin_config_key="Checker/Treewalker/HeaderCheck" plugin_name="checkstyle" description="[null]" priority="4" enabled="true" + cardinality="SINGLE" parent_id="[null]"/> + + <rules id="31" name="Equals Avoid Null" rules_category_id="6" plugin_rule_key="com.puppycrawl.tools.checkstyle.checks.coding.EqualsAvoidNullCheck" + plugin_config_key="Checker/TreeWalker/EqualsAvoidNull" plugin_name="checkstyle" description="[null]" priority="4" enabled="true" + cardinality="SINGLE" parent_id="[null]"/> + + <projects id="200" scope="FIL" qualifier="CLA" kee="project:org.foo.Bar" root_id="[null]" + name="Bar" long_name="org.foo.Bar" description="[null]" + enabled="true" language="java" copy_resource_id="[null]" profile_id="[null]"/> + + <snapshots id="1000" project_id="200" parent_snapshot_id="[null]" root_project_id="100" root_snapshot_id="[null]" + scope="FIL" qualifier="CLA" created_at="2008-11-01 13:58:00.00" version="[null]" path="" + status="U" islast="false" depth="3" /> + + <RULE_FAILURES ID="1" SNAPSHOT_ID="1000" RULE_ID="30" FAILURE_LEVEL="3" MESSAGE="the message" LINE="20" COST="55.6"/> + <RULE_FAILURES ID="2" SNAPSHOT_ID="1000" RULE_ID="30" FAILURE_LEVEL="3" MESSAGE="[null]" LINE="50" COST="80"/> + <RULE_FAILURES ID="3" SNAPSHOT_ID="1000" RULE_ID="31" FAILURE_LEVEL="1" MESSAGE="[null]" LINE="[null]" COST="[null]"/> +</dataset>
\ No newline at end of file |