diff options
author | Julien Lancelot <julien.lancelot@gmail.com> | 2013-10-09 13:54:05 +0200 |
---|---|---|
committer | Julien Lancelot <julien.lancelot@gmail.com> | 2013-10-09 13:54:05 +0200 |
commit | d325a7119e3ac091a41271d5f5089cefa710ece4 (patch) | |
tree | 9f756b3d29c6789e1967ff7eea5a3efe558d03c3 | |
parent | 590e86c4c7f96efefe7c1330e54c76dae462c6e7 (diff) | |
download | sonarqube-d325a7119e3ac091a41271d5f5089cefa710ece4.tar.gz sonarqube-d325a7119e3ac091a41271d5f5089cefa710ece4.zip |
SONAR-4752 Deactivate the possibility to add quality model
12 files changed, 42 insertions, 376 deletions
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/qualitymodel/ModelDefinition.java b/sonar-plugin-api/src/main/java/org/sonar/api/qualitymodel/ModelDefinition.java index 8e398971977..fc60a319668 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/qualitymodel/ModelDefinition.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/qualitymodel/ModelDefinition.java @@ -25,10 +25,10 @@ import org.sonar.api.ServerExtension; /** * - * This extension point must be implemented to define a new quality model. - * * @since 2.3 + * @deprecated since 4.0. It no more possible to define new quality models. */ +@Deprecated public abstract class ModelDefinition implements ServerExtension { private String name; diff --git a/sonar-server/src/main/java/org/sonar/server/platform/Platform.java b/sonar-server/src/main/java/org/sonar/server/platform/Platform.java index 544d6db80e5..5de10e1e6e8 100644 --- a/sonar-server/src/main/java/org/sonar/server/platform/Platform.java +++ b/sonar-server/src/main/java/org/sonar/server/platform/Platform.java @@ -89,7 +89,6 @@ import org.sonar.server.notifications.NotificationService; import org.sonar.server.permission.InternalPermissionService; import org.sonar.server.permission.InternalPermissionTemplateService; import org.sonar.server.plugins.*; -import org.sonar.server.qualitymodel.DefaultModelManager; import org.sonar.server.rule.RubyRuleService; import org.sonar.server.rules.ProfilesConsole; import org.sonar.server.rules.RulesConsole; @@ -224,7 +223,6 @@ public final class Platform { servicesContainer.addSingleton(ServerIdGenerator.class); // depends on plugins servicesContainer.addSingleton(DefaultModelFinder.class); - servicesContainer.addSingleton(DefaultModelManager.class); servicesContainer.addSingleton(ChartFactory.class); servicesContainer.addSingleton(Languages.class); servicesContainer.addSingleton(Views.class); @@ -335,7 +333,7 @@ public final class Platform { startupContainer.addSingleton(RegisterRules.class); startupContainer.addSingleton(RegisterNewProfiles.class); startupContainer.addSingleton(JdbcDriverDeployer.class); - startupContainer.addSingleton(RegisterQualityModels.class); + startupContainer.addSingleton(VerifyNoQualityModelsAreDefined.class); startupContainer.addSingleton(RegisterTechnicalDebtModel.class); startupContainer.addSingleton(DeleteDeprecatedMeasures.class); startupContainer.addSingleton(GeneratePluginIndex.class); diff --git a/sonar-server/src/main/java/org/sonar/server/qualitymodel/DefaultModelManager.java b/sonar-server/src/main/java/org/sonar/server/qualitymodel/DefaultModelManager.java deleted file mode 100644 index 79db2f6acbc..00000000000 --- a/sonar-server/src/main/java/org/sonar/server/qualitymodel/DefaultModelManager.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * SonarQube, open source software quality management tool. - * Copyright (C) 2008-2013 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.server.qualitymodel; - -import org.apache.commons.lang.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.sonar.api.ServerComponent; -import org.sonar.api.database.DatabaseSession; -import org.sonar.api.qualitymodel.Model; -import org.sonar.api.qualitymodel.ModelDefinition; -import org.sonar.api.utils.SonarException; -import org.sonar.jpa.session.DatabaseSessionFactory; - -import javax.persistence.Query; - -public final class DefaultModelManager implements ServerComponent, ModelManager { - - private static final Logger LOG = LoggerFactory.getLogger(DefaultModelManager.class); - - private ModelDefinition[] definitions; - private DatabaseSessionFactory sessionFactory; - - public DefaultModelManager(DatabaseSessionFactory sessionFactory, ModelDefinition[] definitions) { - this.sessionFactory = sessionFactory; - this.definitions = definitions; - } - - /** - * This constructor is used when there are no templates - */ - public DefaultModelManager(DatabaseSessionFactory sessionFactory) { - this.sessionFactory = sessionFactory; - this.definitions = new ModelDefinition[0]; - } - - /** - * Executed when the server starts - */ - @Override - public ModelManager registerDefinitions() { - DatabaseSession session = sessionFactory.getSession(); - for (ModelDefinition definition : definitions) { - if (StringUtils.isNotBlank(definition.getName()) && !exists(session, definition.getName())) { - LOG.info("Register quality model: " + definition.getName()); - Model model = definition.createModel(); - if (StringUtils.isBlank(model.getName())) { - model.setName(definition.getName()); - } - insert(session, model); - session.commit(); - } - } - return this; - } - - @Override - public Model reset(String name) { - ModelDefinition definition = findDefinitionByName(name); - if (definition == null) { - throw new SonarException("Can not reset quality model. Definition not found: " + name); - } - - LoggerFactory.getLogger(getClass()).info("Reset quality model: " + name); - Model model = definition.createModel(); - return reset(model); - } - - - Model reset(Model model) { - DatabaseSession session = sessionFactory.getSession(); - try { - delete(session, model.getName()); - model = insert(session, model); - session.commit(); - return model; - - } catch (RuntimeException e) { - session.rollback(); - throw e; - } - } - @Override - public ModelDefinition findDefinitionByName(String name) { - for (ModelDefinition definition : definitions) { - if (StringUtils.equals(name, definition.getName())) { - return definition; - } - } - return null; - } - - public static void delete(DatabaseSession session, String name) { - Model model = session.getSingleResult(Model.class, "name", name); - if (model != null) { - session.removeWithoutFlush(model); - session.commit(); - } - } - - public static Model insert(DatabaseSession session, Model model) { - return (Model) session.saveWithoutFlush(model); - } - - public static boolean exists(DatabaseSession session, String name) { - Query query = session.getEntityManager().createQuery("SELECT COUNT(qm) FROM " + Model.class.getSimpleName() + " qm WHERE qm.name=:name"); - query.setParameter("name", name); - Number count = (Number) query.getSingleResult(); - return count.intValue() > 0; - } -} diff --git a/sonar-server/src/main/java/org/sonar/server/qualitymodel/ModelManager.java b/sonar-server/src/main/java/org/sonar/server/qualitymodel/ModelManager.java deleted file mode 100644 index 61a9c1cc1fc..00000000000 --- a/sonar-server/src/main/java/org/sonar/server/qualitymodel/ModelManager.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * SonarQube, open source software quality management tool. - * Copyright (C) 2008-2013 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.server.qualitymodel; - -import org.sonar.api.qualitymodel.Model; -import org.sonar.api.qualitymodel.ModelDefinition; - -public interface ModelManager { - - ModelManager registerDefinitions(); - - Model reset(String name); - - ModelDefinition findDefinitionByName(String name); -} diff --git a/sonar-server/src/main/java/org/sonar/server/startup/RegisterQualityModels.java b/sonar-server/src/main/java/org/sonar/server/startup/VerifyNoQualityModelsAreDefined.java index 6b712458ad6..12ae1b55088 100644 --- a/sonar-server/src/main/java/org/sonar/server/startup/RegisterQualityModels.java +++ b/sonar-server/src/main/java/org/sonar/server/startup/VerifyNoQualityModelsAreDefined.java @@ -19,25 +19,29 @@ */ package org.sonar.server.startup; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.sonar.api.utils.TimeProfiler; -import org.sonar.server.qualitymodel.ModelManager; +import org.sonar.api.qualitymodel.ModelDefinition; +import org.sonar.api.utils.MessageException; -public final class RegisterQualityModels { - private static final Logger LOG = LoggerFactory.getLogger(RegisterQualityModels.class); - private final ModelManager manager; +/** + * Verify that no quality models are defined by plugin as now a technical debt model is already provided. See SONAR-4752 for detail. + * @see org.sonar.server.startup.RegisterTechnicalDebtModel + */ +public final class VerifyNoQualityModelsAreDefined { + + private final ModelDefinition[] definitions; + + public VerifyNoQualityModelsAreDefined(ModelDefinition[] definitions) { + this.definitions = definitions; + } - /** - * @param registerRulesBeforeModels used only to be started after the creation of check templates - */ - public RegisterQualityModels(ModelManager manager, RegisterRules registerRulesBeforeModels) {// NOSONAR the parameter registerRulesBeforeModels is only used to provide the execution order by picocontainer - this.manager = manager; + public VerifyNoQualityModelsAreDefined() { + this.definitions = new ModelDefinition[0]; } public void start() { - TimeProfiler profiler = new TimeProfiler(LOG).start("Register Quality Models"); - manager.registerDefinitions(); - profiler.stop(); + if (definitions.length > 0) { + throw MessageException.of("The SQALE model definition is already provided by SonarQube. " + + "You're probably using a old version of the SQALE plugin, please upgrade to a newer version."); + } } } diff --git a/sonar-server/src/test/java/org/sonar/server/qualitymodel/DefaultModelManagerTest.java b/sonar-server/src/test/java/org/sonar/server/qualitymodel/DefaultModelManagerTest.java deleted file mode 100644 index ab2469f5089..00000000000 --- a/sonar-server/src/test/java/org/sonar/server/qualitymodel/DefaultModelManagerTest.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * SonarQube, open source software quality management tool. - * Copyright (C) 2008-2013 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.server.qualitymodel; - -import org.junit.Test; -import org.sonar.api.qualitymodel.Characteristic; -import org.sonar.api.qualitymodel.Model; -import org.sonar.api.qualitymodel.ModelDefinition; -import org.sonar.jpa.test.AbstractDbUnitTestCase; - -import java.util.List; - -import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.*; - -public class DefaultModelManagerTest extends AbstractDbUnitTestCase { - - @Test - public void reset() { - setupData("shared"); - DefaultModelManager manager = new DefaultModelManager(getSessionFactory()); - - Model model = Model.createByName("M1"); - Characteristic c1 = model.createCharacteristicByName("NEWM1C1"); - Characteristic c1a = model.createCharacteristicByName("NEWM1C1A"); - c1.addChild(c1a); - - model.createCharacteristicByName("NEWM1C2"); - manager.reset(model); - - model = getSession().getSingleResult(Model.class, "name", "M1"); - assertNotNull(model); - assertThat(model.getCharacteristics().size(), is(3)); - assertThat(model.getCharacteristicByName("NEWM1C1A").getParents().size(), is(1)); - assertNotNull(model.getCharacteristicByName("NEWM1C1A").getParent("NEWM1C1")); - } - - @Test - public void noDefinitionsToRegister() { - setupData("shared"); - ModelManager provider = new DefaultModelManager(getSessionFactory()); - provider.registerDefinitions(); - - // same state - List<Model> models = getSession().getResults(Model.class); - assertThat(models.size(), is(2)); - } - - @Test - public void registerOnlyNewDefinitions() { - setupData("shared"); - - ModelDefinition existingDefinition = new FakeDefinition("M1", Model.create()); - ModelDefinition newDefinition = new FakeDefinition("NEWMODEL", Model.create()); - - ModelDefinition[] definitions = new ModelDefinition[]{existingDefinition, newDefinition}; - ModelManager manager = new DefaultModelManager(getSessionFactory(), definitions); - manager.registerDefinitions(); - - List<Model> models = getSession().getResults(Model.class); - assertThat(models.size(), is(3)); // 2 existing + one new - } - - @Test - public void registerModelProperties() { - Model model = Model.create(); - Characteristic characteristic = model.createCharacteristicByName("Usability"); - characteristic.setProperty("factor", 2.0); - characteristic.setProperty("severity", "BLOCKER"); - - setupData("shared"); - ModelDefinition def = new FakeDefinition("with-properties", model); - ModelManager manager = new DefaultModelManager(getSessionFactory(), new ModelDefinition[]{def}); - manager.registerDefinitions(); - checkTables("registerModelProperties", "quality_models", "characteristics", "characteristic_properties"); - } - - @Test - public void exists() { - setupData("shared"); - assertTrue(DefaultModelManager.exists(getSession(), "M1")); - } - - @Test - public void notExists() { - setupData("shared"); - assertFalse(DefaultModelManager.exists(getSession(), "UNKNOWN")); - } -} - -class FakeDefinition extends ModelDefinition { - private final Model model; - - public FakeDefinition(String name, Model model) { - super(name); - this.model = model; - } - - @Override - public Model createModel() { - return model; - } - -} diff --git a/sonar-server/src/test/java/org/sonar/server/startup/RegisterQualityModelsTest.java b/sonar-server/src/test/java/org/sonar/server/startup/VerifyNoQualityModelsAreDefinedTest.java index 98bed157c1d..36b4a08abcd 100644 --- a/sonar-server/src/test/java/org/sonar/server/startup/RegisterQualityModelsTest.java +++ b/sonar-server/src/test/java/org/sonar/server/startup/VerifyNoQualityModelsAreDefinedTest.java @@ -20,18 +20,30 @@ package org.sonar.server.startup; import org.junit.Test; -import org.sonar.server.qualitymodel.ModelManager; +import org.sonar.api.qualitymodel.ModelDefinition; +import org.sonar.api.utils.MessageException; +import static org.fest.assertions.Assertions.assertThat; +import static org.fest.assertions.Fail.fail; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -public class RegisterQualityModelsTest { +public class VerifyNoQualityModelsAreDefinedTest { @Test - public void isASimpleBridgeOverProvider() { - ModelManager manager = mock(ModelManager.class); - RegisterQualityModels startup = new RegisterQualityModels(manager, null); - startup.start(); - verify(manager).registerDefinitions(); + public void not_fail_if_no_model_defined() { + // Not fail + VerifyNoQualityModelsAreDefined verifyNoQualityModelsAreDefined = new VerifyNoQualityModelsAreDefined(); + verifyNoQualityModelsAreDefined.start(); + } + + @Test + public void fail_if_at_least_one_model_is_defined() { + try { + VerifyNoQualityModelsAreDefined verifyNoQualityModelsAreDefined = new VerifyNoQualityModelsAreDefined(new ModelDefinition[]{mock(ModelDefinition.class)}); + verifyNoQualityModelsAreDefined.start(); + fail(); + } catch (Exception e) { + assertThat(e).isInstanceOf(MessageException.class); + } } } diff --git a/sonar-server/src/test/resources/org/sonar/server/qualitymodel/DefaultModelManagerTest/noDefinitionsToRegister-result.xml b/sonar-server/src/test/resources/org/sonar/server/qualitymodel/DefaultModelManagerTest/noDefinitionsToRegister-result.xml deleted file mode 100644 index c26de5371bb..00000000000 --- a/sonar-server/src/test/resources/org/sonar/server/qualitymodel/DefaultModelManagerTest/noDefinitionsToRegister-result.xml +++ /dev/null @@ -1,10 +0,0 @@ -<dataset> - <quality_models id="1" name="M1" /> - <quality_models id="2" name="M2" /> - - <characteristics id="1" kee="M1C1" name="M1C1" quality_model_id="1" rule_id="[null]" characteristic_order="1" depth="1" description="[null]" enabled="true" /> - <characteristics id="2" kee="M1C2" name="M1C2" quality_model_id="1" rule_id="[null]" characteristic_order="1" depth="2" description="[null]" enabled="true" /> - <characteristics id="3" kee="M2C1" name="M2C1" quality_model_id="2" rule_id="[null]" characteristic_order="1" depth="1" description="[null]" enabled="true"/> - - <characteristic_edges child_id="2" parent_id="1"/> -</dataset>
\ No newline at end of file diff --git a/sonar-server/src/test/resources/org/sonar/server/qualitymodel/DefaultModelManagerTest/registerModelProperties-result.xml b/sonar-server/src/test/resources/org/sonar/server/qualitymodel/DefaultModelManagerTest/registerModelProperties-result.xml deleted file mode 100644 index 6deb78039ac..00000000000 --- a/sonar-server/src/test/resources/org/sonar/server/qualitymodel/DefaultModelManagerTest/registerModelProperties-result.xml +++ /dev/null @@ -1,16 +0,0 @@ -<dataset> - <quality_models id="1" name="M1" /> - <quality_models id="2" name="M2" /> - <quality_models id="3" name="with-properties" /> - - <characteristics id="1" kee="M1C1" name="M1C1" quality_model_id="1" rule_id="[null]" characteristic_order="1" depth="1" description="[null]" enabled="true" /> - <characteristics id="2" kee="M1C2" name="M1C2" quality_model_id="1" rule_id="[null]" characteristic_order="1" depth="2" description="[null]" enabled="true" /> - <characteristics id="3" kee="M2C1" name="M2C1" quality_model_id="2" rule_id="[null]" characteristic_order="1" depth="1" description="[null]" enabled="true"/> - <characteristics id="4" kee="USABILITY" name="Usability" quality_model_id="3" rule_id="[null]" characteristic_order="1" depth="1" description="[null]" enabled="true"/> - - <characteristic_edges child_id="2" parent_id="1"/> - - <characteristic_properties id="1" characteristic_id="1" kee="foo" value="[null]" text_value="bar" /> - <characteristic_properties id="2" characteristic_id="4" kee="factor" value="2.0" text_value="[null]" /> - <characteristic_properties id="3" characteristic_id="4" kee="severity" value="[null]" text_value="BLOCKER" /> -</dataset>
\ No newline at end of file diff --git a/sonar-server/src/test/resources/org/sonar/server/qualitymodel/DefaultModelManagerTest/registerOnlyNewDefinitions-result.xml b/sonar-server/src/test/resources/org/sonar/server/qualitymodel/DefaultModelManagerTest/registerOnlyNewDefinitions-result.xml deleted file mode 100644 index 455cfb41cde..00000000000 --- a/sonar-server/src/test/resources/org/sonar/server/qualitymodel/DefaultModelManagerTest/registerOnlyNewDefinitions-result.xml +++ /dev/null @@ -1,13 +0,0 @@ -<dataset> - <quality_models id="1" name="M1" /> - <quality_models id="2" name="M2" /> - - <!-- NEW MODEL --> - <quality_models id="3" name="NEWMODEL" /> - - <characteristics id="1" kee="M1C1" name="M1C1" quality_model_id="1" rule_id="[null]" characteristic_order="1" depth="1" description="[null]" enabled="true" /> - <characteristics id="2" kee="M1C2" name="M1C2" quality_model_id="1" rule_id="[null]" characteristic_order="1" depth="2" description="[null]" enabled="true" /> - <characteristics id="3" kee="M2C1" name="M2C1" quality_model_id="2" rule_id="[null]" characteristic_order="1" depth="1" description="[null]" enabled="true"/> - - <characteristic_edges child_id="2" parent_id="1"/> -</dataset>
\ No newline at end of file diff --git a/sonar-server/src/test/resources/org/sonar/server/qualitymodel/DefaultModelManagerTest/reset-result.xml b/sonar-server/src/test/resources/org/sonar/server/qualitymodel/DefaultModelManagerTest/reset-result.xml deleted file mode 100644 index bbffd8e4a77..00000000000 --- a/sonar-server/src/test/resources/org/sonar/server/qualitymodel/DefaultModelManagerTest/reset-result.xml +++ /dev/null @@ -1,15 +0,0 @@ -<dataset> - <!--<quality_models id="1" name="M1" />--> - <quality_models id="2" name="M2" /> - <quality_models id="3" name="M1" /> - - <!--<characteristics id="1" kee="M1C1" name="M1C1" quality_model_id="1" rule_id="[null]" characteristic_order="1" depth="1" description="[null]" enabled="true" />--> - <!--<characteristics id="2" kee="M1C2" name="M1C2" quality_model_id="1" rule_id="[null]" characteristic_order="1" depth="2" description="[null]" enabled="true"/>--> - <characteristics id="3" kee="M2C1" name="M2C1" quality_model_id="2" rule_id="[null]" characteristic_order="1" depth="1" description="[null]" enabled="true" /> - <characteristics id="4" kee="NEWM1C1A" name="NEWM1C1A" quality_model_id="3" rule_id="[null]" characteristic_order="1" depth="2" description="[null]" enabled="true"/> - <characteristics id="5" kee="NEWM1C1" name="NEWM1C1" quality_model_id="3" rule_id="[null]" characteristic_order="1" depth="1" description="[null]" enabled="true"/> - <characteristics id="6" kee="NEWM1C2" name="NEWM1C2" quality_model_id="3" rule_id="[null]" characteristic_order="2" depth="1" description="[null]" enabled="true"/> - - <!--<characteristic_edges child_id="2" parent_id="1"/>--> - <characteristic_edges child_id="4" parent_id="5"/> -</dataset>
\ No newline at end of file diff --git a/sonar-server/src/test/resources/org/sonar/server/qualitymodel/DefaultModelManagerTest/shared.xml b/sonar-server/src/test/resources/org/sonar/server/qualitymodel/DefaultModelManagerTest/shared.xml deleted file mode 100644 index c509eb3f937..00000000000 --- a/sonar-server/src/test/resources/org/sonar/server/qualitymodel/DefaultModelManagerTest/shared.xml +++ /dev/null @@ -1,12 +0,0 @@ -<dataset> - <quality_models id="1" name="M1" /> - <quality_models id="2" name="M2" /> - - <characteristics id="1" kee="M1C1" name="M1C1" quality_model_id="1" rule_id="[null]" characteristic_order="1" depth="1" description="[null]" enabled="true" /> - <characteristics id="2" kee="M1C2" name="M1C2" quality_model_id="1" rule_id="[null]" characteristic_order="1" depth="2" description="[null]" enabled="true" /> - <characteristics id="3" kee="M2C1" name="M2C1" quality_model_id="2" rule_id="[null]" characteristic_order="1" depth="1" description="[null]" enabled="true"/> - - <characteristic_edges child_id="2" parent_id="1"/> - - <characteristic_properties id="1" characteristic_id="1" kee="foo" value="[null]" text_value="bar" /> -</dataset>
\ No newline at end of file |