From: Sébastien Lesaint Date: Tue, 27 Sep 2016 15:32:37 +0000 (+0200) Subject: SONAR-8100 add DefaultOrganization to Pico container in CE and Web X-Git-Tag: 6.2-RC1~581 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=fe24831c5ba0d4d3e0896d9e6dc5d036ff058182;p=sonarqube.git SONAR-8100 add DefaultOrganization to Pico container in CE and Web includes a ThreadLocal cache to avoid multiple calls to DB for same information --- diff --git a/server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java b/server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java index 9796bfcd4dd..a09c4cb5589 100644 --- a/server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java +++ b/server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java @@ -95,6 +95,7 @@ import org.sonar.server.notification.NotificationCenter; import org.sonar.server.notification.NotificationService; import org.sonar.server.notification.email.AlertsEmailTemplate; import org.sonar.server.notification.email.EmailNotificationChannel; +import org.sonar.server.organization.DefaultOrganizationProviderImpl; import org.sonar.server.platform.DatabaseServerCompatibility; import org.sonar.server.platform.DefaultServerUpgradeStatus; import org.sonar.server.platform.ServerFileSystemImpl; @@ -270,7 +271,8 @@ public class ComputeEngineContainerImpl implements ComputeEngineContainer { new StartupMetadataProvider(), ServerIdManager.class, UriReader.class, - ServerImpl.class + ServerImpl.class, + DefaultOrganizationProviderImpl.class }; } diff --git a/server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java b/server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java index b027d5e4cae..a3a33b4f636 100644 --- a/server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java +++ b/server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java @@ -97,7 +97,7 @@ public class ComputeEngineContainerImplTest { ); assertThat(picoContainer.getParent().getComponentAdapters()).hasSize( CONTAINER_ITSELF - + 4 // level 3 + + 5 // level 3 ); assertThat(picoContainer.getParent().getParent().getComponentAdapters()).hasSize( CONTAINER_ITSELF diff --git a/server/sonar-server/src/main/java/org/sonar/ce/organization/DefaultOrganizationLoader.java b/server/sonar-server/src/main/java/org/sonar/ce/organization/DefaultOrganizationLoader.java new file mode 100644 index 00000000000..8af0ffffcee --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/ce/organization/DefaultOrganizationLoader.java @@ -0,0 +1,45 @@ +/* + * 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.ce.organization; + +import org.picocontainer.Startable; +import org.sonar.api.ce.ComputeEngineSide; +import org.sonar.server.computation.task.container.EagerStart; +import org.sonar.server.organization.DefaultOrganizationCache; + +@EagerStart +@ComputeEngineSide +public class DefaultOrganizationLoader implements Startable { + private final DefaultOrganizationCache defaultOrganizationCache; + + public DefaultOrganizationLoader(DefaultOrganizationCache defaultOrganizationCache) { + this.defaultOrganizationCache = defaultOrganizationCache; + } + + @Override + public void start() { + defaultOrganizationCache.load(); + } + + @Override + public void stop() { + defaultOrganizationCache.unload(); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/ce/organization/package-info.java b/server/sonar-server/src/main/java/org/sonar/ce/organization/package-info.java new file mode 100644 index 00000000000..48eb2840ff4 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/ce/organization/package-info.java @@ -0,0 +1,23 @@ +/* + * 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.ce.organization; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/container/ProjectAnalysisTaskContainerPopulator.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/container/ProjectAnalysisTaskContainerPopulator.java index 5f455576977..78eeeed0b9e 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/container/ProjectAnalysisTaskContainerPopulator.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/container/ProjectAnalysisTaskContainerPopulator.java @@ -22,19 +22,21 @@ package org.sonar.server.computation.task.projectanalysis.container; import java.util.Arrays; import java.util.List; import javax.annotation.Nullable; +import org.sonar.ce.organization.DefaultOrganizationLoader; import org.sonar.ce.queue.CeTask; import org.sonar.ce.settings.SettingsLoader; import org.sonar.core.issue.tracking.Tracker; import org.sonar.core.platform.ContainerPopulator; import org.sonar.plugin.ce.ReportAnalysisComponentProvider; +import org.sonar.server.computation.task.container.TaskContainer; import org.sonar.server.computation.task.projectanalysis.analysis.AnalysisMetadataHolderImpl; +import org.sonar.server.computation.task.projectanalysis.api.posttask.PostProjectAnalysisTasksExecutor; import org.sonar.server.computation.task.projectanalysis.batch.BatchReportDirectoryHolderImpl; import org.sonar.server.computation.task.projectanalysis.batch.BatchReportReaderImpl; import org.sonar.server.computation.task.projectanalysis.component.DbIdsRepositoryImpl; import org.sonar.server.computation.task.projectanalysis.component.DisabledComponentsHolderImpl; import org.sonar.server.computation.task.projectanalysis.component.SettingsRepositoryImpl; import org.sonar.server.computation.task.projectanalysis.component.TreeRootHolderImpl; -import org.sonar.server.computation.task.container.TaskContainer; import org.sonar.server.computation.task.projectanalysis.duplication.CrossProjectDuplicationStatusHolderImpl; import org.sonar.server.computation.task.projectanalysis.duplication.DuplicationRepositoryImpl; import org.sonar.server.computation.task.projectanalysis.duplication.IntegrateCrossProjectDuplications; @@ -84,7 +86,6 @@ import org.sonar.server.computation.task.projectanalysis.measure.MeasureReposito import org.sonar.server.computation.task.projectanalysis.measure.MeasureToMeasureDto; import org.sonar.server.computation.task.projectanalysis.metric.MetricModule; import org.sonar.server.computation.task.projectanalysis.period.PeriodsHolderImpl; -import org.sonar.server.computation.task.projectanalysis.api.posttask.PostProjectAnalysisTasksExecutor; import org.sonar.server.computation.task.projectanalysis.qualitygate.EvaluationResultTextConverterImpl; import org.sonar.server.computation.task.projectanalysis.qualitygate.QualityGateHolderImpl; import org.sonar.server.computation.task.projectanalysis.qualitygate.QualityGateServiceImpl; @@ -97,9 +98,9 @@ import org.sonar.server.computation.task.projectanalysis.scm.ScmInfoRepositoryIm import org.sonar.server.computation.task.projectanalysis.source.LastCommitVisitor; import org.sonar.server.computation.task.projectanalysis.source.SourceHashRepositoryImpl; import org.sonar.server.computation.task.projectanalysis.source.SourceLinesRepositoryImpl; +import org.sonar.server.computation.task.projectanalysis.step.ReportComputationSteps; import org.sonar.server.computation.task.step.ComputationStepExecutor; import org.sonar.server.computation.task.step.ComputationSteps; -import org.sonar.server.computation.task.projectanalysis.step.ReportComputationSteps; import org.sonar.server.computation.taskprocessor.MutableTaskResultHolderImpl; import org.sonar.server.view.index.ViewIndex; @@ -118,6 +119,7 @@ public final class ProjectAnalysisTaskContainerPopulator implements ContainerPop public void populateContainer(TaskContainer container) { ComputationSteps steps = new ReportComputationSteps(container); container.add(SettingsLoader.class); + container.add(DefaultOrganizationLoader.class); container.add(task); container.add(steps); container.addSingletons(componentClasses()); diff --git a/server/sonar-server/src/main/java/org/sonar/server/organization/DefaultOrganization.java b/server/sonar-server/src/main/java/org/sonar/server/organization/DefaultOrganization.java new file mode 100644 index 00000000000..566f5ae932e --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/organization/DefaultOrganization.java @@ -0,0 +1,110 @@ +/* + * 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.organization; + +import static java.util.Objects.requireNonNull; + +public class DefaultOrganization { + private final String uuid; + private final String key; + private final String name; + private final long createdAt; + private final long updatedAt; + + private DefaultOrganization(Builder builder) { + this.uuid = requireNonNull(builder.uuid, "uuid can't be null"); + this.key = requireNonNull(builder.key, "key can't be null"); + this.name = requireNonNull(builder.name, "name can't be null"); + this.createdAt = requireNonNull(builder.createdAt, "createdAt can't be null"); + this.updatedAt = requireNonNull(builder.updatedAt, "updatedAt can't be null"); + } + + public static Builder newBuilder() { + return new Builder(); + } + + public String getUuid() { + return uuid; + } + + public String getKey() { + return key; + } + + public String getName() { + return name; + } + + public long getCreatedAt() { + return createdAt; + } + + public long getUpdatedAt() { + return updatedAt; + } + + @Override + public String toString() { + return "DefaultOrganization{" + + "uuid='" + uuid + '\'' + + ", key='" + key + '\'' + + ", name='" + name + '\'' + + ", createdAt=" + createdAt + + ", updatedAt=" + updatedAt + + '}'; + } + + public static final class Builder { + private String uuid; + private String key; + private String name; + private Long createdAt; + private Long updatedAt; + + public Builder setUuid(String uuid) { + this.uuid = uuid; + return this; + } + + public Builder setKey(String key) { + this.key = key; + return this; + } + + public Builder setName(String name) { + this.name = name; + return this; + } + + public Builder setCreatedAt(long createdAt) { + this.createdAt = createdAt; + return this; + } + + public Builder setUpdatedAt(long updatedAt) { + this.updatedAt = updatedAt; + return this; + } + + public DefaultOrganization build() { + return new DefaultOrganization(this); + } + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/organization/DefaultOrganizationCache.java b/server/sonar-server/src/main/java/org/sonar/server/organization/DefaultOrganizationCache.java new file mode 100644 index 00000000000..0f017d8649b --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/organization/DefaultOrganizationCache.java @@ -0,0 +1,33 @@ +/* + * 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.organization; + +public interface DefaultOrganizationCache { + + /** + * Loads {@link DefaultOrganization} in cache. + */ + void load(); + + /** + * Unloads {@link DefaultOrganization} from cache. + */ + void unload(); +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/organization/DefaultOrganizationProvider.java b/server/sonar-server/src/main/java/org/sonar/server/organization/DefaultOrganizationProvider.java new file mode 100644 index 00000000000..3073809f804 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/organization/DefaultOrganizationProvider.java @@ -0,0 +1,28 @@ +/* + * 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.organization; + +public interface DefaultOrganizationProvider { + /** + * @throws IllegalStateException if there is no default organization + */ + DefaultOrganization get(); + +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/organization/DefaultOrganizationProviderImpl.java b/server/sonar-server/src/main/java/org/sonar/server/organization/DefaultOrganizationProviderImpl.java new file mode 100644 index 00000000000..80f7c72939a --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/organization/DefaultOrganizationProviderImpl.java @@ -0,0 +1,97 @@ +/* + * 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.organization; + +import java.util.Optional; +import java.util.function.Supplier; +import javax.annotation.CheckForNull; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.organization.OrganizationDto; +import org.sonar.server.property.InternalProperties; + +import static com.google.common.base.Preconditions.checkState; + +public class DefaultOrganizationProviderImpl implements DefaultOrganizationProvider, DefaultOrganizationCache { + private static final ThreadLocal CACHE = new ThreadLocal<>(); + + private final DbClient dbClient; + + public DefaultOrganizationProviderImpl(DbClient dbClient) { + this.dbClient = dbClient; + } + + @Override + public DefaultOrganization get() { + Cache cache = CACHE.get(); + if (cache != null) { + return cache.get(() -> getDefaultOrganization(dbClient)); + } + + return getDefaultOrganization(dbClient); + } + + private static DefaultOrganization getDefaultOrganization(DbClient dbClient) { + try (DbSession dbSession = dbClient.openSession(false)) { + Optional uuid = dbClient.internalPropertiesDao().selectByKey(dbSession, InternalProperties.DEFAULT_ORGANIZATION); + checkState(uuid.isPresent() && !uuid.get().isEmpty(), "No Default organization uuid configured"); + Optional dto = dbClient.organizationDao().selectByUuid(dbSession, uuid.get()); + checkState(dto.isPresent(), "Default organization with uuid '%s' does not exist", uuid.get()); + return toDefaultOrganization(dto.get()); + } + } + + private static DefaultOrganization toDefaultOrganization(OrganizationDto organizationDto) { + return DefaultOrganization.newBuilder() + .setUuid(organizationDto.getUuid()) + .setKey(organizationDto.getKey()) + .setName(organizationDto.getName()) + .setCreatedAt(organizationDto.getCreatedAt()) + .setUpdatedAt(organizationDto.getUpdatedAt()) + .build(); + } + + @Override + public void load() { + checkState( + CACHE.get() == null, + "load called twice for thread '%s' or state wasn't cleared last time it was used", + Thread.currentThread().getName()); + CACHE.set(new Cache()); + } + + @Override + public void unload() { + CACHE.remove(); + } + + private static final class Cache { + @CheckForNull + private DefaultOrganization defaultOrganization; + + public DefaultOrganization get(Supplier supplier) { + if (defaultOrganization == null) { + defaultOrganization = supplier.get(); + } + return defaultOrganization; + } + + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel3.java b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel3.java index 0d46bb9eaf9..88447785412 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel3.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel3.java @@ -21,6 +21,7 @@ package org.sonar.server.platform.platformlevel; import org.sonar.api.utils.UriReader; import org.sonar.core.util.DefaultHttpDownloader; +import org.sonar.server.organization.DefaultOrganizationProviderImpl; import org.sonar.server.platform.ServerIdGenerator; import org.sonar.server.platform.ServerIdLoader; import org.sonar.server.platform.ServerIdManager; @@ -47,6 +48,7 @@ public class PlatformLevel3 extends PlatformLevel { ServerIdLoader.class, ServerIdGenerator.class, LogServerId.class, - DefaultHttpDownloader.class); + DefaultHttpDownloader.class, + DefaultOrganizationProviderImpl.class); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/UserSessionFilter.java b/server/sonar-server/src/main/java/org/sonar/server/user/UserSessionFilter.java index 535641746e9..73ca6d740d4 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/user/UserSessionFilter.java +++ b/server/sonar-server/src/main/java/org/sonar/server/user/UserSessionFilter.java @@ -21,6 +21,7 @@ package org.sonar.server.user; import com.google.common.annotations.VisibleForTesting; import java.io.IOException; +import javax.annotation.Nullable; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; @@ -30,11 +31,11 @@ import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.sonar.server.authentication.UserSessionInitializer; +import org.sonar.server.organization.DefaultOrganizationCache; import org.sonar.server.platform.Platform; import org.sonar.server.setting.ThreadLocalSettings; public class UserSessionFilter implements Filter { - private final Platform platform; public UserSessionFilter() { @@ -52,18 +53,32 @@ public class UserSessionFilter implements Filter { HttpServletResponse response = (HttpServletResponse) servletResponse; ThreadLocalSettings settings = platform.getContainer().getComponentByType(ThreadLocalSettings.class); + DefaultOrganizationCache defaultOrganizationCache = platform.getContainer().getComponentByType(DefaultOrganizationCache.class); UserSessionInitializer userSessionInitializer = platform.getContainer().getComponentByType(UserSessionInitializer.class); - settings.load(); + defaultOrganizationCache.load(); + try { + settings.load(); + try { + doFilter(request, response, chain, userSessionInitializer); + } finally { + settings.unload(); + } + } finally { + defaultOrganizationCache.unload(); + } + } + + private static void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain, + @Nullable UserSessionInitializer userSessionInitializer) throws IOException, ServletException { try { if (userSessionInitializer == null || userSessionInitializer.initUserSession(request, response)) { - chain.doFilter(servletRequest, servletResponse); + chain.doFilter(request, response); } } finally { if (userSessionInitializer != null) { userSessionInitializer.removeUserSession(); } - settings.unload(); } } diff --git a/server/sonar-server/src/test/java/org/sonar/ce/organization/DefaultOrganizationLoaderTest.java b/server/sonar-server/src/test/java/org/sonar/ce/organization/DefaultOrganizationLoaderTest.java new file mode 100644 index 00000000000..2d01e4ca5ac --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/ce/organization/DefaultOrganizationLoaderTest.java @@ -0,0 +1,48 @@ +/* + * 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.ce.organization; + +import org.junit.Test; +import org.sonar.server.organization.DefaultOrganizationCache; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +public class DefaultOrganizationLoaderTest { + private DefaultOrganizationCache defaultOrganizationCache = mock(DefaultOrganizationCache.class); + private DefaultOrganizationLoader underTest = new DefaultOrganizationLoader(defaultOrganizationCache); + + @Test + public void start_calls_cache_load_method() { + underTest.start(); + + verify(defaultOrganizationCache).load(); + verifyNoMoreInteractions(defaultOrganizationCache); + } + + @Test + public void stop_calls_cache_unload_method() { + underTest.stop(); + + verify(defaultOrganizationCache).unload(); + verifyNoMoreInteractions(defaultOrganizationCache); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/organization/DefaultOrganizationProviderImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/organization/DefaultOrganizationProviderImplTest.java new file mode 100644 index 00000000000..ff71285c89b --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/organization/DefaultOrganizationProviderImplTest.java @@ -0,0 +1,185 @@ +/* + * 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.organization; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.utils.System2; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.DbTester; +import org.sonar.db.organization.OrganizationDto; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.sonar.server.property.InternalProperties.DEFAULT_ORGANIZATION; + +public class DefaultOrganizationProviderImplTest { + private static final OrganizationDto ORGANIZATION_DTO_1 = new OrganizationDto() + .setUuid("uuid1") + .setName("the name of 1") + .setKey("the key 1"); + private static final long DATE_1 = 1_999_888L; + + private System2 system2 = mock(System2.class); + + @Rule + public DbTester dbTester = DbTester.create(system2); + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private DbClient dbClient = dbTester.getDbClient(); + private DbSession dbSession = dbTester.getSession(); + + private DefaultOrganizationProviderImpl underTest = new DefaultOrganizationProviderImpl(dbClient); + + @Test + public void get_fails_with_ISE_if_default_organization_internal_property_does_not_exist() { + expectISENoDefaultOrganizationUuid(); + + underTest.get(); + } + + @Test + public void get_fails_with_ISE_if_default_organization_internal_property_is_empty() { + dbClient.internalPropertiesDao().saveAsEmpty(dbSession, DEFAULT_ORGANIZATION); + dbSession.commit(); + + expectISENoDefaultOrganizationUuid(); + + underTest.get(); + } + + private void expectISENoDefaultOrganizationUuid() { + expectedException.expect(IllegalStateException.class); + expectedException.expectMessage("No Default organization uuid configured"); + } + + @Test + public void get_fails_with_ISE_if_default_organization_does_not_exist() { + dbClient.internalPropertiesDao().save(dbSession, DEFAULT_ORGANIZATION, "bla"); + dbSession.commit(); + + expectedException.expect(IllegalStateException.class); + expectedException.expectMessage("Default organization with uuid 'bla' does not exist"); + + underTest.get(); + } + + @Test + public void get_returns_DefaultOrganization_populated_from_DB() { + insertOrganization(ORGANIZATION_DTO_1, DATE_1); + dbClient.internalPropertiesDao().save(dbSession, DEFAULT_ORGANIZATION, ORGANIZATION_DTO_1.getUuid()); + dbSession.commit(); + + DefaultOrganization defaultOrganization = underTest.get(); + assertThat(defaultOrganization.getUuid()).isEqualTo(ORGANIZATION_DTO_1.getUuid()); + assertThat(defaultOrganization.getKey()).isEqualTo(ORGANIZATION_DTO_1.getKey()); + assertThat(defaultOrganization.getName()).isEqualTo(ORGANIZATION_DTO_1.getName()); + assertThat(defaultOrganization.getCreatedAt()).isEqualTo(DATE_1); + assertThat(defaultOrganization.getUpdatedAt()).isEqualTo(DATE_1); + } + + @Test + public void get_returns_new_DefaultOrganization_with_each_call_when_cache_is_not_loaded() { + insertOrganization(ORGANIZATION_DTO_1, DATE_1); + dbClient.internalPropertiesDao().save(dbSession, DEFAULT_ORGANIZATION, ORGANIZATION_DTO_1.getUuid()); + dbSession.commit(); + + assertThat(underTest.get()).isNotSameAs(underTest.get()); + } + + @Test + public void unload_does_not_fail_if_load_has_not_been_called() { + underTest.unload(); + } + + @Test + public void load_fails_with_ISE_when_called_twice_without_unload_in_between() { + underTest.load(); + + try { + underTest.load(); + fail("A IllegalStateException should have been raised"); + } catch (IllegalStateException e) { + assertThat(e).hasMessage("load called twice for thread '" + Thread.currentThread().getName() + "' or state wasn't cleared last time it was used"); + } finally { + underTest.unload(); + } + } + + @Test + public void load_and_unload_cache_DefaultOrganization_object_by_thread() throws InterruptedException { + insertOrganization(ORGANIZATION_DTO_1, DATE_1); + dbClient.internalPropertiesDao().save(dbSession, DEFAULT_ORGANIZATION, ORGANIZATION_DTO_1.getUuid()); + dbSession.commit(); + + try { + underTest.load(); + + DefaultOrganization cachedForThread1 = underTest.get(); + assertThat(cachedForThread1).isSameAs(underTest.get()); + + Thread otherThread = new Thread(() -> { + try { + underTest.load(); + + assertThat(underTest.get()) + .isNotSameAs(cachedForThread1) + .isSameAs(underTest.get()); + } finally { + underTest.unload(); + } + }); + otherThread.start(); + otherThread.join(); + } finally { + underTest.unload(); + } + } + + @Test + public void get_returns_new_instance_for_each_call_once_unload_has_been_called() { + insertOrganization(ORGANIZATION_DTO_1, DATE_1); + dbClient.internalPropertiesDao().save(dbSession, DEFAULT_ORGANIZATION, ORGANIZATION_DTO_1.getUuid()); + dbSession.commit(); + + try { + underTest.load(); + DefaultOrganization cached = underTest.get(); + assertThat(cached).isSameAs(underTest.get()); + + underTest.unload(); + assertThat(underTest.get()).isNotSameAs(underTest.get()).isNotSameAs(cached); + } finally { + // fail safe + underTest.unload(); + } + } + + private void insertOrganization(OrganizationDto dto, long createdAt) { + when(system2.now()).thenReturn(createdAt); + dbClient.organizationDao().insert(dbSession, dto); + dbSession.commit(); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/organization/DefaultOrganizationProviderRule.java b/server/sonar-server/src/test/java/org/sonar/server/organization/DefaultOrganizationProviderRule.java new file mode 100644 index 00000000000..7f07855140a --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/organization/DefaultOrganizationProviderRule.java @@ -0,0 +1,70 @@ +/* + * 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.organization; + +import com.google.common.base.Preconditions; +import org.junit.rules.ExternalResource; +import org.sonar.core.util.UuidFactoryImpl; + +public class DefaultOrganizationProviderRule extends ExternalResource implements DefaultOrganizationProvider { + private DefaultOrganization defaultOrganization; + + private DefaultOrganizationProviderRule(DefaultOrganization defaultOrganization) { + this.defaultOrganization = defaultOrganization; + } + + /** + *

+ * This method is meant to be statically imported. + *

+ */ + public static DefaultOrganizationProviderRule someDefaultOrganization() { + String uuid = UuidFactoryImpl.INSTANCE.create(); + return new DefaultOrganizationProviderRule(DefaultOrganization.newBuilder() + .setUuid(uuid) + .setName("Default organization " + uuid) + .setKey(uuid + "_key") + .setCreatedAt(uuid.hashCode()) + .setUpdatedAt(uuid.hashCode()) + .build()); + } + + /** + *

+ * This method is meant to be statically imported. + *

+ */ + public static DefaultOrganizationProviderRule defaultOrganizationWithName(String name) { + String uuid = UuidFactoryImpl.INSTANCE.create(); + return new DefaultOrganizationProviderRule(DefaultOrganization.newBuilder() + .setUuid(uuid) + .setName(name) + .setKey(uuid + "_key") + .setCreatedAt(uuid.hashCode()) + .setUpdatedAt(uuid.hashCode()) + .build()); + } + + @Override + public DefaultOrganization get() { + Preconditions.checkState(defaultOrganization != null, "No default organization is set"); + return defaultOrganization; + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/organization/DefaultOrganizationTest.java b/server/sonar-server/src/test/java/org/sonar/server/organization/DefaultOrganizationTest.java new file mode 100644 index 00000000000..90f825d13f6 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/organization/DefaultOrganizationTest.java @@ -0,0 +1,116 @@ +/* + * 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.organization; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import static org.assertj.core.api.Assertions.assertThat; + +public class DefaultOrganizationTest { + private static final long DATE_2 = 2_000_000L; + private static final long DATE_1 = 1_000_000L; + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private DefaultOrganization.Builder populatedBuilder = new DefaultOrganization.Builder() + .setUuid("uuid") + .setKey("key") + .setName("name") + .setCreatedAt(DATE_1) + .setUpdatedAt(DATE_2); + + @Test + public void build_fails_if_uuid_is_null() { + populatedBuilder.setUuid(null); + + expectedException.expect(NullPointerException.class); + expectedException.expectMessage("uuid can't be null"); + + populatedBuilder.build(); + } + + @Test + public void build_fails_if_key_is_null() { + populatedBuilder.setKey(null); + + expectedException.expect(NullPointerException.class); + expectedException.expectMessage("key can't be null"); + + populatedBuilder.build(); + } + + @Test + public void build_fails_if_name_is_null() { + populatedBuilder.setName(null); + + expectedException.expect(NullPointerException.class); + expectedException.expectMessage("name can't be null"); + + populatedBuilder.build(); + } + + @Test + public void build_fails_if_createdAt_not_set() { + DefaultOrganization.Builder underTest = new DefaultOrganization.Builder() + .setUuid("uuid") + .setKey("key") + .setName("name") + .setUpdatedAt(DATE_2); + + expectedException.expect(NullPointerException.class); + expectedException.expectMessage("createdAt can't be null"); + + underTest.build(); + } + + @Test + public void build_fails_if_updatedAt_not_set() { + DefaultOrganization.Builder underTest = new DefaultOrganization.Builder() + .setUuid("uuid") + .setKey("key") + .setName("name") + .setCreatedAt(DATE_1); + + expectedException.expect(NullPointerException.class); + expectedException.expectMessage("updatedAt can't be null"); + + underTest.build(); + } + + @Test + public void verify_toString() { + assertThat(populatedBuilder.build().toString()) + .isEqualTo("DefaultOrganization{uuid='uuid', key='key', name='name', createdAt=1000000, updatedAt=2000000}"); + } + + @Test + public void verify_getters() { + DefaultOrganization underTest = populatedBuilder.build(); + + assertThat(underTest.getUuid()).isEqualTo("uuid"); + assertThat(underTest.getKey()).isEqualTo("key"); + assertThat(underTest.getName()).isEqualTo("name"); + assertThat(underTest.getCreatedAt()).isEqualTo(DATE_1); + assertThat(underTest.getUpdatedAt()).isEqualTo(DATE_2); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/user/UserSessionFilterTest.java b/server/sonar-server/src/test/java/org/sonar/server/user/UserSessionFilterTest.java index 6e6810a596a..3768bfc388b 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/user/UserSessionFilterTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/user/UserSessionFilterTest.java @@ -27,11 +27,17 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.junit.Before; import org.junit.Test; +import org.mockito.InOrder; import org.mockito.Mockito; import org.sonar.server.authentication.UserSessionInitializer; +import org.sonar.server.organization.DefaultOrganizationCache; import org.sonar.server.platform.Platform; import org.sonar.server.setting.ThreadLocalSettings; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -46,17 +52,18 @@ public class UserSessionFilterTest { private HttpServletResponse response = mock(HttpServletResponse.class); private FilterChain chain = mock(FilterChain.class); private ThreadLocalSettings settings = mock(ThreadLocalSettings.class); + private DefaultOrganizationCache defaultOrganizationCache = mock(DefaultOrganizationCache.class); private UserSessionFilter underTest = new UserSessionFilter(platform); @Before public void setUp() { when(platform.getContainer().getComponentByType(ThreadLocalSettings.class)).thenReturn(settings); + when(platform.getContainer().getComponentByType(DefaultOrganizationCache.class)).thenReturn(defaultOrganizationCache); } @Test public void cleanup_user_session_after_request_handling() throws IOException, ServletException { - when(platform.getContainer().getComponentByType(UserSessionInitializer.class)).thenReturn(userSessionInitializer); - when(userSessionInitializer.initUserSession(request, response)).thenReturn(true); + mockUserSessionInitializer(true); underTest.doFilter(request, response, chain); @@ -66,8 +73,7 @@ public class UserSessionFilterTest { @Test public void stop_when_user_session_return_false() throws Exception { - when(platform.getContainer().getComponentByType(UserSessionInitializer.class)).thenReturn(userSessionInitializer); - when(userSessionInitializer.initUserSession(request, response)).thenReturn(false); + mockUserSessionInitializer(false); underTest.doFilter(request, response, chain); @@ -77,7 +83,7 @@ public class UserSessionFilterTest { @Test public void does_nothing_when_not_initialized() throws Exception { - when(platform.getContainer().getComponentByType(UserSessionInitializer.class)).thenReturn(null); + mockNoUserSessionInitializer(); underTest.doFilter(request, response, chain); @@ -85,6 +91,118 @@ public class UserSessionFilterTest { verifyZeroInteractions(userSessionInitializer); } + @Test + public void doFilter_loads_and_unloads_settings() throws Exception { + mockNoUserSessionInitializer(); + + underTest.doFilter(request, response, chain); + + InOrder inOrder = inOrder(settings); + inOrder.verify(settings).load(); + inOrder.verify(settings).unload(); + inOrder.verifyNoMoreInteractions(); + } + + @Test + public void doFilter_unloads_Settings_even_if_chain_throws_exception() throws Exception { + mockNoUserSessionInitializer(); + RuntimeException thrown = mockChainDoFilterError(); + + try { + underTest.doFilter(request, response, chain); + fail("A RuntimeException should have been thrown"); + } catch (RuntimeException e) { + assertThat(e).isSameAs(thrown); + verify(settings).unload(); + } + } + + @Test + public void doFilter_unloads_Settings_even_if_DefaultOrganizationCache_unload_fails() throws Exception { + mockNoUserSessionInitializer(); + RuntimeException thrown = new RuntimeException("Faking DefaultOrganizationCache.unload failing"); + doThrow(thrown) + .when(defaultOrganizationCache) + .unload(); + + try { + underTest.doFilter(request, response, chain); + fail("A RuntimeException should have been thrown"); + } catch (RuntimeException e) { + assertThat(e).isSameAs(thrown); + verify(settings).unload(); + } + } + + @Test + public void doFilter_unloads_Settings_even_if_UserSessionInitializer_removeUserSession_fails() throws Exception { + RuntimeException thrown = mockUserSessionInitializerRemoveUserSessionFailing(); + + try { + underTest.doFilter(request, response, chain); + fail("A RuntimeException should have been thrown"); + } catch (RuntimeException e) { + assertThat(e).isSameAs(thrown); + verify(settings).unload(); + } + } + + @Test + public void doFilter_loads_and_unloads_DefaultOrganizationCache() throws Exception { + mockNoUserSessionInitializer(); + + underTest.doFilter(request, response, chain); + + InOrder inOrder = inOrder(defaultOrganizationCache); + inOrder.verify(defaultOrganizationCache).load(); + inOrder.verify(defaultOrganizationCache).unload(); + inOrder.verifyNoMoreInteractions(); + } + + @Test + public void doFilter_unloads_DefaultOrganizationCache_even_if_chain_throws_exception() throws Exception { + mockNoUserSessionInitializer(); + RuntimeException thrown = mockChainDoFilterError(); + + try { + underTest.doFilter(request, response, chain); + fail("A RuntimeException should have been thrown"); + } catch (RuntimeException e) { + assertThat(e).isSameAs(thrown); + verify(defaultOrganizationCache).unload(); + } + } + + @Test + public void doFilter_unloads_DefaultOrganizationCache_even_if_Settings_unload_fails() throws Exception { + mockNoUserSessionInitializer(); + RuntimeException thrown = new RuntimeException("Faking Settings.unload failing"); + doThrow(thrown) + .when(settings) + .unload(); + + try { + underTest.doFilter(request, response, chain); + fail("A RuntimeException should have been thrown"); + } catch (RuntimeException e) { + assertThat(e).isSameAs(thrown); + verify(defaultOrganizationCache).unload(); + } + } + + @Test + public void doFilter_unloads_DefaultOrganizationCache_even_if_UserSessionInitializer_removeUserSession_fails() throws Exception { + RuntimeException thrown = mockUserSessionInitializerRemoveUserSessionFailing(); + + try { + underTest.doFilter(request, response, chain); + fail("A RuntimeException should have been thrown"); + } catch (RuntimeException e) { + assertThat(e).isSameAs(thrown); + verify(defaultOrganizationCache).unload(); + } + } + @Test public void just_for_fun_and_coverage() throws ServletException { UserSessionFilter filter = new UserSessionFilter(); @@ -92,4 +210,30 @@ public class UserSessionFilterTest { filter.destroy(); // do not fail } + + private void mockNoUserSessionInitializer() { + when(platform.getContainer().getComponentByType(UserSessionInitializer.class)).thenReturn(null); + } + + private void mockUserSessionInitializer(boolean value) { + when(platform.getContainer().getComponentByType(UserSessionInitializer.class)).thenReturn(userSessionInitializer); + when(userSessionInitializer.initUserSession(request, response)).thenReturn(value); + } + + private RuntimeException mockUserSessionInitializerRemoveUserSessionFailing() { + when(platform.getContainer().getComponentByType(UserSessionInitializer.class)).thenReturn(userSessionInitializer); + RuntimeException thrown = new RuntimeException("Faking UserSessionInitializer.removeUserSession failing"); + doThrow(thrown) + .when(userSessionInitializer) + .removeUserSession(); + return thrown; + } + + private RuntimeException mockChainDoFilterError() throws IOException, ServletException { + RuntimeException thrown = new RuntimeException("Faking chain.doFilter failing"); + doThrow(thrown) + .when(chain) + .doFilter(request, response); + return thrown; + } }