]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-4923 List inheritance profiles now uses Java facade
authorJulien Lancelot <julien.lancelot@sonarsource.com>
Wed, 8 Jan 2014 16:27:05 +0000 (17:27 +0100)
committerJulien Lancelot <julien.lancelot@sonarsource.com>
Wed, 8 Jan 2014 16:27:05 +0000 (17:27 +0100)
19 files changed:
sonar-core/src/main/java/org/sonar/core/qualityprofile/db/QualityProfileDao.java
sonar-core/src/main/java/org/sonar/core/qualityprofile/db/QualityProfileMapper.java
sonar-core/src/main/resources/org/sonar/core/qualityprofile/db/QualityProfileMapper.xml
sonar-core/src/test/java/org/sonar/core/qualityprofile/db/QualityProfileDaoTest.java
sonar-core/src/test/resources/org/sonar/core/qualityprofile/db/QualityProfileDaoTest/select_by_language.xml [new file with mode: 0644]
sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileRule.java
sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileSearch.java
sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfiles.java
sonar-server/src/main/java/org/sonar/server/rule/ProfileRuleQuery.java
sonar-server/src/main/java/org/sonar/server/rule/ProfileRules.java
sonar-server/src/main/webapp/WEB-INF/app/controllers/profiles_controller.rb
sonar-server/src/main/webapp/WEB-INF/app/helpers/profiles_helper.rb
sonar-server/src/main/webapp/WEB-INF/app/views/profiles/inheritance.html.erb
sonar-server/src/test/java/org/sonar/server/qualityprofile/QProfileSearchTest.java
sonar-server/src/test/java/org/sonar/server/qualityprofile/QProfilesTest.java
sonar-server/src/test/java/org/sonar/server/rule/ProfileRuleQueryTest.java
sonar-server/src/test/java/org/sonar/server/rule/ProfileRulesTest.java
sonar-server/src/test/resources/org/sonar/server/rule/ProfileRulesTest/should_find_active_rules/active_rule25.json
sonar-server/src/test/resources/org/sonar/server/rule/ProfileRulesTest/should_find_active_rules/active_rule391.json

index 646d3edf2d9cd48426684a2eb07ad6efe5168d56..b4fb64c40fa4a7fee858649b1be9ef1bfc6c9c33 100644 (file)
@@ -26,6 +26,8 @@ import org.sonar.api.ServerComponent;
 import org.sonar.core.component.ComponentDto;
 import org.sonar.core.persistence.MyBatis;
 
+import javax.annotation.CheckForNull;
+
 import java.util.List;
 
 public class QualityProfileDao implements ServerComponent {
@@ -97,6 +99,16 @@ public class QualityProfileDao implements ServerComponent {
     }
   }
 
+  public List<QualityProfileDto> selectByLanguage(String language) {
+    SqlSession session = mybatis.openSession();
+    try {
+      return session.getMapper(QualityProfileMapper.class).selectByLanguage(language);
+    } finally {
+      MyBatis.closeQuietly(session);
+    }
+  }
+
+  @CheckForNull
   public QualityProfileDto selectById(Integer id) {
     SqlSession session = mybatis.openSession();
     try {
@@ -106,6 +118,7 @@ public class QualityProfileDao implements ServerComponent {
     }
   }
 
+  @CheckForNull
   public QualityProfileDto selectParent(Integer childId) {
     SqlSession session = mybatis.openSession();
     try {
index 089e6b5d8bd358bb1e853a227676f4c8f1b15291..8338ab6c885fd7dc3e25e6ec42c5596bea236152 100644 (file)
@@ -46,6 +46,7 @@ public interface QualityProfileMapper {
   @CheckForNull
   QualityProfileDto selectById(@Param("id") Integer id);
 
+  List<QualityProfileDto> selectByLanguage(String language);
 
   // INHERITANCE
 
@@ -64,5 +65,4 @@ public interface QualityProfileMapper {
 
   List<QualityProfileDto> selectByProject(@Param("projectId") Long projectId, @Param("key") String propertyKeyPrefix);
 
-
 }
index 3fdf3073ce16e99ff37c7189de849b04ee84ed45..2d833b76fafc8cd386547c0c6eb2346c76df5b2c 100644 (file)
     </where>
   </select>
 
+  <select id="selectByLanguage" parameterType="String" resultType="QualityProfile">
+    SELECT DISTINCT <include refid="profilesColumns"/>
+    FROM rules_profiles p
+    <where>
+      AND p.language=#{language}
+    </where>
+    ORDER BY p.name
+  </select>
+
   <select id="selectById" parameterType="Integer" resultType="QualityProfile">
     SELECT <include refid="profilesColumns"/>
     FROM rules_profiles p
index f35bb8765bc6a341fa9141bbcd87896f1fdcb881..863471257c9d6fbfe2da2ec0d066bc6db899f3ce 100644 (file)
@@ -141,6 +141,16 @@ public class QualityProfileDaoTest extends AbstractDaoTestCase {
     assertThat(dao.selectByNameAndLanguage("Sonar Way", "unknown")).isNull();
   }
 
+  @Test
+  public void select_by_language() {
+    setupData("select_by_language");
+
+    List<QualityProfileDto> result = dao.selectByLanguage("java");
+    assertThat(result).hasSize(2);
+    assertThat(result.get(0).getName()).isEqualTo("Sonar Way 1");
+    assertThat(result.get(1).getName()).isEqualTo("Sonar Way 2");
+  }
+
   @Test
   public void select_by_id() {
     setupData("shared");
diff --git a/sonar-core/src/test/resources/org/sonar/core/qualityprofile/db/QualityProfileDaoTest/select_by_language.xml b/sonar-core/src/test/resources/org/sonar/core/qualityprofile/db/QualityProfileDaoTest/select_by_language.xml
new file mode 100644 (file)
index 0000000..d3c7038
--- /dev/null
@@ -0,0 +1,12 @@
+<dataset>
+
+  <rules_profiles id="1" name="Sonar Way 1" language="java" parent_name="[null]" version="1"
+                  used_profile="[false]"/>
+
+  <rules_profiles id="2" name="Sonar Way" language="js" parent_name="[null]" version="1"
+                  used_profile="[false]"/>
+
+  <rules_profiles id="3" name="Sonar Way 2" language="java" parent_name="[null]" version="1"
+                  used_profile="[false]"/>
+
+</dataset>
index 837c52f1508d998211060ba8b2b5ec0485e78502..d7a6693e8c0909b3e34ed49ef4b68f351f593cdb 100644 (file)
@@ -23,7 +23,6 @@ import org.apache.commons.lang.builder.ReflectionToStringBuilder;
 import org.elasticsearch.common.collect.Lists;
 import org.elasticsearch.common.collect.Maps;
 import org.elasticsearch.common.joda.time.format.ISODateTimeFormat;
-import org.sonar.api.rules.ActiveRule;
 import org.sonar.check.Cardinality;
 import org.sonar.server.rule.ActiveRuleDocument;
 import org.sonar.server.rule.RuleDocument;
@@ -36,6 +35,9 @@ import java.util.Map;
 
 public class QProfileRule {
 
+  public static final String INHERITED = "INHERITED";
+  public static final String OVERRIDES = "OVERRIDES";
+
   private final Integer id;
   private final Integer parentId;
   private final String key;
@@ -200,11 +202,11 @@ public class QProfileRule {
   }
 
   public boolean isInherited() {
-    return ActiveRule.INHERITED.equals(inheritance);
+    return INHERITED.equals(inheritance);
   }
 
   public boolean isOverrides() {
-    return ActiveRule.OVERRIDES.equals(inheritance);
+    return OVERRIDES.equals(inheritance);
   }
 
   public boolean isTemplate() {
index 27d9ad2abc93465352c4825eea0b4f4b34231477..60370762bc297bf4bb96a4f45fd556725355325e 100644 (file)
@@ -44,6 +44,10 @@ public class QProfileSearch implements ServerComponent {
     return toQProfiles(dao.selectAll());
   }
 
+  public List<QProfile> profiles(String language) {
+    return toQProfiles(dao.selectByLanguage(language));
+  }
+
   @CheckForNull
   public QProfile defaultProfile(String language) {
     QualityProfileDto dto = dao.selectDefaultProfile(language, QProfileProjectService.PROPERTY_PREFIX + language);
@@ -57,6 +61,24 @@ public class QProfileSearch implements ServerComponent {
     return toQProfiles(dao.selectChildren(profile.name(), profile.language()));
   }
 
+  public List<QProfile> ancestors(QProfile profile) {
+    List<QProfile> ancestors = newArrayList();
+    incrementAncestors(profile, ancestors);
+    return ancestors;
+  }
+
+  private void incrementAncestors(QProfile profile, List<QProfile> ancestors){
+    if (profile.parent() != null) {
+      QualityProfileDto parentDto = dao.selectParent(profile.id());
+      if (parentDto == null) {
+        throw new IllegalStateException("Cannot find parent of profile : "+ profile.id());
+      }
+      QProfile parent = QProfile.from(parentDto);
+      ancestors.add(parent);
+      incrementAncestors(parent, ancestors);
+    }
+  }
+
   public int countChildren(QProfile profile) {
     return dao.countChildren(profile.name(), profile.language());
   }
index 9095be6bcc6462244e1afa3dc1fd09f55ed7be05..633b353f20276fa4dcfe41e8965b21e2d794a88b 100644 (file)
@@ -94,13 +94,17 @@ public class QProfiles implements ServerComponent {
   // ACTIVE RULES
   // bulk activate all
   // bulk deactivate all
-  // active rule parameter validation (only Integer types are checked)
+  // active rule parameter validation (only Integer and Boolean types are checked)
 
 
   public List<QProfile> allProfiles() {
     return search.allProfiles();
   }
 
+  public List<QProfile> profilesByLanguage(String language) {
+    return search.profiles(language);
+  }
+
   public QProfile profile(int id) {
     return QProfile.from(findNotNull(id));
   }
@@ -123,6 +127,10 @@ public class QProfiles implements ServerComponent {
     return search.children(profile);
   }
 
+  public List<QProfile> ancestors(QProfile profile) {
+    return search.ancestors(profile);
+  }
+
   public int countChildren(QProfile profile) {
     return search.countChildren(profile);
   }
@@ -219,6 +227,10 @@ public class QProfiles implements ServerComponent {
     return rules.countProfileRules(ProfileRuleQuery.create(profile.id()));
   }
 
+  public long countOverridingProfileRules(QProfile profile) {
+    return rules.countProfileRules(ProfileRuleQuery.create(profile.id()).setInheritance(QProfileRule.OVERRIDES));
+  }
+
   public ProfileRuleChanged activateRule(int profileId, int ruleId, String severity) {
     QualityProfileDto qualityProfile = findNotNull(profileId);
     RuleDto rule = findRuleNotNull(ruleId);
index 397db3757575d2b8639071dc398dab4027d9f5d6..1ec3113831f068603a801e011461ceb0d97f4608 100644 (file)
@@ -26,6 +26,7 @@ import org.sonar.server.exceptions.BadRequestException;
 import org.sonar.server.util.RubyUtils;
 
 import javax.annotation.CheckForNull;
+
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
@@ -44,6 +45,7 @@ public class ProfileRuleQuery {
   private static final String PARAM_REPOSITORY_KEYS = "repositoryKeys";
   private static final String PARAM_SEVERITIES = "severities";
   private static final String PARAM_STATUSES = "statuses";
+  private static final String PARAM_INHERITANCE = "inheritance";
 
   private int profileId;
   private String language;
@@ -51,6 +53,7 @@ public class ProfileRuleQuery {
   private List<String> repositoryKeys;
   private List<String> severities;
   private List<String> statuses;
+  private String inheritance;
 
   private ProfileRuleQuery() {
     repositoryKeys = Lists.newArrayList();
@@ -80,6 +83,9 @@ public class ProfileRuleQuery {
     if (params.get(PARAM_STATUSES) != null) {
       result.addStatuses(optionalVarargs(params.get(PARAM_STATUSES)));
     }
+    if (params.containsKey(PARAM_INHERITANCE)) {
+      result.setInheritance((String) params.get(PARAM_INHERITANCE));
+    }
 
     if (!errors.isEmpty()) {
       throw BadRequestException.of("Incorrect rule search parameters", errors);
@@ -128,6 +134,12 @@ public class ProfileRuleQuery {
     return this;
   }
 
+  public ProfileRuleQuery setInheritance(String inheritance) {
+    this.inheritance = inheritance;
+    return this;
+  }
+
+
   public int profileId() {
     return profileId;
   }
@@ -154,6 +166,11 @@ public class ProfileRuleQuery {
     return ImmutableList.copyOf(statuses);
   }
 
+  @CheckForNull
+  public String inheritance() {
+    return inheritance;
+  }
+
   private static String[] optionalVarargs(Object jRubyArray) {
     List<String> items = RubyUtils.toStrings(jRubyArray);
     String[] empty = new String[0];
index 0402d82a117fbf4a8c15ad4de2a62249ecce18d6..64bafb28dc9b7f6f9fa2da5650ce93b531130d55 100644 (file)
@@ -158,12 +158,20 @@ public class ProfileRules implements ServerExtension {
             hasParentFilter(TYPE_RULE, parentRuleFilter(query))
         );
     addMustTermOrTerms(filter, ActiveRuleDocument.FIELD_SEVERITY, query.severities());
+    String inheritance = query.inheritance();
+    if (inheritance != null) {
+      addMustTermOrTerms(filter, ActiveRuleDocument.FIELD_INHERITANCE, newArrayList(inheritance));
+    }
     return filter;
   }
 
   public long countProfileRules(ProfileRuleQuery query) {
-    return index.executeCount(index.client().prepareCount(INDEX_RULES).setTypes(TYPE_ACTIVE_RULE)
-      .setQuery(QueryBuilders.filteredQuery(QueryBuilders.matchAllQuery(), activeRuleFilter(query))));
+    return index.executeCount(
+      index.client()
+        .prepareCount(INDEX_RULES)
+        .setTypes(TYPE_ACTIVE_RULE)
+      .setQuery(QueryBuilders.filteredQuery(QueryBuilders.matchAllQuery(), activeRuleFilter(query)))
+    );
   }
 
   public long countInactiveProfileRules(ProfileRuleQuery query) {
index b4bce6d2c8313513d665b031040ecef81bef8ba0..cab5e63475a7fa57337a22400e8726c05775607c 100644 (file)
@@ -180,10 +180,15 @@ class ProfilesController < ApplicationController
   # GET /profiles/inheritance?id=<profile id>
   def inheritance
     require_parameters 'id'
-    @profile = Profile.find(params[:id])
 
-    profiles=Profile.all(:conditions => ['language=? and id<>? and (parent_name is null or parent_name<>?)', @profile.language, @profile.id, @profile.name], :order => 'name')
-    @select_parent = [[message('none'), nil]] + profiles.collect { |profile| [profile.name, profile.name] }
+    call_backend do
+      @profile = Internal.quality_profiles.profile(params[:id].to_i)
+      @ancestors = Internal.quality_profiles.ancestors(@profile).to_a
+      @children = Internal.quality_profiles.children(@profile).to_a
+      profiles = Internal.quality_profiles.profilesByLanguage(@profile.language()).to_a.reject{|p| p.id == @profile.id() || p.parent() == @profile.name()}
+      profiles = Api::Utils.insensitive_sort(profiles) { |p| p.name()}
+      @select_parent = [[message('none'), nil]] + profiles.collect { |profile| [profile.name(), profile.name()] }
+    end
 
     set_profile_breadcrumbs
   end
index eb019e18398d618cfd29ca998fc8e2e85e1f19c2..71df4bfae6658296f9503c8e8cacf86e07d85100 100644 (file)
@@ -23,10 +23,11 @@ module ProfilesHelper
     controller.java_facade.getLanguages()
   end
 
-  def label_for_rules_count(profile)
-    label="#{profile.count_active_rules} #{message('rules').downcase}"
+  def label_for_rules_count(qProfile)
+    profile_rules_count = profile_rules_count(qProfile)
+    label = "#{profile_rules_count} #{message('rules').downcase}"
 
-    count_overriding=profile.count_overriding_rules
+    count_overriding = Internal.quality_profiles.countOverridingProfileRules(qProfile).to_i
     if count_overriding>0
       label += message('quality_profiles.including_x_overriding.suffix', :params => count_overriding)
       label += image_tag('overrides.png')
index 06cb2996288440a2f51527c6d77282e364040d9f..806111b245ca1272ef34d63fe5477f0c8cac8df3 100644 (file)
@@ -7,19 +7,19 @@
     <tr>
       <td align="center"  valign="top">
         <div>
-            <% @profile.ancestors.reverse.each do |parent| %>
-              <a href="<%= url_for :action => 'inheritance', :id => parent.id -%>"><%= parent.name -%></a>
+            <% @ancestors.reverse.each do |parent| %>
+              <a href="<%= url_for :action => 'inheritance', :id => parent.id() -%>"><%= parent.name() -%></a>
               <span class="note">(<%= label_for_rules_count(parent) -%>)</span><br/>
               <%= image_tag 'blue-up.png' -%><br/>
             <% end %>
 
             <b><%= @profile.name -%></b> <span class="note">(<%= label_for_rules_count(@profile) -%>)</span><br/>
 
-            <% if @profile.children.size>0 %>
+            <% if @children.size>0 %>
               <%= image_tag 'blue-up.png' -%><br/>
-              <% @profile.children.each_with_index do |child,index| %>
+              <% @children.each_with_index do |child,index| %>
                 <%= ', ' if index>0 -%>
-                <a href="<%= url_for :action => 'inheritance', :id => child.id -%>"><%= child.name -%></a>
+                <a href="<%= url_for :action => 'inheritance', :id => child.id() -%>"><%= child.name() -%></a>
                 <span class="note">(<%= label_for_rules_count(child) -%>)</span>
               <% end %>
               <br/><%= image_tag 'blue-up.png' -%><br/>
@@ -33,8 +33,8 @@
            <h3><%= message('quality_profiles.set_parent') -%>:</h3>
            <p><%= message('quality_profiles.inherit_rules_from_profile') -%>:</p>
            <% form_tag({:action => 'change_parent'}, {:method => 'post'}) do %>
-             <%= hidden_field_tag "id", @profile.id %>
-             <%= select_tag "parent_name", options_for_select(@select_parent, @profile.parent_name) %>
+             <%= hidden_field_tag "id", @profile.id() %>
+             <%= select_tag "parent_name", options_for_select(@select_parent, @profile.parent()) %>
              <%= submit_tag message('change_verb'), :id => 'submit_parent'%>
            <% end %>
          </div>
index 1dcb06b73ab82702fd6f1de5d35596cfb3cdf969..966a4b6521040c23cc695f741f46a1c05d8f2020 100644 (file)
@@ -66,6 +66,12 @@ public class QProfileSearchTest {
     assertThat(qProfile.used()).isFalse();
   }
 
+  @Test
+  public void search_profiles_by_language() throws Exception {
+    search.profiles("java");
+    verify(dao).selectByLanguage("java");
+  }
+
   @Test
   public void search_children_profiles() throws Exception {
     search.children(new QProfile().setName("Sonar Way").setLanguage("java"));
@@ -94,5 +100,25 @@ public class QProfileSearchTest {
     assertThat(search.defaultProfile("java")).isNull();
   }
 
-}
+  @Test
+  public void search_ancestors() throws Exception {
+    when(dao.selectParent(3)).thenReturn(new QualityProfileDto().setId(2).setName("Child").setLanguage("java").setParent("Parent"));
+    when(dao.selectParent(2)).thenReturn(new QualityProfileDto().setId(1).setName("Parent").setLanguage("java"));
+    when(dao.selectParent(1)).thenReturn(null);
 
+    List<QProfile> result = search.ancestors(new QProfile().setId(3).setName("Grandchild").setLanguage("java").setParent("Child"));
+    assertThat(result).hasSize(2);
+  }
+
+  @Test
+  public void fail_to_get_ancestors_if_parent_cannot_be_found() throws Exception {
+    when(dao.selectParent(3)).thenReturn(null);
+
+    try {
+      search.ancestors(new QProfile().setId(3).setName("Grandchild").setLanguage("java").setParent("Child"));
+    } catch (Exception e) {
+      assertThat(e).isInstanceOf(IllegalStateException.class);
+    }
+  }
+
+}
index abfd25fe1dfab7221b70df6d3f8948b5d1e2579a..6175c1e5a142edd31b2d7944f16709e99c76abdf 100644 (file)
@@ -105,6 +105,12 @@ public class QProfilesTest {
     verify(search).allProfiles();
   }
 
+  @Test
+  public void search_profiles_by_language() throws Exception {
+    qProfiles.profilesByLanguage("java");
+    verify(search).profiles("java");
+  }
+
   @Test
   public void search_default_profile_by_language() throws Exception {
     qProfiles.defaultProfile("java");
@@ -131,6 +137,13 @@ public class QProfilesTest {
     verify(search).children(profile);
   }
 
+  @Test
+  public void search_ancestors() throws Exception {
+    QProfile profile = new QProfile();
+    qProfiles.ancestors(profile);
+    verify(search).ancestors(profile);
+  }
+
   @Test
   public void count_children() throws Exception {
     QProfile profile = new QProfile();
index 252a3f909c67fa274e526b16bea3d1299f03b7d2..8c8121c8a7136d20c219839ff3a8be187c009aea 100644 (file)
@@ -32,14 +32,14 @@ import static org.fest.assertions.Assertions.assertThat;
 public class ProfileRuleQueryTest {
 
   @Test
-  public void should_create_basic_query() {
+  public void create_basic_query() {
     final int profileId = 42;
     ProfileRuleQuery query = ProfileRuleQuery.create(profileId);
     assertThat(query.profileId()).isEqualTo(profileId);
   }
 
   @Test
-  public void should_parse_nominal_request() {
+  public void parse_nominal_request() {
     final int profileId = 42;
     Map<String, Object> params = ImmutableMap.of("profileId", (Object) Integer.toString(profileId));
     ProfileRuleQuery query = ProfileRuleQuery.parse(params);
@@ -47,7 +47,19 @@ public class ProfileRuleQueryTest {
   }
 
   @Test
-  public void should_fail_on_missing_profileId() {
+  public void parse_with_inheritance() {
+    final int profileId = 42;
+    Map<String, Object> params = ImmutableMap.of(
+      "profileId", (Object) Integer.toString(profileId),
+      "inheritance", "OVERRIDES"
+    );
+    ProfileRuleQuery query = ProfileRuleQuery.parse(params);
+    assertThat(query.profileId()).isEqualTo(profileId);
+    assertThat(query.inheritance()).isEqualTo("OVERRIDES");
+  }
+
+  @Test
+  public void fail_on_missing_profileId() {
     Map<String, Object> params = ImmutableMap.of();
     try {
       ProfileRuleQuery.parse(params);
@@ -58,7 +70,7 @@ public class ProfileRuleQueryTest {
   }
 
   @Test
-  public void should_fail_on_incorrect_profileId() {
+  public void fail_on_incorrect_profileId() {
     Map<String, Object> params = ImmutableMap.of("profileId", (Object) "not an integer");
     try {
       ProfileRuleQuery.parse(params);
index 8f24536f6da14a9314d9bf4cacf00155c7239cbb..2d2d9caca77a3f2d10ff61b54105bdf6b25b258b 100644 (file)
@@ -115,6 +115,15 @@ public class ProfileRulesTest {
     assertThat(rulesWParam.get(0).params()).hasSize(2);
   }
 
+  @Test
+  public void find_profile_rules_with_inheritance() {
+    Paging paging = Paging.create(10, 1);
+
+    assertThat(profileRules.searchProfileRules(ProfileRuleQuery.create(1).setInheritance(null), paging).rules()).hasSize(3);
+    assertThat(profileRules.searchProfileRules(ProfileRuleQuery.create(1).setInheritance(QProfileRule.INHERITED), paging).rules()).hasSize(1);
+    assertThat(profileRules.searchProfileRules(ProfileRuleQuery.create(1).setInheritance(QProfileRule.OVERRIDES), paging).rules()).hasSize(1);
+  }
+
   @Test
   @Ignore("bug in E/S : fail to do a scroll when filter contain has_parent -> return good total_hits but hits is empty")
   public void find_profile_rule_ids() {
index 7c279b9abd7e90a0da2926572e72e1a5fba3598e..14b8c18903bf5e65dbad5e7fb8402ea44f146a14 100644 (file)
@@ -1 +1 @@
-{"id": 391, "severity": "MAJOR", "profileId": 1, "inheritance": null}
\ No newline at end of file
+{"id": 391, "severity": "MAJOR", "profileId": 1, "inheritance": "INHERITED"}