]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-6573 WS api/metrics/delete delete metrics and associated custom measures 335/head
authorTeryk Bellahsene <teryk.bellahsene@sonarsource.com>
Wed, 27 May 2015 14:28:01 +0000 (16:28 +0200)
committerTeryk Bellahsene <teryk.bellahsene@sonarsource.com>
Thu, 28 May 2015 09:23:55 +0000 (11:23 +0200)
17 files changed:
server/sonar-server/src/main/java/org/sonar/server/custommeasure/persistence/CustomMeasureDao.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/db/DbClient.java
server/sonar-server/src/main/java/org/sonar/server/metric/persistence/MetricDao.java
server/sonar-server/src/main/java/org/sonar/server/metric/ws/DeleteAction.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel1.java
server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
server/sonar-server/src/test/java/org/sonar/server/custommeasure/persistence/CustomMeasureDaoTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/custommeasure/persistence/CustomMeasureTesting.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/metric/ws/DeleteActionTest.java [new file with mode: 0644]
sonar-core/src/main/java/org/sonar/core/custommeasure/db/CustomMeasureDto.java [new file with mode: 0644]
sonar-core/src/main/java/org/sonar/core/custommeasure/db/CustomMeasureMapper.java [new file with mode: 0644]
sonar-core/src/main/java/org/sonar/core/custommeasure/db/package-info.java [new file with mode: 0644]
sonar-core/src/main/java/org/sonar/core/metric/db/MetricMapper.java
sonar-core/src/main/java/org/sonar/core/persistence/DaoUtils.java
sonar-core/src/main/java/org/sonar/core/persistence/MyBatis.java
sonar-core/src/main/resources/org/sonar/core/custommeasure/db/CustomMeasureMapper.xml [new file with mode: 0644]
sonar-core/src/main/resources/org/sonar/core/metric/db/MetricMapper.xml

diff --git a/server/sonar-server/src/main/java/org/sonar/server/custommeasure/persistence/CustomMeasureDao.java b/server/sonar-server/src/main/java/org/sonar/server/custommeasure/persistence/CustomMeasureDao.java
new file mode 100644 (file)
index 0000000..e8bcdcb
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * 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.server.custommeasure.persistence;
+
+import com.google.common.base.Function;
+import java.util.List;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+import org.sonar.api.server.ServerSide;
+import org.sonar.core.custommeasure.db.CustomMeasureDto;
+import org.sonar.core.custommeasure.db.CustomMeasureMapper;
+import org.sonar.core.persistence.DaoComponent;
+import org.sonar.core.persistence.DaoUtils;
+import org.sonar.core.persistence.DbSession;
+
+@ServerSide
+public class CustomMeasureDao implements DaoComponent {
+  public void insert(DbSession session, CustomMeasureDto customMeasureDto) {
+    mapper(session).insert(customMeasureDto);
+  }
+
+  public void deleteByMetricIds(final DbSession session, final List<Integer> metricIds) {
+    DaoUtils.executeLargeInputsWithoutOutput(metricIds, new Function<List<Integer>, Void>() {
+      @Override
+      public Void apply(@Nonnull List<Integer> input) {
+        mapper(session).deleteByMetricIds(metricIds);
+        return null;
+      }
+    });
+  }
+
+  @CheckForNull
+  public CustomMeasureDto selectNullableById(DbSession session, long id) {
+    return mapper(session).selectById(id);
+  }
+
+  private CustomMeasureMapper mapper(DbSession session) {
+    return session.getMapper(CustomMeasureMapper.class);
+  }
+}
index 356a52645477368d47fc9fcb7122183aa607e26f..29a59dceec7b21c805863a89cfa89b85add4e88b 100644 (file)
@@ -50,6 +50,7 @@ import org.sonar.server.component.db.ComponentIndexDao;
 import org.sonar.server.component.db.ComponentLinkDao;
 import org.sonar.server.component.db.SnapshotDao;
 import org.sonar.server.computation.db.AnalysisReportDao;
+import org.sonar.server.custommeasure.persistence.CustomMeasureDao;
 import org.sonar.server.dashboard.db.DashboardDao;
 import org.sonar.server.dashboard.db.WidgetDao;
 import org.sonar.server.dashboard.db.WidgetPropertyDao;
@@ -105,6 +106,7 @@ public class DbClient {
   private final ComponentLinkDao componentLinkDao;
   private final EventDao eventDao;
   private final PurgeDao purgeDao;
+  private final CustomMeasureDao customMeasureDao;
 
   public DbClient(Database db, MyBatis myBatis, DaoComponent... daoComponents) {
     this.db = db;
@@ -125,6 +127,7 @@ public class DbClient {
     resourceDao = getDao(map, ResourceDao.class);
     measureDao = getDao(map, MeasureDao.class);
     metricDao = getDao(map, MetricDao.class);
+    customMeasureDao = getDao(map, CustomMeasureDao.class);
     activityDao = getDao(map, ActivityDao.class);
     authorizationDao = getDao(map, AuthorizationDao.class);
     userDao = getDao(map, UserDao.class);
@@ -213,6 +216,10 @@ public class DbClient {
     return metricDao;
   }
 
+  public CustomMeasureDao customMeasureDao() {
+    return customMeasureDao;
+  }
+
   public ActivityDao activityDao() {
     return activityDao;
   }
index e6a9e352be87718f9fdfac8e8d0fda1626a95008..8b8a01d3bec983aa21116ce2594e816e2901825b 100644 (file)
 
 package org.sonar.server.metric.persistence;
 
+import com.google.common.base.Function;
 import com.google.common.base.Predicate;
 import com.google.common.collect.Collections2;
 import com.google.common.collect.Maps;
+import java.util.List;
+import java.util.Map;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
 import org.apache.ibatis.session.RowBounds;
 import org.sonar.api.server.ServerSide;
 import org.sonar.core.metric.db.MetricDto;
 import org.sonar.core.metric.db.MetricMapper;
 import org.sonar.core.persistence.DaoComponent;
+import org.sonar.core.persistence.DaoUtils;
 import org.sonar.core.persistence.DbSession;
 import org.sonar.server.es.SearchOptions;
 
-import javax.annotation.CheckForNull;
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-import java.util.List;
-import java.util.Map;
-
 import static com.google.common.collect.Lists.newArrayList;
 
 @ServerSide
@@ -76,4 +77,23 @@ public class MetricDao implements DaoComponent {
   private MetricMapper mapper(DbSession session) {
     return session.getMapper(MetricMapper.class);
   }
+
+  public List<MetricDto> selectByKeys(final DbSession session, List<String> keys) {
+    return DaoUtils.executeLargeInputs(keys, new Function<List<String>, List<MetricDto>>() {
+      @Override
+      public List<MetricDto> apply(@Nonnull List<String> input) {
+        return mapper(session).selectByKeys(input);
+      }
+    });
+  }
+
+  public void disable(final DbSession session, List<Integer> ids) {
+    DaoUtils.executeLargeInputsWithoutOutput(ids, new Function<List<Integer>, Void>() {
+      @Override
+      public Void apply(@Nonnull List<Integer> input) {
+        mapper(session).disable(input);
+        return null;
+      }
+    });
+  }
 }
diff --git a/server/sonar-server/src/main/java/org/sonar/server/metric/ws/DeleteAction.java b/server/sonar-server/src/main/java/org/sonar/server/metric/ws/DeleteAction.java
new file mode 100644 (file)
index 0000000..0d06f10
--- /dev/null
@@ -0,0 +1,107 @@
+/*
+ * 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.server.metric.ws;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Lists;
+import java.util.List;
+import javax.annotation.Nonnull;
+import org.sonar.api.server.ws.Request;
+import org.sonar.api.server.ws.Response;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.core.metric.db.MetricDto;
+import org.sonar.core.permission.GlobalPermissions;
+import org.sonar.core.persistence.DbSession;
+import org.sonar.core.persistence.MyBatis;
+import org.sonar.server.db.DbClient;
+import org.sonar.server.user.UserSession;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+public class DeleteAction implements MetricsWsAction {
+  public static final String PARAM_IDS = "ids";
+  public static final String PARAM_KEYS = "keys";
+
+  private final DbClient dbClient;
+  private final UserSession userSession;
+
+  public DeleteAction(DbClient dbClient, UserSession userSession) {
+    this.dbClient = dbClient;
+    this.userSession = userSession;
+  }
+
+  @Override
+  public void define(WebService.NewController context) {
+    WebService.NewAction action = context.createAction("delete")
+      .setHandler(this)
+      .setSince("5.2")
+      .setPost(true)
+      .setDescription("Delete metrics and associated measures. Delete only custom metrics.<br />Ids or keys must be provided. <br />Requires 'Administer System' permission.");
+
+    action.createParam(PARAM_IDS)
+      .setDescription("Metrics ids to delete.")
+      .setExampleValue("5, 23, 42");
+
+    action.createParam(PARAM_KEYS)
+      .setDescription("Metrics keys to delete")
+      .setExampleValue("team_size, business_value");
+  }
+
+  @Override
+  public void handle(Request request, Response response) throws Exception {
+    userSession.checkLoggedIn().checkGlobalPermission(GlobalPermissions.SYSTEM_ADMIN);
+    DbSession dbSession = dbClient.openSession(false);
+    try {
+      List<Integer> ids = loadIds(dbSession, request);
+      dbClient.metricDao().disable(dbSession, ids);
+      dbClient.customMeasureDao().deleteByMetricIds(dbSession, ids);
+      dbSession.commit();
+    } finally {
+      MyBatis.closeQuietly(dbSession);
+    }
+
+    response.noContent();
+  }
+
+  private List<Integer> loadIds(DbSession dbSession, Request request) {
+    List<String> idsAsStrings = request.paramAsStrings(PARAM_IDS);
+    List<String> keys = request.paramAsStrings(PARAM_KEYS);
+    checkArgument(idsAsStrings != null || keys != null, "Ids or keys must be provided.");
+    List<Integer> ids = null;
+    if (idsAsStrings != null) {
+      ids = Lists.transform(idsAsStrings, new Function<String, Integer>() {
+        @Override
+        public Integer apply(String id) {
+          return Integer.valueOf(id);
+        }
+      });
+    } else if (keys != null) {
+      ids = Lists.transform(dbClient.metricDao().selectByKeys(dbSession, keys), new Function<MetricDto, Integer>() {
+        @Override
+        public Integer apply(@Nonnull MetricDto input) {
+          return input.getId();
+        }
+      });
+    }
+
+    return ids;
+  }
+}
index 8216f74dfa197869b5e990c8eacbd0d06e441f20..cb582d899aaa8a4d2fd4d5e82613003f62f4f261 100644 (file)
@@ -39,6 +39,7 @@ import org.sonar.server.component.db.ComponentIndexDao;
 import org.sonar.server.component.db.ComponentLinkDao;
 import org.sonar.server.component.db.SnapshotDao;
 import org.sonar.server.computation.db.AnalysisReportDao;
+import org.sonar.server.custommeasure.persistence.CustomMeasureDao;
 import org.sonar.server.dashboard.db.DashboardDao;
 import org.sonar.server.dashboard.db.WidgetDao;
 import org.sonar.server.dashboard.db.WidgetPropertyDao;
@@ -146,6 +147,7 @@ public class PlatformLevel1 extends PlatformLevel {
       MeasureDao.class,
       MetricDao.class,
       MeasureFilterDao.class,
+      CustomMeasureDao.class,
 
       // components
       ComponentDao.class,
index fc5d6d50cab989a63659b1c89b789e780b27dc5f..eca68f9b1f40c648fdae4176a21641f028bfe606 100644 (file)
@@ -501,6 +501,7 @@ public class PlatformLevel4 extends PlatformLevel {
       org.sonar.server.metric.ws.ListAction.class,
       org.sonar.server.metric.ws.TypesAction.class,
       org.sonar.server.metric.ws.DomainsAction.class,
+      org.sonar.server.metric.ws.DeleteAction.class,
 
       // quality gates
       QualityGateDao.class,
diff --git a/server/sonar-server/src/test/java/org/sonar/server/custommeasure/persistence/CustomMeasureDaoTest.java b/server/sonar-server/src/test/java/org/sonar/server/custommeasure/persistence/CustomMeasureDaoTest.java
new file mode 100644 (file)
index 0000000..e47fa32
--- /dev/null
@@ -0,0 +1,87 @@
+/*
+ * 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.server.custommeasure.persistence;
+
+import java.util.Arrays;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.sonar.core.custommeasure.db.CustomMeasureDto;
+import org.sonar.core.persistence.DbSession;
+import org.sonar.core.persistence.DbTester;
+import org.sonar.server.db.DbClient;
+import org.sonar.test.DbTests;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.offset;
+
+@Category(DbTests.class)
+public class CustomMeasureDaoTest {
+  @ClassRule
+  public static DbTester db = new DbTester();
+
+  CustomMeasureDao sut;
+  DbSession session;
+
+  @Before
+  public void setUp() {
+    DbClient dbClient = new DbClient(db.database(), db.myBatis(), new CustomMeasureDao());
+    session = dbClient.openSession(false);
+    sut = dbClient.customMeasureDao();
+    db.truncateTables();
+  }
+
+  @After
+  public void tearDown() {
+    session.close();
+  }
+
+  @Test
+  public void insert() {
+    CustomMeasureDto measure = CustomMeasureTesting.newDto();
+
+    sut.insert(session, measure);
+
+    CustomMeasureDto result = sut.selectNullableById(session, measure.getId());
+    assertThat(result.getId()).isEqualTo(measure.getId());
+    assertThat(result.getMetricId()).isEqualTo(measure.getMetricId());
+    assertThat(result.getResourceId()).isEqualTo(measure.getResourceId());
+    assertThat(result.getDescription()).isEqualTo(measure.getDescription());
+    assertThat(result.getUserLogin()).isEqualTo(measure.getUserLogin());
+    assertThat(result.getTextValue()).isEqualTo(measure.getTextValue());
+    assertThat(result.getValue()).isCloseTo(measure.getValue(), offset(0.001d));
+    assertThat(result.getCreatedAt()).isEqualTo(measure.getCreatedAt());
+    assertThat(result.getUpdatedAt()).isEqualTo(measure.getUpdatedAt());
+  }
+
+  @Test
+  public void delete() {
+    CustomMeasureDto measure = CustomMeasureTesting.newDto();
+    sut.insert(session, measure);
+    assertThat(sut.selectNullableById(session, measure.getId())).isNotNull();
+
+    sut.deleteByMetricIds(session, Arrays.asList(measure.getMetricId()));
+
+    assertThat(sut.selectNullableById(session, measure.getId())).isNull();
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/custommeasure/persistence/CustomMeasureTesting.java b/server/sonar-server/src/test/java/org/sonar/server/custommeasure/persistence/CustomMeasureTesting.java
new file mode 100644 (file)
index 0000000..3b21581
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * 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.server.custommeasure.persistence;
+
+import org.apache.commons.lang.RandomStringUtils;
+import org.apache.commons.lang.math.RandomUtils;
+import org.sonar.api.utils.System2;
+import org.sonar.core.custommeasure.db.CustomMeasureDto;
+
+public class CustomMeasureTesting {
+  private CustomMeasureTesting() {
+    // static stuff only
+  }
+
+  public static CustomMeasureDto newDto() {
+    return new CustomMeasureDto()
+      .setDescription(RandomStringUtils.random(255))
+      .setTextValue(RandomStringUtils.random(255))
+      .setUserLogin(RandomStringUtils.random(255))
+      .setValue(RandomUtils.nextDouble())
+      .setMetricId(RandomUtils.nextInt())
+      .setResourceId(RandomUtils.nextInt())
+      .setCreatedAt(System2.INSTANCE.now())
+      .setUpdatedAt(System2.INSTANCE.now())
+      ;
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/metric/ws/DeleteActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/metric/ws/DeleteActionTest.java
new file mode 100644 (file)
index 0000000..a663ac8
--- /dev/null
@@ -0,0 +1,163 @@
+/*
+ * 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.server.metric.ws;
+
+import java.util.Arrays;
+import java.util.List;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.rules.ExpectedException;
+import org.sonar.core.custommeasure.db.CustomMeasureDto;
+import org.sonar.core.metric.db.MetricDto;
+import org.sonar.core.permission.GlobalPermissions;
+import org.sonar.core.persistence.DbSession;
+import org.sonar.core.persistence.DbTester;
+import org.sonar.server.custommeasure.persistence.CustomMeasureDao;
+import org.sonar.server.custommeasure.persistence.CustomMeasureTesting;
+import org.sonar.server.db.DbClient;
+import org.sonar.server.exceptions.ForbiddenException;
+import org.sonar.server.metric.persistence.MetricDao;
+import org.sonar.server.tester.UserSessionRule;
+import org.sonar.server.ws.WsTester;
+import org.sonar.test.DbTests;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@Category(DbTests.class)
+public class DeleteActionTest {
+
+  @ClassRule
+  public static DbTester db = new DbTester();
+  @Rule
+  public UserSessionRule userSessionRule = UserSessionRule.standalone();
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+  DbClient dbClient;
+  DbSession dbSession;
+  WsTester ws;
+  MetricDao metricDao;
+
+  @Before
+  public void setUp() throws Exception {
+    dbClient = new DbClient(db.database(), db.myBatis(), new MetricDao(), new CustomMeasureDao());
+    dbSession = dbClient.openSession(false);
+    db.truncateTables();
+    userSessionRule.login("login").setGlobalPermissions(GlobalPermissions.SYSTEM_ADMIN);
+    ws = new WsTester(new MetricsWs(new DeleteAction(dbClient, userSessionRule)));
+    metricDao = dbClient.metricDao();
+  }
+
+  @After
+  public void tearDown() throws Exception {
+    dbSession.close();
+  }
+
+  @Test
+  public void delete_by_keys() throws Exception {
+    insertCustomEnabledMetrics(1, 2, 3);
+    dbSession.commit();
+
+    newRequest().setParam("keys", "key-1, key-3").execute();
+    dbSession.commit();
+
+    List<MetricDto> disabledMetrics = metricDao.selectByKeys(dbSession, Arrays.asList("key-1", "key-3"));
+    assertThat(disabledMetrics).extracting("enabled").containsOnly(false);
+    assertThat(metricDao.selectByKey(dbSession, "key-2").isEnabled()).isTrue();
+  }
+
+  @Test
+  public void delete_by_id() throws Exception {
+    MetricDto metric = newCustomEnabledMetric(1);
+    metricDao.insert(dbSession, metric);
+    dbSession.commit();
+
+    WsTester.Result result = newRequest().setParam("ids", String.valueOf(metric.getId())).execute();
+    dbSession.commit();
+
+    assertThat(metricDao.selectEnabled(dbSession)).isEmpty();
+    result.assertNoContent();
+  }
+
+  @Test
+  public void do_not_delete_non_custom_metric() throws Exception {
+    metricDao.insert(dbSession, newCustomEnabledMetric(1).setUserManaged(false));
+    dbSession.commit();
+
+    newRequest().setParam("keys", "key-1").execute();
+    dbSession.commit();
+
+    MetricDto metric = metricDao.selectByKey(dbSession, "key-1");
+    assertThat(metric.isEnabled()).isTrue();
+  }
+
+  @Test
+  public void delete_associated_measures() throws Exception {
+    MetricDto metric = newCustomEnabledMetric(1);
+    metricDao.insert(dbSession, metric);
+    CustomMeasureDto customMeasure = CustomMeasureTesting.newDto().setMetricId(metric.getId());
+    CustomMeasureDto undeletedCustomMeasure = CustomMeasureTesting.newDto().setMetricId(metric.getId() + 1);
+    dbClient.customMeasureDao().insert(dbSession, customMeasure);
+    dbClient.customMeasureDao().insert(dbSession, undeletedCustomMeasure);
+    dbSession.commit();
+
+    newRequest().setParam("keys", "key-1").execute();
+
+    assertThat(dbClient.customMeasureDao().selectNullableById(dbSession, customMeasure.getId())).isNull();
+    assertThat(dbClient.customMeasureDao().selectNullableById(dbSession, undeletedCustomMeasure.getId())).isNotNull();
+  }
+
+  @Test
+  public void fail_when_no_argument() throws Exception {
+    expectedException.expect(IllegalArgumentException.class);
+
+    newRequest().execute();
+  }
+
+  @Test
+  public void fail_when_insufficient_privileges() throws Exception {
+    expectedException.expect(ForbiddenException.class);
+
+    userSessionRule.setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION);
+    insertCustomEnabledMetrics(1);
+
+    newRequest().setParam("keys", "key-1").execute();
+  }
+
+  private MetricDto newCustomEnabledMetric(int id) {
+    return MetricTesting.newDto().setEnabled(true).setUserManaged(true).setKey("key-" + id);
+  }
+
+  private void insertCustomEnabledMetrics(int... ids) {
+    for (int id : ids) {
+      metricDao.insert(dbSession, newCustomEnabledMetric(id));
+    }
+
+    dbSession.commit();
+  }
+
+  private WsTester.TestRequest newRequest() {
+    return ws.newPostRequest(MetricsWs.ENDPOINT, "delete");
+  }
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/custommeasure/db/CustomMeasureDto.java b/sonar-core/src/main/java/org/sonar/core/custommeasure/db/CustomMeasureDto.java
new file mode 100644 (file)
index 0000000..96c395d
--- /dev/null
@@ -0,0 +1,114 @@
+/*
+ * 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.core.custommeasure.db;
+
+public class CustomMeasureDto {
+  private long id;
+  private int metricId;
+  private int resourceId;
+  private double value;
+  private String textValue;
+  private String userLogin;
+  private String description;
+  private long createdAt;
+  private long updatedAt;
+
+  public String getDescription() {
+    return description;
+  }
+
+  public CustomMeasureDto setDescription(String description) {
+    this.description = description;
+    return this;
+  }
+
+  public String getUserLogin() {
+    return userLogin;
+  }
+
+  public CustomMeasureDto setUserLogin(String userLogin) {
+    this.userLogin = userLogin;
+    return this;
+  }
+
+  public String getTextValue() {
+    return textValue;
+  }
+
+  public CustomMeasureDto setTextValue(String textValue) {
+    this.textValue = textValue;
+    return this;
+  }
+
+  public double getValue() {
+    return value;
+  }
+
+  public CustomMeasureDto setValue(double value) {
+    this.value = value;
+    return this;
+  }
+
+  public int getResourceId() {
+    return resourceId;
+  }
+
+  public CustomMeasureDto setResourceId(int resourceId) {
+    this.resourceId = resourceId;
+    return this;
+  }
+
+  public int getMetricId() {
+    return metricId;
+  }
+
+  public CustomMeasureDto setMetricId(int metricId) {
+    this.metricId = metricId;
+    return this;
+  }
+
+  public long getId() {
+    return id;
+  }
+
+  public CustomMeasureDto setId(long id) {
+    this.id = id;
+    return this;
+  }
+
+  public long getUpdatedAt() {
+    return updatedAt;
+  }
+
+  public CustomMeasureDto setUpdatedAt(long updatedAt) {
+    this.updatedAt = updatedAt;
+    return this;
+  }
+
+  public long getCreatedAt() {
+    return createdAt;
+  }
+
+  public CustomMeasureDto setCreatedAt(long createdAt) {
+    this.createdAt = createdAt;
+    return this;
+  }
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/custommeasure/db/CustomMeasureMapper.java b/sonar-core/src/main/java/org/sonar/core/custommeasure/db/CustomMeasureMapper.java
new file mode 100644 (file)
index 0000000..95bb4fd
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * 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.core.custommeasure.db;
+
+import java.util.List;
+import org.apache.ibatis.annotations.Param;
+
+public interface CustomMeasureMapper {
+  void insert(CustomMeasureDto customMeasureDto);
+
+  void deleteByMetricIds(@Param("metricIds") List<Integer> metricIds);
+
+  CustomMeasureDto selectById(long id);
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/custommeasure/db/package-info.java b/sonar-core/src/main/java/org/sonar/core/custommeasure/db/package-info.java
new file mode 100644 (file)
index 0000000..8a19bbe
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+
+@ParametersAreNonnullByDefault
+package org.sonar.core.custommeasure.db;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
index 3b1a44375e239df91173275eb5b0bd414fbfb5f4..46eaf787e211fb3b470daf47980b08f756bb56cc 100644 (file)
 
 package org.sonar.core.metric.db;
 
-import org.apache.ibatis.annotations.Param;
-import org.apache.ibatis.session.RowBounds;
-
 import java.util.List;
 import java.util.Map;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.session.RowBounds;
 
 public interface MetricMapper {
 
@@ -37,4 +36,8 @@ public interface MetricMapper {
   void insert(MetricDto dto);
 
   List<String> selectDomains();
+
+  List<MetricDto> selectByKeys(@Param("keys") List<String> keys);
+
+  void disable(@Param("ids") List<Integer> ids);
 }
index 935bc122b29701c2f38b966818523e822d2f853d..03372a7071726e07de26866b647de014efe675ac 100644 (file)
@@ -116,6 +116,24 @@ public final class DaoUtils {
     return results;
   }
 
+  /**
+   * Partition by 1000 elements a list of input and execute a function on each part.
+   * The function has not output (ex: delete operation)
+   *
+   * The goal is to prevent issue with ORACLE when there's more than 1000 elements in a 'in ('X', 'Y', ...)'
+   * and with MsSQL when there's more than 2000 parameters in a query
+   */
+  public static <INPUT> void executeLargeInputsWithoutOutput(Collection<INPUT> input, Function<List<INPUT>, Void> function) {
+    if (input.isEmpty()) {
+      return;
+    }
+
+    List<List<INPUT>> partitions = Lists.partition(newArrayList(input), PARTITION_SIZE_FOR_ORACLE);
+    for (List<INPUT> partition : partitions) {
+      function.apply(partition);
+    }
+  }
+
   public static String repeatCondition(String sql, int count, String separator) {
     StringBuilder sb = new StringBuilder();
     for (int i = 0; i < count; i++) {
index abf18b7ef4cfb7a5bdf8d286cfbe5b586b274596..4e79ef0e85082928ced95c2c9f695222ea2d95e8 100644 (file)
@@ -53,6 +53,8 @@ import org.sonar.core.component.db.SnapshotMapper;
 import org.sonar.core.computation.db.AnalysisReportDto;
 import org.sonar.core.computation.db.AnalysisReportMapper;
 import org.sonar.core.config.Logback;
+import org.sonar.core.custommeasure.db.CustomMeasureDto;
+import org.sonar.core.custommeasure.db.CustomMeasureMapper;
 import org.sonar.core.dashboard.ActiveDashboardDto;
 import org.sonar.core.dashboard.ActiveDashboardMapper;
 import org.sonar.core.dashboard.DashboardDto;
@@ -238,6 +240,7 @@ public class MyBatis {
     loadAlias(conf, "FilePathWithHash", FilePathWithHashDto.class);
     loadAlias(conf, "UuidWithProjectUuid", UuidWithProjectUuidDto.class);
     loadAlias(conf, "Event", EventDto.class);
+    loadAlias(conf, "CustomMeasure", CustomMeasureDto.class);
 
     // AuthorizationMapper has to be loaded before IssueMapper because this last one used it
     loadMapper(conf, "org.sonar.core.user.AuthorizationMapper");
@@ -256,7 +259,7 @@ public class MyBatis {
       ActionPlanStatsMapper.class,
       NotificationQueueMapper.class, CharacteristicMapper.class,
       GroupMembershipMapper.class, QualityProfileMapper.class, ActiveRuleMapper.class,
-      MeasureMapper.class, MetricMapper.class, QualityGateMapper.class, QualityGateConditionMapper.class, ComponentMapper.class, SnapshotMapper.class,
+      MeasureMapper.class, MetricMapper.class, CustomMeasureMapper.class, QualityGateMapper.class, QualityGateConditionMapper.class, ComponentMapper.class, SnapshotMapper.class,
       ProjectQgateAssociationMapper.class, EventMapper.class,
       AnalysisReportMapper.class, ComponentIndexMapper.class, ComponentLinkMapper.class,
       Migration45Mapper.class, Migration50Mapper.class
diff --git a/sonar-core/src/main/resources/org/sonar/core/custommeasure/db/CustomMeasureMapper.xml b/sonar-core/src/main/resources/org/sonar/core/custommeasure/db/CustomMeasureMapper.xml
new file mode 100644 (file)
index 0000000..4aefce1
--- /dev/null
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+<mapper namespace="org.sonar.core.custommeasure.db.CustomMeasureMapper">
+  <sql id="selectColumns">
+    m.id,
+    m.metric_id as metricId,
+    m.resource_id as resourceId,
+    m.value,
+    m.text_value as textValue,
+    m.user_login as userLogin,
+    m.description,
+    m.created_at as createdAt,
+    m.updated_at as updatedAt
+  </sql>
+
+  <select id="selectById" resultType="CustomMeasure">
+    select
+    <include refid="selectColumns"/>
+    from manual_measures m
+    where m.id=#{id}
+  </select>
+
+  <insert id="insert" parameterType="CustomMeasure" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
+    INSERT INTO manual_measures (
+    metric_id, resource_id, value, text_value, user_login, description, created_at, updated_at
+    )
+    VALUES (
+    #{metricId, jdbcType=INTEGER}, #{resourceId, jdbcType=INTEGER}, #{value, jdbcType=DOUBLE},
+    #{textValue, jdbcType=VARCHAR}, #{userLogin, jdbcType=VARCHAR},#{description, jdbcType=VARCHAR},
+    #{createdAt, jdbcType=BIGINT}, #{updatedAt, jdbcType=BIGINT}
+    )
+  </insert>
+
+  <delete id="deleteByMetricIds">
+    delete from manual_measures
+    where metric_id in
+    <foreach collection="metricIds" item="metricId" open="(" close=")" separator=",">
+      #{metricId}
+    </foreach>
+  </delete>
+</mapper>
index 1ec976a4bea030ba931e2e3966abd5f5b5b43803..4c38d99c12aee80ec5505a17280b48bef83cbbe0 100644 (file)
     ORDER BY UPPER(m.short_name)
   </select>
 
-  <insert id="insert" parameterType="org.sonar.core.metric.db.MetricDto" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
+  <insert id="insert" parameterType="org.sonar.core.metric.db.MetricDto" useGeneratedKeys="true" keyColumn="id"
+          keyProperty="id">
     INSERT INTO metrics (
-      name, description, direction, domain, short_name, qualitative, val_type, user_managed, enabled, origin, worst_value, best_value, optimized_best_value, hidden, delete_historical_data)
+    name, description, direction, domain, short_name, qualitative, val_type, user_managed, enabled, origin, worst_value,
+    best_value, optimized_best_value, hidden, delete_historical_data)
     VALUES (
     #{name, jdbcType=VARCHAR}, #{description, jdbcType=VARCHAR}, #{direction, jdbcType=INTEGER},
     #{domain, jdbcType=VARCHAR}, #{shortName, jdbcType=VARCHAR}, #{qualitative, jdbcType=BOOLEAN},
     where m.domain is not null
   </select>
 
+  <update id="disable">
+    update metrics
+    set enabled=${_false}
+    <where>
+      AND user_managed=${_true}
+      AND id in
+      <foreach item="id" collection="ids" open="(" separator="," close=")">
+        #{id}
+      </foreach>
+    </where>
+  </update>
+
+  <select id="selectByKeys" resultType="org.sonar.core.metric.db.MetricDto">
+    SELECT
+    <include refid="metricColumns"/>
+    FROM metrics m
+    <where>
+      AND m.name in
+      <foreach item="key" collection="keys" open="(" separator="," close=")">
+        #{key}
+      </foreach>
+    </where>
+  </select>
+
 </mapper>