import org.sonar.server.text.MacroInterpreter;
import org.sonar.server.text.RubyTextService;
import org.sonar.server.ui.PageDecorations;
+import org.sonar.server.ui.PageRepository;
import org.sonar.server.ui.Views;
import org.sonar.server.ui.ws.NavigationWsModule;
import org.sonar.server.updatecenter.UpdateCenterModule;
add(
PluginDownloader.class,
Views.class,
+ PageRepository.class,
ResourceTypes.class,
DefaultResourceTypes.get(),
SettingsChangeNotifier.class,
--- /dev/null
+/*
+ * 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.ui;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableList;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Consumer;
+import javax.annotation.Nullable;
+import org.sonar.api.Startable;
+import org.sonar.api.server.ServerSide;
+import org.sonar.api.web.page.Context;
+import org.sonar.api.web.page.Page;
+import org.sonar.api.web.page.Page.Qualifier;
+import org.sonar.api.web.page.Page.Scope;
+import org.sonar.api.web.page.PageDefinition;
+import org.sonar.core.platform.PluginRepository;
+import org.sonar.core.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkState;
+import static java.util.Collections.emptyList;
+import static java.util.Comparator.comparing;
+import static java.util.Objects.requireNonNull;
+import static org.sonar.api.web.page.Page.Scope.COMPONENT;
+import static org.sonar.api.web.page.Page.Scope.GLOBAL;
+
+@ServerSide
+public class PageRepository implements Startable {
+ private static final Splitter PAGE_KEY_SPLITTER = Splitter.on("/").omitEmptyStrings();
+
+ private final PluginRepository pluginRepository;
+ private final List<PageDefinition> definitions;
+ private List<Page> pages;
+
+ public PageRepository(PluginRepository pluginRepository) {
+ this.pluginRepository = pluginRepository;
+ // in case there's no page definition
+ this.definitions = Collections.emptyList();
+ }
+
+ public PageRepository(PluginRepository pluginRepository, PageDefinition[] pageDefinitions) {
+ this.pluginRepository = pluginRepository;
+ definitions = ImmutableList.copyOf(pageDefinitions);
+ }
+
+ private static Consumer<Page> checkWellFormed() {
+ return page -> {
+ boolean isWellFormed = PAGE_KEY_SPLITTER.splitToList(page.getKey()).size() == 2;
+ checkState(isWellFormed, "Page '%s' with key '%s' does not respect the format plugin_key/extension_point_key (ex: governance/project_dump)",
+ page.getName(), page.getKey());
+ };
+ }
+
+ @Override
+ public void start() {
+ Context context = new Context();
+ definitions.forEach(definition -> definition.define(context));
+ pages = context.getPages().stream()
+ .peek(checkWellFormed())
+ .peek(checkPluginExists())
+ .sorted(comparing(Page::getKey))
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public void stop() {
+ // nothing to do
+ }
+
+ public List<Page> getGlobalPages(boolean isAdmin) {
+ return getPages(GLOBAL, isAdmin, null);
+ }
+
+ public List<Page> getComponentPages(boolean isAdmin, String qualifierKey) {
+ Qualifier qualifier = Qualifier.fromKey(qualifierKey);
+ return qualifier == null ? emptyList() : getPages(COMPONENT, isAdmin, qualifier);
+ }
+
+ private List<Page> getPages(Scope scope, boolean isAdmin, @Nullable Qualifier qualifier) {
+ return getAllPages().stream()
+ .filter(p -> p.getScope().equals(scope))
+ .filter(p -> p.isAdmin() == isAdmin)
+ .filter(p -> GLOBAL.equals(p.getScope()) || p.getComponentQualifiers().contains(qualifier))
+ .collect(Collectors.toList());
+ }
+
+ @VisibleForTesting
+ List<Page> getAllPages() {
+ return requireNonNull(pages, "Pages haven't been initialized yet");
+ }
+
+ private Consumer<Page> checkPluginExists() {
+ return page -> {
+ String plugin = PAGE_KEY_SPLITTER.splitToList(page.getKey()).get(0);
+ boolean pluginExists = pluginRepository.hasPlugin(plugin);
+ checkState(pluginExists, "Page '%s' references plugin '%s' that does not exist", page.getName(), plugin);
+ };
+ }
+
+}
--- /dev/null
+/*
+ * 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.ui;
+
+import java.util.List;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.resources.Qualifiers;
+import org.sonar.api.utils.log.LogTester;
+import org.sonar.api.web.page.Page;
+import org.sonar.api.web.page.Page.Qualifier;
+import org.sonar.api.web.page.PageDefinition;
+import org.sonar.core.platform.PluginRepository;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.tuple;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.api.web.page.Page.Scope.COMPONENT;
+import static org.sonar.api.web.page.Page.Scope.GLOBAL;
+
+public class PageRepositoryTest {
+
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+ @Rule
+ public LogTester LOGGER = new LogTester();
+
+ private PluginRepository pluginRepository = mock(PluginRepository.class);
+
+ private PageRepository underTest = new PageRepository(pluginRepository);
+
+ @Before
+ public void setUp() {
+ when(pluginRepository.hasPlugin(anyString())).thenReturn(true);
+ }
+
+ @Test
+ public void pages_from_different_page_definitions_ordered_by_key() {
+ PageDefinition firstPlugin = context -> context
+ .addPage(Page.builder("my_plugin/K1").setName("N1").build())
+ .addPage(Page.builder("my_plugin/K3").setName("N3").build());
+ PageDefinition secondPlugin = context -> context.addPage(Page.builder("my_plugin/K2").setName("N2").build());
+ underTest = new PageRepository(pluginRepository, new PageDefinition[] {firstPlugin, secondPlugin});
+ underTest.start();
+
+ List<Page> result = underTest.getAllPages();
+
+ assertThat(result).extracting(Page::getKey, Page::getName)
+ .containsExactly(
+ tuple("my_plugin/K1", "N1"),
+ tuple("my_plugin/K2", "N2"),
+ tuple("my_plugin/K3", "N3"));
+ }
+
+ @Test
+ public void filter_by_navigation_and_qualifier() {
+ PageDefinition plugin = context -> context
+ // Default with GLOBAL navigation and no qualifiers
+ .addPage(Page.builder("my_plugin/K1").setName("K1").build())
+ .addPage(Page.builder("my_plugin/K2").setName("K2").setScope(COMPONENT).setComponentQualifiers(Qualifier.PROJECT).build())
+ .addPage(Page.builder("my_plugin/K3").setName("K3").setScope(COMPONENT).setComponentQualifiers(Qualifier.MODULE).build())
+ .addPage(Page.builder("my_plugin/K4").setName("K4").setScope(GLOBAL).build())
+ .addPage(Page.builder("my_plugin/K5").setName("K5").setScope(COMPONENT).setComponentQualifiers(Qualifier.VIEW).build());
+ underTest = new PageRepository(pluginRepository, new PageDefinition[] {plugin});
+ underTest.start();
+
+ List<Page> result = underTest.getComponentPages(false, Qualifiers.PROJECT);
+
+ assertThat(result).extracting(Page::getKey).containsExactly("my_plugin/K2");
+ }
+
+ @Test
+ public void empty_pages_if_no_page_definition() {
+ underTest.start();
+
+ List<Page> result = underTest.getAllPages();
+
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ public void filter_pages_without_qualifier() {
+ PageDefinition plugin = context -> context
+ .addPage(Page.builder("my_plugin/K1").setName("N1").build())
+ .addPage(Page.builder("my_plugin/K2").setName("N2").build())
+ .addPage(Page.builder("my_plugin/K3").setName("N3").build());
+ underTest = new PageRepository(pluginRepository, new PageDefinition[] {plugin});
+ underTest.start();
+
+ List<Page> result = underTest.getGlobalPages(false);
+
+ assertThat(result).extracting(Page::getKey).containsExactly("my_plugin/K1", "my_plugin/K2", "my_plugin/K3");
+ }
+
+ @Test
+ public void fail_if_pages_called_before_server_startup() {
+ expectedException.expect(NullPointerException.class);
+ expectedException.expectMessage("Pages haven't been initialized yet");
+
+ underTest.getAllPages();
+ }
+
+ @Test
+ public void fail_if_page_with_wrong_format() {
+ PageDefinition plugin = context -> context
+ .addPage(Page.builder("my_key").setName("N1").build())
+ .addPage(Page.builder("my_plugin/my_key").setName("N2").build());
+ underTest = new PageRepository(pluginRepository, new PageDefinition[] {plugin});
+
+ expectedException.expect(IllegalStateException.class);
+ expectedException.expectMessage("Page 'N1' with key 'my_key' does not respect the format plugin_key/extension_point_key (ex: governance/project_dump)");
+
+ underTest.start();
+ }
+
+ @Test
+ public void fail_if_page_with_unknown_plugin() {
+ PageDefinition governance = context -> context.addPage(Page.builder("governance/my_key").setName("N1").build());
+ PageDefinition plugin42 = context -> context.addPage(Page.builder("plugin_42/my_key").setName("N2").build());
+ pluginRepository = mock(PluginRepository.class);
+ when(pluginRepository.hasPlugin("governance")).thenReturn(true);
+ underTest = new PageRepository(pluginRepository, new PageDefinition[] {governance, plugin42});
+
+ expectedException.expect(IllegalStateException.class);
+ expectedException.expectMessage("Page 'N2' references plugin 'plugin_42' that does not exist");
+
+ underTest.start();
+ }
+}
--- /dev/null
+/*
+ * 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.api.web.page;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import static java.lang.String.format;
+import static java.util.Collections.unmodifiableCollection;
+
+/**
+ * @see PageDefinition
+ * @since 6.3
+ */
+public final class Context {
+ private final Map<String, Page> pagesByPath = new HashMap<>();
+
+ public Context addPage(Page page) {
+ Page existingPageWithSameKey = pagesByPath.putIfAbsent(page.getKey(), page);
+ if (existingPageWithSameKey != null) {
+ throw new IllegalArgumentException(format("Page '%s' cannot be loaded. Another page with key '%s' already exists.", page.getName(), page.getKey()));
+ }
+
+ return this;
+ }
+
+ public Collection<Page> getPages() {
+ return unmodifiableCollection(pagesByPath.values());
+ }
+}
--- /dev/null
+/*
+ * 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.api.web.page;
+
+import java.util.Arrays;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import javax.annotation.CheckForNull;
+
+import static java.lang.String.format;
+import static java.util.Collections.unmodifiableSet;
+import static java.util.Objects.requireNonNull;
+import static org.sonar.api.web.page.Page.Scope.COMPONENT;
+import static org.sonar.api.web.page.Page.Scope.GLOBAL;
+
+/**
+ * @see PageDefinition
+ * @since 6.3
+ */
+public final class Page {
+ private final String key;
+ private final String name;
+ private final boolean isAdmin;
+ private final Scope scope;
+ private final Set<Qualifier> qualifiers;
+
+ private Page(Builder builder) {
+ this.key = builder.key;
+ this.name = builder.name;
+ this.qualifiers = unmodifiableSet(Stream.of(builder.qualifiers).sorted().collect(Collectors.toSet()));
+ this.isAdmin = builder.isAdmin;
+ this.scope = builder.scope;
+ }
+
+ public static Builder builder(String key) {
+ return new Builder(key);
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Set<Qualifier> getComponentQualifiers() {
+ return qualifiers;
+ }
+
+ public boolean isAdmin() {
+ return isAdmin;
+ }
+
+ public Scope getScope() {
+ return scope;
+ }
+
+ public enum Scope {
+ GLOBAL, COMPONENT
+ }
+
+ public enum Qualifier {
+ PROJECT(org.sonar.api.resources.Qualifiers.PROJECT),
+ MODULE(org.sonar.api.resources.Qualifiers.MODULE),
+ VIEW(org.sonar.api.resources.Qualifiers.VIEW),
+ SUB_VIEW(org.sonar.api.resources.Qualifiers.SUBVIEW);
+
+ private final String key;
+
+ Qualifier(String key) {
+ this.key = key;
+ }
+
+ @CheckForNull
+ public static Qualifier fromKey(String key) {
+ return Arrays.stream(values())
+ .filter(qualifier -> qualifier.key.equals(key))
+ .findAny()
+ .orElse(null);
+ }
+
+ public String getKey() {
+ return key;
+ }
+ }
+
+ public static class Builder {
+ private final String key;
+ private String name;
+ private boolean isAdmin = false;
+ private Scope scope = GLOBAL;
+ private Qualifier[] qualifiers = new Qualifier[] {};
+
+ /**
+ * @param key It must respect the format plugin_key/page_identifier. Example: <code>my_plugin/my_page</code>
+ */
+ private Builder(String key) {
+ this.key = requireNonNull(key, "Key must not be null");
+ }
+
+ /**
+ * Page name displayed in the UI. Mandatory.
+ */
+ public Builder setName(String name) {
+ this.name = name;
+ return this;
+ }
+
+ /**
+ * if set to true, display the page in the administration section, depending on the scope
+ */
+ public Builder setAdmin(boolean admin) {
+ this.isAdmin = admin;
+ return this;
+ }
+
+ /**
+ * Define where the page is displayed, either in the global menu or in a component page
+ * @param scope - default is GLOBAL
+ */
+ public Builder setScope(Scope scope) {
+ this.scope = requireNonNull(scope, "Scope must not be null");
+ return this;
+ }
+
+ /**
+ * Define the components where the page is displayed. If set, {@link #setScope(Scope)} must be set to COMPONENT
+ * @see Qualifier
+ */
+ public Builder setComponentQualifiers(Qualifier... qualifiers) {
+ this.qualifiers = requireNonNull(qualifiers);
+ return this;
+ }
+
+ public Page build() {
+ if (key == null || key.isEmpty()) {
+ throw new IllegalArgumentException("Key must not be empty");
+ }
+ if (name == null || name.isEmpty()) {
+ throw new IllegalArgumentException("Name must be defined and not empty");
+ }
+ if (qualifiers.length > 0 && GLOBAL.equals(scope)) {
+ throw new IllegalArgumentException(format("The scope must be '%s' when component qualifiers are provided", COMPONENT));
+ }
+ if (qualifiers.length == 0 && COMPONENT.equals(scope)) {
+ qualifiers = Qualifier.values();
+ }
+
+ return new Page(this);
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.api.web.page;
+
+import org.sonar.api.ExtensionPoint;
+import org.sonar.api.server.ServerSide;
+
+/**
+ * Defines the Javascript pages added to SonarQube.
+ * <br>
+ * This interface replaces the deprecated class {@link org.sonar.api.web.Page}. Moreover, the technology changed from Ruby to Javascript
+ * <br>
+ * <h3>How to define pages</h3>
+ * <pre>
+ * import org.sonar.api.web.page.Page.Qualifier;
+ *
+ * public class MyPluginPagesDefinition implements PagesDefinition {
+ * {@literal @Override}
+ * public void define(Context context) {
+ * context
+ * // Global page by default
+ * .addPage(Page.builder("my_plugin/global_page").setName("Global Page").build())
+ * // Global admin page
+ * .addPage(Page.builder("my_plugin/global_admin_page").setName("Admin Global Page").setAdmin(true).build())
+ * // Project page
+ * .addPage(Page.builder("my_plugin/project_page").setName("Project Page").setScope(Scope.COMPONENT).setQualifiers(Qualifier.PROJECT).build())
+ * // Admin project or module page
+ * .addPage(Page.builder("my_plugin/project_admin_page").setName("Admin Page for Project or Module").setAdmin(true)
+ * .setScope(Scope.COMPONENT).setQualifiers(Qualifier.PROJECT, Qualifier.MODULE).build())
+ * // Page on all components (see Qualifier class) supported
+ * .addPage(Page.builder("my_plugin/component_page").setName("Component Page").setScope(Scope.COMPONENT).build());
+ * }
+ * }
+ * </pre>
+ * <h3>How to test a page definition</h3>
+ * <pre>
+ * public class PageDefinitionTest {
+ * {@literal @Test}
+ * public void test_page_definition() {
+ * PageDefinition underTest = context -> context.addPage(Page.builder("my_plugin/my_page").setName("My Page").build());
+ * Context context = new Context();
+ *
+ * underTest.define(context);
+ *
+ * assertThat(context.getPages()).extracting(Page::getKey).contains("my_plugin/my_page");
+ * }
+ * </pre>
+ *
+ * @since 6.3
+ */
+
+@ServerSide
+@ExtensionPoint
+public interface PageDefinition {
+ /**
+ * This method is executed when server is started
+ */
+ void define(Context context);
+}
--- /dev/null
+/*
+ * 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.
+ */
+
+@ParametersAreNonnullByDefault
+package org.sonar.api.web.page;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
--- /dev/null
+/*
+ * 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.api.web.page;
+
+import java.util.Collection;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.tuple;
+
+public class ContextTest {
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ private Context underTest = new Context();
+
+ private Page page = Page.builder("governance/project_export").setName("Project Export").build();
+
+ @Test
+ public void no_pages_with_the_same_path() {
+ underTest.addPage(page);
+
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Page 'Project Export' cannot be loaded. Another page with key 'governance/project_export' already exists.");
+
+ underTest.addPage(page);
+ }
+
+ @Test
+ public void ordered_by_name() {
+ underTest
+ .addPage(Page.builder("K1").setName("N2").build())
+ .addPage(Page.builder("K2").setName("N3").build())
+ .addPage(Page.builder("K3").setName("N1").build());
+
+ Collection<Page> result = underTest.getPages();
+
+ assertThat(result).extracting(Page::getKey, Page::getName)
+ .containsOnly(
+ tuple("K3", "N1"),
+ tuple("K1", "N2"),
+ tuple("K2", "N3"));
+ }
+
+ @Test
+ public void empty_pages_by_default() {
+ Collection<Page> result = underTest.getPages();
+
+ assertThat(result).isEmpty();
+ }
+
+}
--- /dev/null
+/*
+ * 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.api.web.page;
+
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Used for the documentation
+ */
+public class PageDefinitionTest {
+ @Test
+ public void test_page_definition() {
+ PageDefinition underTest = context -> context.addPage(Page.builder("my_plugin/my_page").setName("My Page").build());
+ Context context = new Context();
+
+ underTest.define(context);
+
+ assertThat(context.getPages()).extracting(Page::getKey).contains("my_plugin/my_page");
+ }
+
+}
--- /dev/null
+/*
+ * 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.api.web.page;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.resources.Qualifiers;
+import org.sonar.api.web.page.Page.Qualifier;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.api.web.page.Page.Qualifier.MODULE;
+import static org.sonar.api.web.page.Page.Qualifier.PROJECT;
+import static org.sonar.api.web.page.Page.Qualifier.SUB_VIEW;
+import static org.sonar.api.web.page.Page.Qualifier.VIEW;
+import static org.sonar.api.web.page.Page.Scope.COMPONENT;
+import static org.sonar.api.web.page.Page.Scope.GLOBAL;
+
+public class PageTest {
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ private Page.Builder underTest = Page.builder("governance/project_dump").setName("Project Dump");
+
+ @Test
+ public void full_test() {
+ Page result = underTest
+ .setComponentQualifiers(PROJECT, MODULE)
+ .setScope(COMPONENT)
+ .setAdmin(true)
+ .build();
+
+ assertThat(result.getKey()).isEqualTo("governance/project_dump");
+ assertThat(result.getName()).isEqualTo("Project Dump");
+ assertThat(result.getComponentQualifiers()).containsOnly(PROJECT, MODULE);
+ assertThat(result.getScope()).isEqualTo(COMPONENT);
+ assertThat(result.isAdmin()).isTrue();
+ }
+
+ @Test
+ public void qualifiers_map_to_key() {
+ assertThat(Qualifier.PROJECT.getKey()).isEqualTo(org.sonar.api.resources.Qualifiers.PROJECT);
+ assertThat(Qualifier.MODULE.getKey()).isEqualTo(org.sonar.api.resources.Qualifiers.MODULE);
+ assertThat(Qualifier.VIEW.getKey()).isEqualTo(org.sonar.api.resources.Qualifiers.VIEW);
+ assertThat(Qualifier.SUB_VIEW.getKey()).isEqualTo(org.sonar.api.resources.Qualifiers.SUBVIEW);
+ }
+
+ @Test
+ public void authorized_qualifiers() {
+ Qualifier[] qualifiers = Qualifier.values();
+
+ assertThat(qualifiers).hasSize(4).containsOnly(PROJECT, MODULE, VIEW, SUB_VIEW);
+ }
+
+ @Test
+ public void default_values() {
+ Page result = underTest.build();
+
+ assertThat(result.getComponentQualifiers()).isEmpty();
+ assertThat(result.getScope()).isEqualTo(GLOBAL);
+ assertThat(result.isAdmin()).isFalse();
+ }
+
+ @Test
+ public void all_qualifiers_when_component_page() {
+ Page result = underTest.setScope(COMPONENT).build();
+
+ assertThat(result.getComponentQualifiers()).containsOnly(Qualifier.values());
+ }
+
+ @Test
+ public void qualifiers_from_key() {
+ assertThat(Qualifier.fromKey(Qualifiers.PROJECT)).isEqualTo(Qualifier.PROJECT);
+ assertThat(Qualifier.fromKey("42")).isNull();
+ }
+
+ @Test
+ public void fail_if_no_qualifier() {
+ expectedException.expect(NullPointerException.class);
+
+ underTest.setComponentQualifiers(null).build();
+ }
+
+ @Test
+ public void fail_if_a_page_has_a_null_key() {
+ expectedException.expect(NullPointerException.class);
+
+ Page.builder(null).setName("Say my name").build();
+ }
+
+ @Test
+ public void fail_if_a_page_has_an_empty_key() {
+ expectedException.expect(IllegalArgumentException.class);
+
+ Page.builder("").setName("Say my name").build();
+ }
+
+ @Test
+ public void fail_if_a_page_has_a_null_name() {
+ expectedException.expect(IllegalArgumentException.class);
+
+ Page.builder("governance/project_dump").build();
+ }
+
+ @Test
+ public void fail_if_a_page_has_an_empty_name() {
+ expectedException.expect(IllegalArgumentException.class);
+
+ Page.builder("governance/project_dump").setName("").build();
+ }
+
+ @Test
+ public void fail_if_qualifiers_without_scope() {
+ expectedException.expect(IllegalArgumentException.class);
+
+ underTest.setComponentQualifiers(PROJECT).build();
+ }
+}