From bfb7923942f670bc95eec4623637e86f50ce5939 Mon Sep 17 00:00:00 2001 From: Julien Lancelot Date: Wed, 18 Jan 2017 12:37:08 +0100 Subject: [PATCH] SONAR-8574 Delete api/resources/index WS --- .../java/it/serverSystem/HttpHeadersTest.java | 2 +- .../server/component/ws/ResourcesWs.java | 84 +---- .../server/ws/RemovedWebServiceHandler.java | 41 +++ .../server/component/ws/ResourcesWsTest.java | 5 +- .../ws/RemovedWebServiceHandlerTest.java | 51 +++ .../controllers/api/resources_controller.rb | 339 ------------------ 6 files changed, 101 insertions(+), 421 deletions(-) create mode 100644 server/sonar-server/src/main/java/org/sonar/server/ws/RemovedWebServiceHandler.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/ws/RemovedWebServiceHandlerTest.java diff --git a/it/it-tests/src/test/java/it/serverSystem/HttpHeadersTest.java b/it/it-tests/src/test/java/it/serverSystem/HttpHeadersTest.java index 4910729c859..02f362bbfdf 100644 --- a/it/it-tests/src/test/java/it/serverSystem/HttpHeadersTest.java +++ b/it/it-tests/src/test/java/it/serverSystem/HttpHeadersTest.java @@ -69,7 +69,7 @@ public class HttpHeadersTest { @Test public void verify_headers_of_ruby_ws() throws Exception { - Response response = call(orchestrator.getServer().getUrl() + "/api/resources/index"); + Response response = call(orchestrator.getServer().getUrl() + "/api/resources/search"); verifySecurityHeaders(response); verifyContentType(response, "application/json;charset=utf-8"); diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/ws/ResourcesWs.java b/server/sonar-server/src/main/java/org/sonar/server/component/ws/ResourcesWs.java index c7b8b93d1c9..e25f41a1898 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/component/ws/ResourcesWs.java +++ b/server/sonar-server/src/main/java/org/sonar/server/component/ws/ResourcesWs.java @@ -22,8 +22,7 @@ package org.sonar.server.component.ws; import com.google.common.io.Resources; import org.sonar.api.server.ws.RailsHandler; import org.sonar.api.server.ws.WebService; - -import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001; +import org.sonar.server.ws.RemovedWebServiceHandler; public class ResourcesWs implements WebService { @@ -40,91 +39,18 @@ public class ResourcesWs implements WebService { } private void defineIndexAction(NewController controller) { - NewAction action = controller.createAction("index") - .setDescription("Gets a list of components. Requires Browse permission on resource.
" + - "The web service is deprecated and you're invited to use the alternatives: " + + controller.createAction("index") + .setDescription("The web service is removed and you're invited to use the alternatives: " + "" + - "When you provide one metric, the number of results is limited to 500. When several metrics are provided, the number of measures is limited to 10000. " + - "The number of components is limited to 500." + - "This is a known limitation and it won't be fixed. You're invited to use the alternatives suggested above.") + "") .setSince("2.10") .setDeprecatedSince("5.4") - .setHandler(RailsHandler.INSTANCE) + .setHandler(RemovedWebServiceHandler.INSTANCE) .setResponseExample(Resources.getResource(this.getClass(), "resources-example-index.json")); - - action.createParam("resource") - .setDescription("id or key of the resource") - .setExampleValue(KEY_PROJECT_EXAMPLE_001); - - action.createParam("metrics") - .setDescription("Comma-separated list of metric keys/ids. " + - "Load measures on selected metrics. If only one metric is set, then measures are ordered by value") - .setExampleValue("lines,blocker_violations"); - - action.createParam("depth") - .setDescription("Used only when resource is set:
" + - "") - .setDefaultValue("0") - .setExampleValue("-1"); - - action.createParam("scopes") - .setDescription("Comma-separated list of scopes:
" + - "") - .setExampleValue("PRJ,DIR"); - - action.createParam("qualifiers") - .setDescription("Comma-separated list of qualifiers:
" + - "") - .setExampleValue("TRK,BRC"); - - action.createParam("verbose") - .setDescription("Add some data to response") - .setBooleanPossibleValues() - .setDefaultValue(String.valueOf(false)); - - action.createParam("limit") - .setDescription("Limit the number of results. Only used if one metric, and only one, is set") - .setExampleValue("10"); - - action.createParam("includetrends") - .setDescription("Include period variations in response: add nodes <p*> for period variations") - .setDefaultValue(String.valueOf(false)) - .setBooleanPossibleValues(); - - action.createParam("includealerts") - .setDescription("Include alerts data: add nodes <alert> (ERROR, WARN, OK) and <alert_text>") - .setBooleanPossibleValues() - .setDefaultValue(String.valueOf(false)); - - action.createParam("rules") - .setDescription("Filter on rules: setting it to true will return rules id and rule name for measure having such info " + - "(such as 'blocker_violations', 'critical_violations', ..., 'new_blocker_violations', ...). Possible values: true | false | list of rule ids") - .setBooleanPossibleValues() - .setDefaultValue(String.valueOf(true)); - - RailsHandler.addFormatParam(action); } private void defineSearchAction(NewController controller) { diff --git a/server/sonar-server/src/main/java/org/sonar/server/ws/RemovedWebServiceHandler.java b/server/sonar-server/src/main/java/org/sonar/server/ws/RemovedWebServiceHandler.java new file mode 100644 index 00000000000..da2882c8f7c --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/ws/RemovedWebServiceHandler.java @@ -0,0 +1,41 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program 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. + * + * This program 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.ws; + +import org.sonar.api.server.ws.Request; +import org.sonar.api.server.ws.RequestHandler; +import org.sonar.api.server.ws.Response; +import org.sonar.server.exceptions.ServerException; + +import static java.net.HttpURLConnection.HTTP_GONE; + +/** + * Used to declare web services that are removed + */ +public enum RemovedWebServiceHandler implements RequestHandler { + + INSTANCE; + + @Override + public void handle(Request request, Response response) throws Exception { + throw new ServerException(HTTP_GONE, String.format("The web service '%s' doesn't exists anymore, please read its documentation to use alternatives", request.getPath())); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/ws/ResourcesWsTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/ws/ResourcesWsTest.java index e6530a01785..55c6dbdcc9d 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/component/ws/ResourcesWsTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/component/ws/ResourcesWsTest.java @@ -23,6 +23,7 @@ import org.junit.Before; import org.junit.Test; import org.sonar.api.server.ws.RailsHandler; import org.sonar.api.server.ws.WebService; +import org.sonar.server.ws.RemovedWebServiceHandler; import org.sonar.server.ws.WsTester; import static org.assertj.core.api.Assertions.assertThat; @@ -49,9 +50,9 @@ public class ResourcesWsTest { public void define_index_action() { WebService.Action action = controller.action("index"); assertThat(action).isNotNull(); - assertThat(action.handler()).isInstanceOf(RailsHandler.class); + assertThat(action.handler()).isInstanceOf(RemovedWebServiceHandler.class); assertThat(action.responseExampleAsString()).isNotEmpty(); - assertThat(action.params()).hasSize(11); + assertThat(action.params()).isEmpty(); } @Test diff --git a/server/sonar-server/src/test/java/org/sonar/server/ws/RemovedWebServiceHandlerTest.java b/server/sonar-server/src/test/java/org/sonar/server/ws/RemovedWebServiceHandlerTest.java new file mode 100644 index 00000000000..747747d4a33 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/ws/RemovedWebServiceHandlerTest.java @@ -0,0 +1,51 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program 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. + * + * This program 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.ws; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.server.ws.Request; +import org.sonar.server.exceptions.ServerException; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class RemovedWebServiceHandlerTest { + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + @Test + public void throw_server_exception() throws Exception { + Request request = mock(Request.class); + when(request.getPath()).thenReturn("/api/resources/index"); + + try { + RemovedWebServiceHandler.INSTANCE.handle(request, null); + fail(); + } catch (ServerException e) { + assertThat(e.getMessage()).isEqualTo("The web service '/api/resources/index' doesn't exists anymore, please read its documentation to use alternatives"); + assertThat(e.httpCode()).isEqualTo(410); + } + } +} diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/api/resources_controller.rb b/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/api/resources_controller.rb index 9fde244c889..b5f695e2743 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/api/resources_controller.rb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/api/resources_controller.rb @@ -99,343 +99,4 @@ class Api::ResourcesController < Api::ApiController end end - def index - begin - resource_id=params[:resource] - if resource_id - @resource=Project.by_key(resource_id) - @analysis=(@resource && @resource.last_analysis) - raise ApiException.new(404, "Resource [#{resource_id}] not found") if @analysis.nil? - raise ApiException.new(401, "Unauthorized") unless has_role?(:user, @resource) - else - @analysis=nil - if params['scopes'].blank? && params['qualifiers'].blank? - params['scopes']='PRJ' - params['qualifiers']='TRK' - end - end - - # ---------- PARAMETERS - components_conditions=['snapshots.islast=:islast'] - components_values={:islast => true} - - load_measures=false - measures_conditions=[] - measures_values={} - measures_order = nil - measures_limit = nil - measures_by_component_uuid={} - measures=nil - - if params['scopes'] - components_conditions << 'projects.scope in (:scopes)' - components_values[:scopes]=params['scopes'].split(',') - end - - if params['qualifiers'] - components_conditions << 'projects.qualifier in (:qualifiers)' - components_values[:qualifiers]=params['qualifiers'].split(',') - end - - if @analysis - depth=(params['depth'] ? params['depth'].to_i : 0) - if depth==0 - components_conditions << 'projects.uuid=:component_uuid' - components_values[:component_uuid]=@resource.uuid - - else - # negative : all the tree - components_conditions << 'projects.project_uuid = :project_uuid and projects.enabled=:enabled and (projects.uuid=:component_uuid OR projects.uuid_path LIKE :uuid_path)' - components_values[:component_uuid]=@resource.uuid - components_values[:project_uuid] = @resource.project_uuid - components_values[:enabled] = true - components_values[:uuid_path]="#{@resource.uuid_path}#{@resource.uuid}.%" - end - end - - if params['metrics'] && params['metrics']!='false' - load_measures=true - - if params['metrics']!='true' - metrics = Metric.by_keys(params[:metrics].split(',')) - # Derby does not accept "metric_id in (NULL)" - # The workaround is to use the unknown id -1 - if metrics.empty? - measures_conditions << 'project_measures.metric_id=-1' - else - measures_conditions << 'project_measures.metric_id IN (:metrics)' - measures_values[:metrics]=metrics.select { |m| m.id } - end - if metrics.size==1 - measures_limit = (params[:limit] ? [params[:limit].to_i, 500].min : 500) - measures_order = "project_measures.value #{'DESC' if metrics.first.direction<0}" - end - end - - measures_conditions << 'project_measures.person_id IS NULL' - - measures = ProjectMeasure.all(:joins => [:analysis, :project], - :select => select_columns_for_measures, - :conditions => [(components_conditions + measures_conditions).join(' AND '), components_values.merge(measures_values)], - :order => measures_order, - # SONAR-6584 avoid OOM errors - :limit => measures_limit ? measures_limit : 10000) - - measures.each do |measure| - measures_by_component_uuid[measure.component_uuid] ||= [] - measures_by_component_uuid[measure.component_uuid] << measure - end - - if measures_limit - components_conditions << 'projects.uuid IN (:component_uuids)' - components_values[:component_uuids] = measures_by_component_uuid.keys - end - - end - - # ---------- LOAD COMPONENTS - # H2 does not support empty lists, so short-breaking if no measures - if measures_limit && measures_by_component_uuid.empty? - components = [] - else - components = Project.all( - :include => :last_analysis, - :conditions => [components_conditions.join(' AND '), components_values], - # SONAR-6584 avoid OOM errors - :limit => 500) - end - - # ---------- APPLY SECURITY - remove unauthorized resources - only if no selected resource - if @resource.nil? - components = select_authorized(:user, components) - end - - # ---------- PREPARE RESPONSE - components_by_uuid = {} - components.each do |c| - components_by_uuid[c.uuid]=c - end - - - # ---------- SORT RESOURCES - if load_measures && measures_order && measures && !measures.empty? - # components are sorted by measures - sorted_components = measures.map do |measure| - components_by_uuid[measure.component_uuid] - end - else - # no specific sort - sorted_components = components - end - - sorted_components = sorted_components.uniq.compact - - # ---------- FORMAT RESPONSE - objects={:sorted_components => sorted_components, :components_by_uuid => components_by_uuid, :measures_by_component_uuid => measures_by_component_uuid, :params => params} - respond_to do |format| - format.json { render :json => jsonp(to_json(objects)) } - format.xml { render :xml => to_xml(objects) } - format.text { render :text => text_not_supported } - end - rescue ApiException => e - render_error(e.msg, e.code) - end - end - - private - - def select_columns_for_measures - select_columns='project_measures.id,project_measures.value,project_measures.metric_id,project_measures.component_uuid,project_measures.text_value,project_measures.measure_data' - if params[:includetrends]=='true' - select_columns+=',project_measures.variation_value_1,project_measures.variation_value_2,project_measures.variation_value_3,project_measures.variation_value_4,project_measures.variation_value_5' - end - if params[:includealerts]=='true' - select_columns+=',project_measures.alert_status,project_measures.alert_text' - end - if params[:includedescriptions]=='true' - select_columns+=',project_measures.url,project_measures.description' - end - select_columns - end - - def to_json(objects) - components = objects[:sorted_components] - components_by_uuid = objects[:components_by_uuid] - measures_by_component_uuid = objects[:measures_by_component_uuid] - params = objects[:params] - - result=[] - components.each do |component| - measures = measures_by_component_uuid[component.uuid] - result << component_to_json(component, measures, params) - end - result - end - - def to_xml(objects) - components = objects[:sorted_components] - components_by_uuid = objects[:components_by_uuid] - measures_by_component_uuid = objects[:measures_by_component_uuid] - params = objects[:params] - - xml = Builder::XmlMarkup.new(:indent => 0) - xml.instruct! - - xml.resources do - components.each do |component| - measures = measures_by_component_uuid[component.uuid] - component_to_xml(xml, component, measures, params) - end - end - end - - def component_to_json(component, measures, options={}) - verbose=(options[:verbose]=='true') - include_alerts=(options[:includealerts]=='true') - include_trends=(options[:includetrends]=='true') - include_descriptions=(options[:includedescriptions]=='true') - - json = { - 'id' => component.id, - 'uuid' => component.uuid, - 'key' => component.key, - 'uuid' => component.uuid, - 'name' => component.name, - 'scope' => component.scope, - 'qualifier' => component.qualifier, - 'creationDate' => Api::Utils.format_datetime(component.created_at)} - json['date'] = Api::Utils.format_datetime(component.last_analysis.created_at) if component.last_analysis - json['lname'] = component.long_name if component.long_name - json['lang']=component.language if component.language - json['version']= component.last_analysis.version if component.last_analysis && component.last_analysis.version - json['branch']=component.branch if component.branch - json['description']=component.description if component.description - if include_trends && component.last_analysis - json[:p1]=component.last_snapshot.period1_mode if component.last_analysis.period1_mode - json[:p1p]=component.last_analysis.period1_param if component.last_analysis.period1_param - json[:p1d]=Api::Utils.format_datetime(component.last_analysis.period1_date) if component.last_analysis.period1_date - - json[:p2]=component.last_analysis.period2_mode if component.last_analysis.period2_mode - json[:p2p]=component.last_analysis.period2_param if component.last_analysis.period2_param - json[:p2d]=Api::Utils.format_datetime(component.last_analysis.period2_date) if component.last_analysis.period2_date - - json[:p3]=component.last_analysis.period3_mode if component.last_analysis.period3_mode - json[:p3p]=component.last_analysis.period3_param if component.last_analysis.period3_param - json[:p3d]=Api::Utils.format_datetime(component.last_analysis.period3_date) if component.last_analysis.period3_date - - json[:p4]=component.last_analysis.period4_mode if component.last_analysis.period4_mode - json[:p4p]=component.last_analysis.period4_param if component.last_analysis.period4_param - json[:p4d]=Api::Utils.format_datetime(component.last_analysis.period4_date) if component.last_analysis.period4_date - - json[:p5]=component.last_analysis.period5_mode if component.last_analysis.period5_mode - json[:p5p]=component.last_analysis.period5_param if component.last_analysis.period5_param - json[:p5d]=Api::Utils.format_datetime(component.last_analysis.period5_date) if component.last_analysis.period5_date - end - if measures - json_measures=[] - json['msr']=json_measures - measures.select { |measure| !measure.metric.key.start_with?('new_') || include_trends }.each do |measure| - json_measure={} - json_measures<