diff options
author | Sébastien Lesaint <sebastien.lesaint@sonarsource.com> | 2019-08-14 12:46:39 +0200 |
---|---|---|
committer | SonarTech <sonartech@sonarsource.com> | 2019-08-14 20:21:16 +0200 |
commit | 314809259ca1540d1e015582d2263daebfa5f3a5 (patch) | |
tree | cb0e2992664442d521dd66d9b4e584d5dee192e4 /server/sonar-webserver-api | |
parent | 23e09ca2d312815ddc9c05cd83ca584670250fd5 (diff) | |
download | sonarqube-314809259ca1540d1e015582d2263daebfa5f3a5.tar.gz sonarqube-314809259ca1540d1e015582d2263daebfa5f3a5.zip |
rename sonar-webserver-common to sonar-webserver-api
Diffstat (limited to 'server/sonar-webserver-api')
149 files changed, 8882 insertions, 0 deletions
diff --git a/server/sonar-webserver-api/build.gradle b/server/sonar-webserver-api/build.gradle new file mode 100644 index 00000000000..9c89a4b7de4 --- /dev/null +++ b/server/sonar-webserver-api/build.gradle @@ -0,0 +1,58 @@ +description = 'SonarQube WebServer internal APIs, used by other Web Server modules or Core Extensions' + +sonarqube { + properties { + property 'sonar.projectName', "${projectTitle} :: WebServer :: API" + } +} + +sourceSets { + test { + resources { + srcDirs += ['src/test/projects'] + } + } +} + +configurations { + tests + + testCompile.extendsFrom tests +} + +dependencies { + // please keep the list grouped by configuration and ordered by name + + compile 'com.google.guava:guava' + compile 'io.jsonwebtoken:jjwt-api' + compile 'io.jsonwebtoken:jjwt-impl' + compile project(':sonar-core') + compile project(':server:sonar-db-dao') + compile project(':server:sonar-process') + compile project(':server:sonar-server-common') + compile project(path: ':sonar-plugin-api', configuration: 'shadow') + compile project(':sonar-plugin-api-impl') + compile 'org.mindrot:jbcrypt' + + compileOnly 'com.google.code.findbugs:jsr305' + compileOnly 'javax.servlet:javax.servlet-api' + + testCompile 'org.assertj:assertj-guava' + testCompile 'com.google.code.findbugs:jsr305' + testCompile 'com.tngtech.java:junit-dataprovider' + testCompile 'javax.servlet:javax.servlet-api' + testCompile 'org.mockito:mockito-core' + testCompile project(':server:sonar-db-testing') + testCompile project(path: ":server:sonar-server-common", configuration: "tests") + testCompile project(path: ":server:sonar-webserver-ws", configuration: "tests") + testCompile project(':sonar-testing-harness') +} + +task testJar(type: Jar) { + classifier = 'tests' + from sourceSets.test.output +} + +artifacts { + tests testJar +} diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/app/ProcessCommandWrapper.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/app/ProcessCommandWrapper.java new file mode 100644 index 00000000000..44bdc4377e3 --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/app/ProcessCommandWrapper.java @@ -0,0 +1,43 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.app; + +public interface ProcessCommandWrapper { + /** + * Requests to the main process that SQ be restarted. + */ + void requestSQRestart(); + + /** + * Requests to the main process that the WebServer is stopped. + */ + void requestHardStop(); + + /** + * Notifies any listening process that the WebServer is operational. + */ + void notifyOperational(); + + /** + * Checks whether the Compute Engine is operational. + */ + boolean isCeOperational(); + +} diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/app/ProcessCommandWrapperImpl.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/app/ProcessCommandWrapperImpl.java new file mode 100644 index 00000000000..12b3f68543c --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/app/ProcessCommandWrapperImpl.java @@ -0,0 +1,95 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.app; + +import java.io.File; +import org.sonar.api.config.Configuration; +import org.sonar.process.ProcessId; +import org.sonar.process.sharedmemoryfile.DefaultProcessCommands; +import org.sonar.process.sharedmemoryfile.ProcessCommands; + +import static org.sonar.process.ProcessEntryPoint.PROPERTY_PROCESS_INDEX; +import static org.sonar.process.ProcessEntryPoint.PROPERTY_SHARED_PATH; + +public class ProcessCommandWrapperImpl implements ProcessCommandWrapper { + + private static final ProcessMethod<Void> SET_OPERATIONAL = processCommands -> { + processCommands.setOperational(); + return null; + }; + private static final ProcessMethod<Void> ASK_FOR_RESTART = processCommands -> { + processCommands.askForRestart(); + return null; + }; + private static final ProcessMethod<Void> ASK_FOR_HARD_STOP = processCommands -> { + processCommands.askForHardStop(); + return null; + }; + private static final ProcessMethod<Boolean> IS_OPERATIONAL = ProcessCommands::isOperational; + + private final Configuration config; + + public ProcessCommandWrapperImpl(Configuration config) { + this.config = config; + } + + @Override + public void requestSQRestart() { + call(ASK_FOR_RESTART, selfProcessNumber()); + } + + @Override + public void requestHardStop() { + call(ASK_FOR_HARD_STOP, selfProcessNumber()); + } + + @Override + public void notifyOperational() { + call(SET_OPERATIONAL, selfProcessNumber()); + } + + @Override + public boolean isCeOperational() { + return call(IS_OPERATIONAL, ProcessId.COMPUTE_ENGINE.getIpcIndex()); + } + + private int selfProcessNumber() { + return nonNullAsInt(PROPERTY_PROCESS_INDEX); + } + + private <T> T call(ProcessMethod<T> command, int processNumber) { + File shareDir = nonNullValueAsFile(PROPERTY_SHARED_PATH); + try (DefaultProcessCommands commands = DefaultProcessCommands.secondary(shareDir, processNumber)) { + return command.callOn(commands); + } + } + + private interface ProcessMethod<T> { + T callOn(ProcessCommands processCommands); + } + + private int nonNullAsInt(String key) { + return config.getInt(key).orElseThrow(() -> new IllegalArgumentException(String.format("Property %s is not set", key))); + } + + private File nonNullValueAsFile(String key) { + return new File(config.get(key).orElseThrow(() -> new IllegalArgumentException(String.format("Property %s is not set", key)))); + } +} diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/app/RestartFlagHolder.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/app/RestartFlagHolder.java new file mode 100644 index 00000000000..dfc6c8c8414 --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/app/RestartFlagHolder.java @@ -0,0 +1,42 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.app; + +/** + * Holds a boolean flag representing the restarting status of the WebServer. + * This boolean is {@code false} by default and can safely be changed concurrently using methods {@link #set()} and + * {@link #unset()}. + */ +public interface RestartFlagHolder { + /** + * @return whether restarting flag has been set or not. + */ + boolean isRestarting(); + + /** + * Sets the restarting flag to {@code true}, no matter it already is or not. + */ + void set(); + + /** + * Sets the restarting flag to {@code false}, no matter it already is or not. + */ + void unset(); +} diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/app/RestartFlagHolderImpl.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/app/RestartFlagHolderImpl.java new file mode 100644 index 00000000000..98354a8e77b --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/app/RestartFlagHolderImpl.java @@ -0,0 +1,41 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.app; + +import java.util.concurrent.atomic.AtomicBoolean; + +public class RestartFlagHolderImpl implements RestartFlagHolder { + private final AtomicBoolean restarting = new AtomicBoolean(false); + + @Override + public boolean isRestarting() { + return restarting.get(); + } + + @Override + public void set() { + restarting.set(true); + } + + @Override + public void unset() { + restarting.set(false); + } +} diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/app/package-info.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/app/package-info.java new file mode 100644 index 00000000000..7170de2498d --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/app/package-info.java @@ -0,0 +1,24 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.server.app; + +import javax.annotation.ParametersAreNonnullByDefault; + diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/branch/BranchFeature.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/branch/BranchFeature.java new file mode 100644 index 00000000000..7d60949c954 --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/branch/BranchFeature.java @@ -0,0 +1,26 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.branch; + +interface BranchFeature { + + boolean isEnabled(); + +} diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/branch/BranchFeatureExtension.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/branch/BranchFeatureExtension.java new file mode 100644 index 00000000000..20fc40ff324 --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/branch/BranchFeatureExtension.java @@ -0,0 +1,30 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.branch; + +import org.sonar.api.server.ServerSide; + +/** + * The branch plugin needs to implement this in order to know that the branch feature is supported + */ +@ServerSide +public interface BranchFeatureExtension extends BranchFeature { + +} diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/branch/BranchFeatureProxy.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/branch/BranchFeatureProxy.java new file mode 100644 index 00000000000..8647d7c421f --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/branch/BranchFeatureProxy.java @@ -0,0 +1,29 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.branch; + +/** + * The goal of this class is to handle the 2 different use case : + * - The branch plugin exists, the proxy will redirect method calls to the plugin + * - No branch plugin, feature is disabled + */ +public interface BranchFeatureProxy extends BranchFeature { + +} diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/branch/BranchFeatureProxyImpl.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/branch/BranchFeatureProxyImpl.java new file mode 100644 index 00000000000..634e59721a7 --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/branch/BranchFeatureProxyImpl.java @@ -0,0 +1,38 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.branch; + +public class BranchFeatureProxyImpl implements BranchFeatureProxy { + + private final BranchFeatureExtension branchFeatureExtension; + + public BranchFeatureProxyImpl() { + this.branchFeatureExtension = null; + } + + public BranchFeatureProxyImpl(BranchFeatureExtension branchFeatureExtension) { + this.branchFeatureExtension = branchFeatureExtension; + } + + @Override + public boolean isEnabled() { + return branchFeatureExtension != null && branchFeatureExtension.isEnabled(); + } +} diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/branch/package-info.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/branch/package-info.java new file mode 100644 index 00000000000..cb79dbcf31d --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/branch/package-info.java @@ -0,0 +1,23 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.server.branch; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/ce/http/CeHttpClient.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/ce/http/CeHttpClient.java new file mode 100644 index 00000000000..f86a08efb62 --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/ce/http/CeHttpClient.java @@ -0,0 +1,30 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.ce.http; + +import java.util.Optional; +import org.sonar.api.utils.log.LoggerLevel; +import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo; + +public interface CeHttpClient { + Optional<ProtobufSystemInfo.SystemInfo> retrieveSystemInfo(); + + void changeLogLevel(LoggerLevel level); +} diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/ce/http/package-info.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/ce/http/package-info.java new file mode 100644 index 00000000000..17b36c59125 --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/ce/http/package-info.java @@ -0,0 +1,23 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.server.ce.http; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/exceptions/BadRequestException.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/exceptions/BadRequestException.java new file mode 100644 index 00000000000..3113d202d99 --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/exceptions/BadRequestException.java @@ -0,0 +1,75 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.exceptions; + +import com.google.common.base.MoreObjects; +import java.util.List; + +import static com.google.common.base.Preconditions.checkArgument; +import static java.lang.String.format; +import static java.net.HttpURLConnection.HTTP_BAD_REQUEST; +import static java.util.Arrays.asList; + +/** + * Request is not valid and can not be processed. + */ +public class BadRequestException extends ServerException { + + private final transient List<String> errors; + + private BadRequestException(List<String> errors) { + super(HTTP_BAD_REQUEST, errors.get(0)); + this.errors = errors; + } + + public static void checkRequest(boolean expression, String message, Object... messageArguments) { + if (!expression) { + throw create(format(message, messageArguments)); + } + } + + public static void checkRequest(boolean expression, List<String> messages) { + if (!expression) { + throw create(messages); + } + } + + public static BadRequestException create(List<String> errorMessages) { + checkArgument(!errorMessages.isEmpty(), "At least one error message is required"); + checkArgument(errorMessages.stream().noneMatch(message -> message == null || message.isEmpty()), "Message cannot be empty"); + return new BadRequestException(errorMessages); + } + + public static BadRequestException create(String... errorMessages) { + return create(asList(errorMessages)); + } + + public List<String> errors() { + return errors; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("errors", errors) + .toString(); + } + +} diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/exceptions/ForbiddenException.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/exceptions/ForbiddenException.java new file mode 100644 index 00000000000..d72eefbd02f --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/exceptions/ForbiddenException.java @@ -0,0 +1,34 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.exceptions; + +import com.google.common.base.Preconditions; + +import static java.net.HttpURLConnection.HTTP_FORBIDDEN; + +/** + * Permission denied. User does not have the required permissions. + */ +public class ForbiddenException extends ServerException { + + public ForbiddenException(String message) { + super(HTTP_FORBIDDEN, Preconditions.checkNotNull(message)); + } +} diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/exceptions/Message.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/exceptions/Message.java new file mode 100644 index 00000000000..c069ead73d2 --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/exceptions/Message.java @@ -0,0 +1,66 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.exceptions; + +import com.google.common.base.Preconditions; + +import static com.google.common.base.Strings.isNullOrEmpty; +import static java.lang.String.format; + +public class Message { + + private final String msg; + + private Message(String format, Object... params) { + Preconditions.checkArgument(!isNullOrEmpty(format), "Message cannot be empty"); + this.msg = format(format, params); + } + + public String getMessage() { + return msg; + } + + public static Message of(String msg, Object... arguments) { + return new Message(msg, arguments); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + Message other = (Message) o; + return this.msg.equals(other.msg); + } + + @Override + public int hashCode() { + return msg.hashCode(); + } + + @Override + public String toString() { + return msg; + } +} diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/exceptions/NotFoundException.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/exceptions/NotFoundException.java new file mode 100644 index 00000000000..f21a98b5157 --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/exceptions/NotFoundException.java @@ -0,0 +1,65 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.exceptions; + +import com.google.common.base.Optional; +import javax.annotation.Nullable; + +import static java.lang.String.format; +import static java.net.HttpURLConnection.HTTP_NOT_FOUND; + +public class NotFoundException extends ServerException { + + public NotFoundException(String message) { + super(HTTP_NOT_FOUND, message); + } + + /** + * @throws NotFoundException if the value if null + * @return the value + */ + public static <T> T checkFound(@Nullable T value, String message, Object... messageArguments) { + if (value == null) { + throw new NotFoundException(format(message, messageArguments)); + } + + return value; + } + + /** + * @throws NotFoundException if the value is not present + * @return the value + */ + public static <T> T checkFoundWithOptional(Optional<T> value, String message, Object... messageArguments) { + if (!value.isPresent()) { + throw new NotFoundException(format(message, messageArguments)); + } + + return value.get(); + } + + public static <T> T checkFoundWithOptional(java.util.Optional<T> value, String message, Object... messageArguments) { + if (!value.isPresent()) { + throw new NotFoundException(format(message, messageArguments)); + } + + return value.get(); + } +} diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/exceptions/ServerException.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/exceptions/ServerException.java new file mode 100644 index 00000000000..491c17ed437 --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/exceptions/ServerException.java @@ -0,0 +1,35 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.exceptions; + +import static java.util.Objects.requireNonNull; + +public class ServerException extends RuntimeException { + private final int httpCode; + + public ServerException(int httpCode, String message) { + super(requireNonNull(message, "Error message cannot be null")); + this.httpCode = httpCode; + } + + public int httpCode() { + return httpCode; + } +} diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/exceptions/UnauthorizedException.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/exceptions/UnauthorizedException.java new file mode 100644 index 00000000000..0b4af12beee --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/exceptions/UnauthorizedException.java @@ -0,0 +1,32 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.exceptions; + +import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED; + +/** + * User needs to be authenticated. HTTP request is generally redirected to login form. + */ +public class UnauthorizedException extends ServerException { + + public UnauthorizedException(String message) { + super(HTTP_UNAUTHORIZED, message); + } +} diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/exceptions/package-info.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/exceptions/package-info.java new file mode 100644 index 00000000000..c1ac144f25a --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/exceptions/package-info.java @@ -0,0 +1,24 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.server.exceptions; + +import javax.annotation.ParametersAreNonnullByDefault; + diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/health/ClusterHealth.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/health/ClusterHealth.java new file mode 100644 index 00000000000..23fcfca9f7a --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/health/ClusterHealth.java @@ -0,0 +1,78 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.health; + +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import org.sonar.process.cluster.health.NodeHealth; + +import static com.google.common.collect.ImmutableSet.copyOf; +import static java.util.Objects.requireNonNull; + +public class ClusterHealth { + private final Health health; + private final Set<NodeHealth> nodes; + + public ClusterHealth(Health health, Set<NodeHealth> nodes) { + this.health = requireNonNull(health, "health can't be null"); + this.nodes = copyOf(requireNonNull(nodes, "nodes can't be null")); + } + + public Health getHealth() { + return health; + } + + public Set<NodeHealth> getNodes() { + return nodes; + } + + public Optional<NodeHealth> getNodeHealth(String nodeName) { + return nodes.stream() + .filter(node -> nodeName.equals(node.getDetails().getName())) + .findFirst(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ClusterHealth that = (ClusterHealth) o; + return Objects.equals(health, that.health) && + Objects.equals(nodes, that.nodes); + } + + @Override + public int hashCode() { + return Objects.hash(health, nodes); + } + + @Override + public String toString() { + return "ClusterHealth{" + + "health=" + health + + ", nodes=" + nodes + + '}'; + } +} diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/health/Health.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/health/Health.java new file mode 100644 index 00000000000..d5b49449bbe --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/health/Health.java @@ -0,0 +1,136 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.health; + +import com.google.common.collect.ImmutableSet; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +import static com.google.common.base.Preconditions.checkArgument; +import static java.util.Objects.requireNonNull; + +public class Health { + /** + * The GREEN status without any cause as a constant, for convenience and optimisation. + */ + public static final Health GREEN = newHealthCheckBuilder() + .setStatus(Status.GREEN) + .build(); + + private final Status status; + private final Set<String> causes; + + public Health(Builder builder) { + this.status = builder.status; + this.causes = ImmutableSet.copyOf(builder.causes); + } + + public Status getStatus() { + return status; + } + + public Set<String> getCauses() { + return causes; + } + + public static Builder newHealthCheckBuilder() { + return new Builder(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Health health = (Health) o; + return status == health.status && + Objects.equals(causes, health.causes); + } + + @Override + public int hashCode() { + return Objects.hash(status, causes); + } + + @Override + public String toString() { + return "Health{" + status + + ", causes=" + causes + + '}'; + } + + /** + * Builder of {@link Health} which supports being reused for optimization. + */ + public static class Builder { + private Status status; + private Set<String> causes = new HashSet<>(0); + + private Builder() { + // use static factory method + } + + public Builder clear() { + this.status = null; + this.causes.clear(); + return this; + } + + public Builder setStatus(Status status) { + this.status = checkStatus(status); + return this; + } + + public Builder addCause(String cause) { + requireNonNull(cause, "cause can't be null"); + checkArgument(!cause.trim().isEmpty(), "cause can't be empty"); + causes.add(cause); + return this; + } + + public Health build() { + checkStatus(this.status); + return new Health(this); + } + + private static Status checkStatus(Status status) { + return requireNonNull(status, "status can't be null"); + } + } + + public enum Status { + /** + * Fully working + */ + GREEN, + /** + * Yellow: Working but something must be fixed to make SQ fully operational + */ + YELLOW, + /** + * Red: Not working + */ + RED + } +} diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/health/HealthChecker.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/health/HealthChecker.java new file mode 100644 index 00000000000..80fb8aed1bf --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/health/HealthChecker.java @@ -0,0 +1,35 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.health; + +public interface HealthChecker { + /** + * Perform a check of the health of the current SonarQube node, either as a standalone node or as a member + * of a cluster. + */ + Health checkNode(); + + /** + * Perform a check of the health of the SonarQube cluster. + * + * @throws IllegalStateException if clustering is not enabled. + */ + ClusterHealth checkCluster(); +} diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/health/package-info.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/health/package-info.java new file mode 100644 index 00000000000..18ea762c56b --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/health/package-info.java @@ -0,0 +1,23 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.server.health; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/platform/ClusterFeature.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/platform/ClusterFeature.java new file mode 100644 index 00000000000..24d8617a4e1 --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/platform/ClusterFeature.java @@ -0,0 +1,31 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.platform; + +import org.sonar.api.ExtensionPoint; +import org.sonar.api.server.ServerSide; + +@ServerSide +@ExtensionPoint +public interface ClusterFeature { + + boolean isEnabled(); + +} diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/platform/Platform.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/platform/Platform.java new file mode 100644 index 00000000000..22f3e451a97 --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/platform/Platform.java @@ -0,0 +1,34 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.platform; + +import org.sonar.core.platform.ComponentContainer; + +public interface Platform { + void doStart(); + + Status status(); + + ComponentContainer getContainer(); + + enum Status { + BOOTING, SAFEMODE, STARTING, UP + } +} diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/platform/SystemInfoWriter.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/platform/SystemInfoWriter.java new file mode 100644 index 00000000000..3a82f471f6a --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/platform/SystemInfoWriter.java @@ -0,0 +1,26 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.platform; + +import org.sonar.api.utils.text.JsonWriter; + +public interface SystemInfoWriter { + void write(JsonWriter json) throws Exception; +} diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/platform/package-info.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/platform/package-info.java new file mode 100644 index 00000000000..71d187381a7 --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/platform/package-info.java @@ -0,0 +1,23 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.server.platform; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/InstalledPlugin.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/InstalledPlugin.java new file mode 100644 index 00000000000..0fffb7b7d78 --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/InstalledPlugin.java @@ -0,0 +1,81 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.plugins; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; +import org.apache.commons.codec.digest.DigestUtils; +import org.apache.commons.io.FileUtils; +import org.sonar.core.platform.PluginInfo; + +import static java.util.Objects.requireNonNull; + +@Immutable +public class InstalledPlugin { + private final PluginInfo plugin; + private final FileAndMd5 loadedJar; + @Nullable + private final FileAndMd5 compressedJar; + + public InstalledPlugin(PluginInfo plugin, FileAndMd5 loadedJar, @Nullable FileAndMd5 compressedJar) { + this.plugin = requireNonNull(plugin); + this.loadedJar = requireNonNull(loadedJar); + this.compressedJar = compressedJar; + } + + public PluginInfo getPluginInfo() { + return plugin; + } + + public FileAndMd5 getLoadedJar() { + return loadedJar; + } + + @Nullable + public FileAndMd5 getCompressedJar() { + return compressedJar; + } + + @Immutable + public static final class FileAndMd5 { + private final File file; + private final String md5; + + public FileAndMd5(File file) { + try (InputStream fis = FileUtils.openInputStream(file)) { + this.file = file; + this.md5 = DigestUtils.md5Hex(fis); + } catch (IOException e) { + throw new IllegalStateException("Fail to compute md5 of " + file, e); + } + } + + public File getFile() { + return file; + } + + public String getMd5() { + return md5; + } + } +} diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/PluginDownloader.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/PluginDownloader.java new file mode 100644 index 00000000000..d61efac304c --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/PluginDownloader.java @@ -0,0 +1,165 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.plugins; + +import com.google.common.base.Optional; +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import org.apache.commons.io.FileUtils; +import org.picocontainer.Startable; +import org.sonar.api.utils.HttpDownloader; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; +import org.sonar.core.platform.PluginInfo; +import org.sonar.core.util.stream.MoreCollectors; +import org.sonar.server.platform.ServerFileSystem; +import org.sonar.updatecenter.common.Release; +import org.sonar.updatecenter.common.UpdateCenter; +import org.sonar.updatecenter.common.Version; + +import static org.apache.commons.io.FileUtils.copyFile; +import static org.apache.commons.io.FileUtils.copyFileToDirectory; +import static org.apache.commons.io.FileUtils.forceMkdir; +import static org.apache.commons.io.FileUtils.toFile; +import static org.apache.commons.lang.StringUtils.substringAfterLast; +import static org.sonar.core.util.FileUtils.deleteQuietly; +import static org.sonar.server.exceptions.BadRequestException.checkRequest; + +/** + * Downloads plugins from update center. Files are copied in the directory extensions/downloads and then + * moved to extensions/plugins after server restart. + */ +public class PluginDownloader implements Startable { + + private static final Logger LOG = Loggers.get(PluginDownloader.class); + private static final String TMP_SUFFIX = "tmp"; + private static final String PLUGIN_EXTENSION = "jar"; + + private final UpdateCenterMatrixFactory updateCenterMatrixFactory; + private final HttpDownloader downloader; + private final File downloadDir; + + public PluginDownloader(UpdateCenterMatrixFactory updateCenterMatrixFactory, HttpDownloader downloader, + ServerFileSystem fileSystem) { + this.updateCenterMatrixFactory = updateCenterMatrixFactory; + this.downloader = downloader; + this.downloadDir = fileSystem.getDownloadedPluginsDir(); + } + + /** + * Deletes the temporary files remaining from previous downloads + */ + @Override + public void start() { + try { + forceMkdir(downloadDir); + for (File tempFile : listTempFile(this.downloadDir)) { + deleteQuietly(tempFile); + } + } catch (IOException e) { + throw new IllegalStateException("Fail to create the directory: " + downloadDir, e); + } + } + + @Override + public void stop() { + // Nothing to do + } + + public void cancelDownloads() { + try { + if (downloadDir.exists()) { + org.sonar.core.util.FileUtils.cleanDirectory(downloadDir); + } + } catch (IOException e) { + throw new IllegalStateException("Fail to clean the plugin downloads directory: " + downloadDir, e); + } + } + + public List<String> getDownloadedPluginFilenames() { + List<String> names = new ArrayList<>(); + for (File file : listPlugins(this.downloadDir)) { + names.add(file.getName()); + } + return names; + } + + /** + * @return the list of download plugins as {@link PluginInfo} instances + */ + public Collection<PluginInfo> getDownloadedPlugins() { + return listPlugins(this.downloadDir) + .stream() + .map(PluginInfo::create) + .collect(MoreCollectors.toList()); + } + + public void download(String pluginKey, Version version) { + Optional<UpdateCenter> updateCenter = updateCenterMatrixFactory.getUpdateCenter(true); + if (updateCenter.isPresent()) { + List<Release> installablePlugins = updateCenter.get().findInstallablePlugins(pluginKey, version); + checkRequest(!installablePlugins.isEmpty(), "Error while downloading plugin '%s' with version '%s'. No compatible plugin found.", pluginKey, version.getName()); + for (Release release : installablePlugins) { + try { + downloadRelease(release); + } catch (Exception e) { + String message = String.format("Fail to download the plugin (%s, version %s) from %s (error is : %s)", + release.getArtifact().getKey(), release.getVersion().getName(), release.getDownloadUrl(), e.getMessage()); + LOG.debug(message, e); + throw new IllegalStateException(message, e); + } + } + } + } + + private void downloadRelease(Release release) throws URISyntaxException, IOException { + String url = release.getDownloadUrl(); + + URI uri = new URI(url); + if (url.startsWith("file:")) { + // used for tests + File file = toFile(uri.toURL()); + copyFileToDirectory(file, downloadDir); + } else { + String filename = substringAfterLast(uri.getPath(), "/"); + if (!filename.endsWith("." + PLUGIN_EXTENSION)) { + filename = release.getKey() + "-" + release.getVersion() + "." + PLUGIN_EXTENSION; + } + File targetFile = new File(downloadDir, filename); + File tempFile = new File(downloadDir, filename + "." + TMP_SUFFIX); + downloader.download(uri, tempFile); + copyFile(tempFile, targetFile); + deleteQuietly(tempFile); + } + } + + private static Collection<File> listTempFile(File dir) { + return FileUtils.listFiles(dir, new String[] {TMP_SUFFIX}, false); + } + + private static Collection<File> listPlugins(File dir) { + return FileUtils.listFiles(dir, new String[] {PLUGIN_EXTENSION}, false); + } +} diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/PluginFileSystem.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/PluginFileSystem.java new file mode 100644 index 00000000000..e65336a7586 --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/PluginFileSystem.java @@ -0,0 +1,124 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.plugins; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.jar.JarInputStream; +import java.util.jar.Pack200; +import java.util.zip.GZIPOutputStream; +import org.sonar.api.config.Configuration; +import org.sonar.api.server.ServerSide; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; +import org.sonar.api.utils.log.Profiler; +import org.sonar.core.platform.PluginInfo; +import org.sonar.server.plugins.InstalledPlugin.FileAndMd5; + +import static com.google.common.base.Preconditions.checkState; + +@ServerSide +public class PluginFileSystem { + + public static final String PROPERTY_PLUGIN_COMPRESSION_ENABLE = "sonar.pluginsCompression.enable"; + private static final Logger LOG = Loggers.get(PluginFileSystem.class); + + private final Configuration configuration; + private final Map<String, InstalledPlugin> installedFiles = new HashMap<>(); + + public PluginFileSystem(Configuration configuration) { + this.configuration = configuration; + } + + /** + * @param plugin + * @param loadedJar the JAR loaded by classloaders. It differs from {@code plugin.getJarFile()} + * which is the initial location of JAR as seen by users + */ + public void addInstalledPlugin(PluginInfo plugin, File loadedJar) { + checkState(!installedFiles.containsKey(plugin.getKey()), "Plugin %s is already loaded", plugin.getKey()); + checkState(loadedJar.exists(), "loadedJar does not exist: %s", loadedJar); + + Optional<File> compressed = compressJar(plugin, loadedJar); + InstalledPlugin installedFile = new InstalledPlugin( + plugin, + new FileAndMd5(loadedJar), + compressed.map(FileAndMd5::new).orElse(null)); + installedFiles.put(plugin.getKey(), installedFile); + } + + public Optional<InstalledPlugin> getInstalledPlugin(String pluginKey) { + return Optional.ofNullable(installedFiles.get(pluginKey)); + } + + public Collection<InstalledPlugin> getInstalledFiles() { + return installedFiles.values(); + } + + private Optional<File> compressJar(PluginInfo plugin, File jar) { + if (!configuration.getBoolean(PROPERTY_PLUGIN_COMPRESSION_ENABLE).orElse(false)) { + return Optional.empty(); + } + + Path targetPack200 = getPack200Path(jar.toPath()); + Path sourcePack200Path = getPack200Path(plugin.getNonNullJarFile().toPath()); + + // check if packed file was deployed alongside the jar. If that's the case, use it instead of generating it (SONAR-10395). + if (sourcePack200Path.toFile().exists()) { + try { + LOG.debug("Found pack200: " + sourcePack200Path); + Files.copy(sourcePack200Path, targetPack200); + } catch (IOException e) { + throw new IllegalStateException("Failed to copy pack200 file from " + sourcePack200Path + " to " + targetPack200, e); + } + } else { + pack200(jar.toPath(), targetPack200, plugin.getKey()); + } + return Optional.of(targetPack200.toFile()); + } + + private static void pack200(Path jarPath, Path toPack200Path, String pluginKey) { + Profiler profiler = Profiler.create(LOG); + profiler.startInfo("Compressing plugin " + pluginKey + " [pack200]"); + + try (JarInputStream in = new JarInputStream(new BufferedInputStream(Files.newInputStream(jarPath))); + OutputStream out = new GZIPOutputStream(new BufferedOutputStream(Files.newOutputStream(toPack200Path)))) { + Pack200.newPacker().pack(in, out); + } catch (IOException e) { + throw new IllegalStateException(String.format("Fail to pack200 plugin [%s] '%s' to '%s'", pluginKey, jarPath, toPack200Path), e); + } + profiler.stopInfo(); + } + + private static Path getPack200Path(Path jar) { + String jarFileName = jar.getFileName().toString(); + String filename = jarFileName.substring(0, jarFileName.length() - 3) + "pack.gz"; + return jar.resolveSibling(filename); + } +} diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/PluginUninstaller.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/PluginUninstaller.java new file mode 100644 index 00000000000..8253dbaf8c1 --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/PluginUninstaller.java @@ -0,0 +1,90 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.plugins; + +import java.io.File; +import java.io.IOException; +import java.util.Collection; +import java.util.Collections; +import org.apache.commons.io.FileUtils; +import org.picocontainer.Startable; +import org.sonar.core.platform.PluginInfo; +import org.sonar.core.util.stream.MoreCollectors; +import org.sonar.server.platform.ServerFileSystem; + +import static java.lang.String.format; +import static org.apache.commons.io.FileUtils.forceMkdir; + +public class PluginUninstaller implements Startable { + private static final String PLUGIN_EXTENSION = "jar"; + private final ServerPluginRepository serverPluginRepository; + private final File uninstallDir; + + public PluginUninstaller(ServerPluginRepository serverPluginRepository, ServerFileSystem fs) { + this.serverPluginRepository = serverPluginRepository; + this.uninstallDir = fs.getUninstalledPluginsDir(); + } + + private static Collection<File> listJarFiles(File dir) { + if (dir.exists()) { + return FileUtils.listFiles(dir, new String[] {PLUGIN_EXTENSION}, false); + } + return Collections.emptyList(); + } + + @Override + public void start() { + try { + forceMkdir(uninstallDir); + } catch (IOException e) { + throw new IllegalStateException("Fail to create the directory: " + uninstallDir, e); + } + } + + @Override + public void stop() { + // Nothing to do + } + + public void uninstall(String pluginKey) { + ensurePluginIsInstalled(pluginKey); + serverPluginRepository.uninstall(pluginKey, uninstallDir); + } + + public void cancelUninstalls() { + serverPluginRepository.cancelUninstalls(uninstallDir); + } + + /** + * @return the list of plugins to be uninstalled as {@link PluginInfo} instances + */ + public Collection<PluginInfo> getUninstalledPlugins() { + return listJarFiles(uninstallDir) + .stream() + .map(PluginInfo::create) + .collect(MoreCollectors.toList()); + } + + private void ensurePluginIsInstalled(String key) { + if (!serverPluginRepository.hasPlugin(key)) { + throw new IllegalArgumentException(format("Plugin [%s] is not installed", key)); + } + } +} diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/ServerPluginJarExploder.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/ServerPluginJarExploder.java new file mode 100644 index 00000000000..1970d4475a5 --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/ServerPluginJarExploder.java @@ -0,0 +1,68 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.plugins; + +import java.io.File; +import org.apache.commons.io.FileUtils; +import org.sonar.api.server.ServerSide; +import org.sonar.api.utils.ZipUtils; +import org.sonar.core.platform.ExplodedPlugin; +import org.sonar.core.platform.PluginInfo; +import org.sonar.core.platform.PluginJarExploder; +import org.sonar.server.platform.ServerFileSystem; + +import static org.apache.commons.io.FileUtils.forceMkdir; + +@ServerSide +public class ServerPluginJarExploder extends PluginJarExploder { + private final ServerFileSystem fs; + private final PluginFileSystem pluginFileSystem; + + public ServerPluginJarExploder(ServerFileSystem fs, PluginFileSystem pluginFileSystem) { + this.fs = fs; + this.pluginFileSystem = pluginFileSystem; + } + + /** + * JAR files of directory extensions/plugins can be moved when server is up and plugins are uninstalled. + * For this reason these files must not be locked by classloaders. They are copied to the directory + * web/deploy/plugins in order to be loaded by {@link org.sonar.core.platform.PluginLoader}. + */ + @Override + public ExplodedPlugin explode(PluginInfo pluginInfo) { + File toDir = new File(fs.getDeployedPluginsDir(), pluginInfo.getKey()); + try { + forceMkdir(toDir); + org.sonar.core.util.FileUtils.cleanDirectory(toDir); + + File jarSource = pluginInfo.getNonNullJarFile(); + File jarTarget = new File(toDir, jarSource.getName()); + + FileUtils.copyFile(jarSource, jarTarget); + ZipUtils.unzip(jarSource, toDir, newLibFilter()); + ExplodedPlugin explodedPlugin = explodeFromUnzippedDir(pluginInfo.getKey(), jarTarget, toDir); + pluginFileSystem.addInstalledPlugin(pluginInfo, jarTarget); + return explodedPlugin; + } catch (Exception e) { + throw new IllegalStateException(String.format( + "Fail to unzip plugin [%s] %s to %s", pluginInfo.getKey(), pluginInfo.getNonNullJarFile().getAbsolutePath(), toDir.getAbsolutePath()), e); + } + } +} diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/ServerPluginRepository.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/ServerPluginRepository.java new file mode 100644 index 00000000000..ecc3abc2d76 --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/ServerPluginRepository.java @@ -0,0 +1,372 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.plugins; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Joiner; +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Ordering; +import java.io.File; +import java.io.IOException; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; +import javax.annotation.CheckForNull; +import org.apache.commons.io.FileUtils; +import org.picocontainer.Startable; +import org.sonar.api.Plugin; +import org.sonar.api.SonarRuntime; +import org.sonar.api.utils.MessageException; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; +import org.sonar.core.platform.PluginInfo; +import org.sonar.core.platform.PluginLoader; +import org.sonar.core.platform.PluginRepository; +import org.sonar.server.platform.ServerFileSystem; +import org.sonar.updatecenter.common.Version; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkState; +import static java.lang.String.format; +import static org.apache.commons.io.FileUtils.moveFile; +import static org.apache.commons.io.FileUtils.moveFileToDirectory; +import static org.sonar.core.util.FileUtils.deleteQuietly; + +/** + * Entry point to install and load plugins on server startup. It manages + * <ul> + * <li>installation of new plugins (effective after server startup)</li> + * <li>un-installation of plugins (effective after server startup)</li> + * <li>cancel pending installations/un-installations</li> + * <li>instantiation of plugin entry-points</li> + * </ul> + */ +public class ServerPluginRepository implements PluginRepository, Startable { + + private static final Logger LOG = Loggers.get(ServerPluginRepository.class); + private static final String[] JAR_FILE_EXTENSIONS = new String[] {"jar"}; + // List of plugins that are silently removed if installed + private static final Set<String> DEFAULT_BLACKLISTED_PLUGINS = ImmutableSet.of("scmactivity", "issuesreport", "genericcoverage"); + // List of plugins that should prevent the server to finish its startup + private static final Set<String> FORBIDDEN_COMPATIBLE_PLUGINS = ImmutableSet.of("sqale", "report", "views"); + private static final Joiner SLASH_JOINER = Joiner.on(" / ").skipNulls(); + private static final String NOT_STARTED_YET = "not started yet"; + + private final SonarRuntime runtime; + private final ServerFileSystem fs; + private final PluginLoader loader; + private final AtomicBoolean started = new AtomicBoolean(false); + private Set<String> blacklistedPluginKeys = DEFAULT_BLACKLISTED_PLUGINS; + + // following fields are available after startup + private final Map<String, PluginInfo> pluginInfosByKeys = new HashMap<>(); + private final Map<String, Plugin> pluginInstancesByKeys = new HashMap<>(); + private final Map<ClassLoader, String> keysByClassLoader = new HashMap<>(); + + public ServerPluginRepository(SonarRuntime runtime, ServerFileSystem fs, PluginLoader loader) { + this.runtime = runtime; + this.fs = fs; + this.loader = loader; + } + + @VisibleForTesting + void setBlacklistedPluginKeys(Set<String> keys) { + this.blacklistedPluginKeys = keys; + } + + @Override + public void start() { + loadPreInstalledPlugins(); + moveDownloadedPlugins(); + unloadIncompatiblePlugins(); + logInstalledPlugins(); + loadInstances(); + started.set(true); + } + + @Override + public void stop() { + // close classloaders + loader.unload(pluginInstancesByKeys.values()); + pluginInstancesByKeys.clear(); + pluginInfosByKeys.clear(); + keysByClassLoader.clear(); + started.set(true); + } + + /** + * Return the key of the plugin the extension (in the sense of {@link Plugin.Context#addExtension(Object)} is coming from. + */ + @CheckForNull + public String getPluginKey(Object extension) { + return keysByClassLoader.get(extension.getClass().getClassLoader()); + } + + /** + * Load the plugins that are located in extensions/plugins. Blacklisted plugins are + * deleted. + */ + private void loadPreInstalledPlugins() { + for (File file : listJarFiles(fs.getInstalledPluginsDir())) { + PluginInfo info = PluginInfo.create(file); + registerPluginInfo(info); + } + } + + /** + * Move the plugins recently downloaded to extensions/plugins. + */ + private void moveDownloadedPlugins() { + if (fs.getDownloadedPluginsDir().exists()) { + for (File sourceFile : listJarFiles(fs.getDownloadedPluginsDir())) { + overrideAndRegisterPlugin(sourceFile); + } + } + } + + private void registerPluginInfo(PluginInfo info) { + String pluginKey = info.getKey(); + if (blacklistedPluginKeys.contains(pluginKey)) { + LOG.warn("Plugin {} [{}] is blacklisted and is being uninstalled", info.getName(), pluginKey); + deleteQuietly(info.getNonNullJarFile()); + return; + } + if (FORBIDDEN_COMPATIBLE_PLUGINS.contains(pluginKey)) { + throw MessageException.of(String.format("Plugin '%s' is no longer compatible with this version of SonarQube", pluginKey)); + } + PluginInfo existing = pluginInfosByKeys.put(pluginKey, info); + if (existing != null) { + throw MessageException.of(format("Found two versions of the plugin %s [%s] in the directory extensions/plugins. Please remove one of %s or %s.", + info.getName(), pluginKey, info.getNonNullJarFile().getName(), existing.getNonNullJarFile().getName())); + } + + } + + /** + * Move or copy plugin to directory extensions/plugins. If a version of this plugin + * already exists then it's deleted. + */ + private void overrideAndRegisterPlugin(File sourceFile) { + File destDir = fs.getInstalledPluginsDir(); + File destFile = new File(destDir, sourceFile.getName()); + if (destFile.exists()) { + // plugin with same filename already installed + deleteQuietly(destFile); + } + + try { + moveFile(sourceFile, destFile); + + } catch (IOException e) { + throw new IllegalStateException(format("Fail to move plugin: %s to %s", + sourceFile.getAbsolutePath(), destFile.getAbsolutePath()), e); + } + + PluginInfo info = PluginInfo.create(destFile); + PluginInfo existing = pluginInfosByKeys.put(info.getKey(), info); + if (existing != null) { + if (!existing.getNonNullJarFile().getName().equals(destFile.getName())) { + deleteQuietly(existing.getNonNullJarFile()); + } + LOG.info("Plugin {} [{}] updated to version {}", info.getName(), info.getKey(), info.getVersion()); + } else { + LOG.info("Plugin {} [{}] installed", info.getName(), info.getKey()); + } + } + + /** + * Removes the plugins that are not compatible with current environment. + */ + private void unloadIncompatiblePlugins() { + // loop as long as the previous loop ignored some plugins. That allows to support dependencies + // on many levels, for example D extends C, which extends B, which requires A. If A is not installed, + // then B, C and D must be ignored. That's not possible to achieve this algorithm with a single + // iteration over plugins. + Set<String> removedKeys = new HashSet<>(); + do { + removedKeys.clear(); + for (PluginInfo plugin : pluginInfosByKeys.values()) { + if (!isCompatible(plugin, runtime, pluginInfosByKeys)) { + removedKeys.add(plugin.getKey()); + } + } + for (String removedKey : removedKeys) { + pluginInfosByKeys.remove(removedKey); + } + } while (!removedKeys.isEmpty()); + } + + @VisibleForTesting + static boolean isCompatible(PluginInfo plugin, SonarRuntime runtime, Map<String, PluginInfo> allPluginsByKeys) { + if (Strings.isNullOrEmpty(plugin.getMainClass()) && Strings.isNullOrEmpty(plugin.getBasePlugin())) { + LOG.warn("Plugin {} [{}] is ignored because entry point class is not defined", plugin.getName(), plugin.getKey()); + return false; + } + + if (!plugin.isCompatibleWith(runtime.getApiVersion().toString())) { + throw MessageException.of(format( + "Plugin %s [%s] requires at least SonarQube %s", plugin.getName(), plugin.getKey(), plugin.getMinimalSqVersion())); + } + + if (!Strings.isNullOrEmpty(plugin.getBasePlugin()) && !allPluginsByKeys.containsKey(plugin.getBasePlugin())) { + // it extends a plugin that is not installed + LOG.warn("Plugin {} [{}] is ignored because its base plugin [{}] is not installed", plugin.getName(), plugin.getKey(), plugin.getBasePlugin()); + return false; + } + + for (PluginInfo.RequiredPlugin requiredPlugin : plugin.getRequiredPlugins()) { + PluginInfo installedRequirement = allPluginsByKeys.get(requiredPlugin.getKey()); + if (installedRequirement == null) { + // it requires a plugin that is not installed + LOG.warn("Plugin {} [{}] is ignored because the required plugin [{}] is not installed", plugin.getName(), plugin.getKey(), requiredPlugin.getKey()); + return false; + } + Version installedRequirementVersion = installedRequirement.getVersion(); + if (installedRequirementVersion != null && requiredPlugin.getMinimalVersion().compareToIgnoreQualifier(installedRequirementVersion) > 0) { + // it requires a more recent version + LOG.warn("Plugin {} [{}] is ignored because the version {} of required plugin [{}] is not supported", plugin.getName(), plugin.getKey(), + requiredPlugin.getKey(), requiredPlugin.getMinimalVersion()); + return false; + } + } + return true; + } + + private void logInstalledPlugins() { + List<PluginInfo> orderedPlugins = Ordering.natural().sortedCopy(pluginInfosByKeys.values()); + for (PluginInfo plugin : orderedPlugins) { + LOG.info("Deploy plugin {}", SLASH_JOINER.join(plugin.getName(), plugin.getVersion(), plugin.getImplementationBuild())); + } + } + + private void loadInstances() { + pluginInstancesByKeys.putAll(loader.load(pluginInfosByKeys)); + + for (Map.Entry<String, Plugin> e : pluginInstancesByKeys.entrySet()) { + keysByClassLoader.put(e.getValue().getClass().getClassLoader(), e.getKey()); + } + } + + /** + * Uninstall a plugin and its dependents + */ + public void uninstall(String pluginKey, File uninstallDir) { + Set<String> uninstallKeys = new HashSet<>(); + uninstallKeys.add(pluginKey); + appendDependentPluginKeys(pluginKey, uninstallKeys); + + for (String uninstallKey : uninstallKeys) { + PluginInfo info = getPluginInfo(uninstallKey); + + try { + if (!getPluginFile(info).exists()) { + LOG.info("Plugin already uninstalled: {} [{}]", info.getName(), info.getKey()); + continue; + } + + LOG.info("Uninstalling plugin {} [{}]", info.getName(), info.getKey()); + + File masterFile = getPluginFile(info); + moveFileToDirectory(masterFile, uninstallDir, true); + } catch (IOException e) { + throw new IllegalStateException(format("Fail to uninstall plugin %s [%s]", info.getName(), info.getKey()), e); + } + } + } + + public void cancelUninstalls(File uninstallDir) { + for (File file : listJarFiles(uninstallDir)) { + try { + moveFileToDirectory(file, fs.getInstalledPluginsDir(), false); + } catch (IOException e) { + throw new IllegalStateException("Fail to cancel plugin uninstalls", e); + } + } + } + + /** + * Appends dependent plugins, only the ones that still exist in the plugins folder. + */ + private void appendDependentPluginKeys(String pluginKey, Set<String> appendTo) { + for (PluginInfo otherPlugin : getPluginInfos()) { + if (!otherPlugin.getKey().equals(pluginKey)) { + for (PluginInfo.RequiredPlugin requirement : otherPlugin.getRequiredPlugins()) { + if (requirement.getKey().equals(pluginKey)) { + appendTo.add(otherPlugin.getKey()); + appendDependentPluginKeys(otherPlugin.getKey(), appendTo); + } + } + } + } + } + + private File getPluginFile(PluginInfo info) { + // we don't reuse info.getFile() just to be sure that file is located in from extensions/plugins + return new File(fs.getInstalledPluginsDir(), info.getNonNullJarFile().getName()); + } + + public Map<String, PluginInfo> getPluginInfosByKeys() { + return pluginInfosByKeys; + } + + @Override + public Collection<PluginInfo> getPluginInfos() { + checkState(started.get(), NOT_STARTED_YET); + return ImmutableList.copyOf(pluginInfosByKeys.values()); + } + + @Override + public PluginInfo getPluginInfo(String key) { + checkState(started.get(), NOT_STARTED_YET); + PluginInfo info = pluginInfosByKeys.get(key); + if (info == null) { + throw new IllegalArgumentException(format("Plugin [%s] does not exist", key)); + } + return info; + } + + @Override + public Plugin getPluginInstance(String key) { + checkState(started.get(), NOT_STARTED_YET); + Plugin plugin = pluginInstancesByKeys.get(key); + checkArgument(plugin != null, "Plugin [%s] does not exist", key); + return plugin; + } + + @Override + public boolean hasPlugin(String key) { + checkState(started.get(), NOT_STARTED_YET); + return pluginInfosByKeys.containsKey(key); + } + + private static Collection<File> listJarFiles(File dir) { + if (dir.exists()) { + return FileUtils.listFiles(dir, JAR_FILE_EXTENSIONS, false); + } + return Collections.emptyList(); + } +} diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/UpdateCenterClient.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/UpdateCenterClient.java new file mode 100644 index 00000000000..2f5d0b2d357 --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/UpdateCenterClient.java @@ -0,0 +1,113 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.plugins; + +import com.google.common.base.Optional; +import java.io.InputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; +import java.util.Date; +import org.apache.commons.io.IOUtils; +import org.sonar.api.Properties; +import org.sonar.api.Property; +import org.sonar.api.config.Configuration; +import org.sonar.api.utils.UriReader; +import org.sonar.api.utils.log.Loggers; +import org.sonar.process.ProcessProperties; +import org.sonar.updatecenter.common.UpdateCenter; +import org.sonar.updatecenter.common.UpdateCenterDeserializer; +import org.sonar.updatecenter.common.UpdateCenterDeserializer.Mode; + +/** + * HTTP client to load data from the remote update center hosted at https://update.sonarsource.org. + * + * @since 2.4 + */ +@Properties({ + @Property( + key = UpdateCenterClient.URL_PROPERTY, + defaultValue = "https://update.sonarsource.org/update-center.properties", + name = "Update Center URL", + category = "Update Center", + project = false, + // hidden from UI + global = false) +}) +public class UpdateCenterClient { + + public static final String URL_PROPERTY = "sonar.updatecenter.url"; + public static final int PERIOD_IN_MILLISECONDS = 60 * 60 * 1000; + + private final URI uri; + private final UriReader uriReader; + private final boolean isActivated; + private UpdateCenter pluginCenter = null; + private long lastRefreshDate = 0; + + public UpdateCenterClient(UriReader uriReader, Configuration config) throws URISyntaxException { + this.uriReader = uriReader; + this.uri = new URI(config.get(URL_PROPERTY).get()); + this.isActivated = config.getBoolean(ProcessProperties.Property.SONAR_UPDATECENTER_ACTIVATE.getKey()).get(); + Loggers.get(getClass()).info("Update center: " + uriReader.description(uri)); + } + + public Optional<UpdateCenter> getUpdateCenter() { + return getUpdateCenter(false); + } + + public Optional<UpdateCenter> getUpdateCenter(boolean forceRefresh) { + if (!isActivated) { + return Optional.absent(); + } + + if (pluginCenter == null || forceRefresh || needsRefresh()) { + pluginCenter = init(); + lastRefreshDate = System.currentTimeMillis(); + } + return Optional.fromNullable(pluginCenter); + } + + public Date getLastRefreshDate() { + return lastRefreshDate > 0 ? new Date(lastRefreshDate) : null; + } + + private boolean needsRefresh() { + return lastRefreshDate + PERIOD_IN_MILLISECONDS < System.currentTimeMillis(); + } + + private UpdateCenter init() { + InputStream input = null; + try { + String content = uriReader.readString(uri, StandardCharsets.UTF_8); + java.util.Properties properties = new java.util.Properties(); + input = IOUtils.toInputStream(content, StandardCharsets.UTF_8); + properties.load(input); + return new UpdateCenterDeserializer(Mode.PROD, true).fromProperties(properties); + + } catch (Exception e) { + Loggers.get(getClass()).error("Fail to connect to update center", e); + return null; + + } finally { + IOUtils.closeQuietly(input); + } + } +} diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/UpdateCenterMatrixFactory.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/UpdateCenterMatrixFactory.java new file mode 100644 index 00000000000..0c0a6730987 --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/UpdateCenterMatrixFactory.java @@ -0,0 +1,55 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.plugins; + +import com.google.common.base.Optional; +import org.sonar.api.SonarRuntime; +import org.sonar.updatecenter.common.UpdateCenter; +import org.sonar.updatecenter.common.Version; + +/** + * @since 2.4 + */ +public class UpdateCenterMatrixFactory { + + private final UpdateCenterClient centerClient; + private final SonarRuntime sonarRuntime; + private final InstalledPluginReferentialFactory installedPluginReferentialFactory; + + public UpdateCenterMatrixFactory(UpdateCenterClient centerClient, SonarRuntime runtime, + InstalledPluginReferentialFactory installedPluginReferentialFactory) { + this.centerClient = centerClient; + this.sonarRuntime = runtime; + this.installedPluginReferentialFactory = installedPluginReferentialFactory; + } + + public Optional<UpdateCenter> getUpdateCenter(boolean refreshUpdateCenter) { + Optional<UpdateCenter> updateCenter = centerClient.getUpdateCenter(refreshUpdateCenter); + if (updateCenter.isPresent()) { + org.sonar.api.utils.Version fullVersion = sonarRuntime.getApiVersion(); + org.sonar.api.utils.Version semanticVersion = org.sonar.api.utils.Version.create(fullVersion.major(), fullVersion.minor(), fullVersion.patch()); + + return Optional.of(updateCenter.get().setInstalledSonarVersion(Version.create(semanticVersion.toString())).registerInstalledPlugins( + installedPluginReferentialFactory.getInstalledPluginReferential()) + .setDate(centerClient.getLastRefreshDate())); + } + return Optional.absent(); + } +} diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/WebServerExtensionInstaller.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/WebServerExtensionInstaller.java new file mode 100644 index 00000000000..9fa956d2a15 --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/WebServerExtensionInstaller.java @@ -0,0 +1,33 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.plugins; + +import org.sonar.api.SonarRuntime; +import org.sonar.api.server.ServerSide; +import org.sonar.core.platform.PluginRepository; + +import static java.util.Collections.singleton; + +@ServerSide +public class WebServerExtensionInstaller extends ServerExtensionInstaller { + public WebServerExtensionInstaller(SonarRuntime sonarRuntime, PluginRepository pluginRepository) { + super(sonarRuntime, pluginRepository, singleton(ServerSide.class)); + } +} diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/edition/EditionBundledPlugins.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/edition/EditionBundledPlugins.java new file mode 100644 index 00000000000..f6c1db3b035 --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/edition/EditionBundledPlugins.java @@ -0,0 +1,44 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.plugins.edition; + +import java.util.Arrays; +import org.sonar.core.platform.PluginInfo; +import org.sonar.updatecenter.common.Plugin; + +public final class EditionBundledPlugins { + + private static final String SONARSOURCE_ORGANIZATION = "SonarSource"; + private static final String[] SONARSOURCE_COMMERCIAL_LICENSES = {"SonarSource", "Commercial"}; + + private EditionBundledPlugins() { + // prevents instantiation + } + + public static boolean isEditionBundled(Plugin plugin) { + return SONARSOURCE_ORGANIZATION.equalsIgnoreCase(plugin.getOrganization()) + && Arrays.stream(SONARSOURCE_COMMERCIAL_LICENSES).anyMatch(s -> s.equalsIgnoreCase(plugin.getLicense())); + } + + public static boolean isEditionBundled(PluginInfo pluginInfo) { + return SONARSOURCE_ORGANIZATION.equalsIgnoreCase(pluginInfo.getOrganizationName()) + && Arrays.stream(SONARSOURCE_COMMERCIAL_LICENSES).anyMatch(s -> s.equalsIgnoreCase(pluginInfo.getLicense())); + } +} diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/edition/package-info.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/edition/package-info.java new file mode 100644 index 00000000000..e0a7b00bbf9 --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/edition/package-info.java @@ -0,0 +1,24 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.server.plugins.edition; + +import javax.annotation.ParametersAreNonnullByDefault; + diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/package-info.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/package-info.java new file mode 100644 index 00000000000..cc398bb88c6 --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/package-info.java @@ -0,0 +1,23 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.server.plugins; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/project/ProjectLifeCycleListener.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/project/ProjectLifeCycleListener.java new file mode 100644 index 00000000000..6afdea527e9 --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/project/ProjectLifeCycleListener.java @@ -0,0 +1,41 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.project; + +import java.util.Set; +import org.sonar.api.server.ServerSide; + +@ServerSide +public interface ProjectLifeCycleListener { + /** + * This method is called after the specified projects have been deleted. + */ + void onProjectsDeleted(Set<Project> projects); + + /** + * This method is called after the specified projects have been deleted. + */ + void onProjectBranchesDeleted(Set<Project> projects); + + /** + * This method is called after the specified projects' keys have been modified. + */ + void onProjectsRekeyed(Set<RekeyedProject> rekeyedProjects); +} diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/project/ProjectLifeCycleListeners.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/project/ProjectLifeCycleListeners.java new file mode 100644 index 00000000000..7f3d3f1867d --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/project/ProjectLifeCycleListeners.java @@ -0,0 +1,55 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.project; + +import java.util.Set; + +public interface ProjectLifeCycleListeners { + /** + * This method is called after the specified projects have been deleted and will call method + * {@link ProjectLifeCycleListener#onProjectsDeleted(Set) onProjectsDeleted(Set)} of all known + * {@link ProjectLifeCycleListener} implementations. + * <p> + * This method ensures all {@link ProjectLifeCycleListener} implementations are called, even if one or more of + * them fail with an exception. + */ + void onProjectsDeleted(Set<Project> projects); + + /** + * This method is called after the specified project branches have been deleted and will call method + * {@link ProjectLifeCycleListener#onProjectBranchesDeleted(Set)} of all known + * {@link ProjectLifeCycleListener} implementations. + * <p> + * This method ensures all {@link ProjectLifeCycleListener} implementations are called, even if one or more of + * them fail with an exception. + */ + void onProjectBranchesDeleted(Set<Project> projects); + + /** + * This method is called after the specified project's key has been changed and will call method + * {@link ProjectLifeCycleListener#onProjectsRekeyed(Set) onProjectsRekeyed(Set)} of all known + * {@link ProjectLifeCycleListener} implementations. + * <p> + * This method ensures all {@link ProjectLifeCycleListener} implementations are called, even if one or more of + * them fail with an exception. + */ + void onProjectsRekeyed(Set<RekeyedProject> rekeyedProjects); + +} diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/project/ProjectLifeCycleListenersImpl.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/project/ProjectLifeCycleListenersImpl.java new file mode 100644 index 00000000000..af440d60adb --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/project/ProjectLifeCycleListenersImpl.java @@ -0,0 +1,91 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.project; + +import java.util.Arrays; +import java.util.Set; +import java.util.function.Consumer; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; + +import static com.google.common.base.Preconditions.checkNotNull; + +public class ProjectLifeCycleListenersImpl implements ProjectLifeCycleListeners { + private static final Logger LOG = Loggers.get(ProjectLifeCycleListenersImpl.class); + + private final ProjectLifeCycleListener[] listeners; + + /** + * Used by Pico when there is no ProjectLifeCycleListener implementation in container. + */ + public ProjectLifeCycleListenersImpl() { + this.listeners = new ProjectLifeCycleListener[0]; + } + + /** + * Used by Pico when there is at least one ProjectLifeCycleListener implementation in container. + */ + public ProjectLifeCycleListenersImpl(ProjectLifeCycleListener[] listeners) { + this.listeners = listeners; + } + + @Override + public void onProjectsDeleted(Set<Project> projects) { + checkNotNull(projects, "projects can't be null"); + if (projects.isEmpty()) { + return; + } + + Arrays.stream(listeners) + .forEach(safelyCallListener(listener -> listener.onProjectsDeleted(projects))); + } + + @Override + public void onProjectBranchesDeleted(Set<Project> projects) { + checkNotNull(projects, "projects can't be null"); + if (projects.isEmpty()) { + return; + } + + Arrays.stream(listeners) + .forEach(safelyCallListener(listener -> listener.onProjectBranchesDeleted(projects))); + } + + @Override + public void onProjectsRekeyed(Set<RekeyedProject> rekeyedProjects) { + checkNotNull(rekeyedProjects, "rekeyedProjects can't be null"); + if (rekeyedProjects.isEmpty()) { + return; + } + + Arrays.stream(listeners) + .forEach(safelyCallListener(listener -> listener.onProjectsRekeyed(rekeyedProjects))); + } + + private static Consumer<ProjectLifeCycleListener> safelyCallListener(Consumer<ProjectLifeCycleListener> task) { + return listener -> { + try { + task.accept(listener); + } catch (Error | Exception e) { + LOG.error("Call on ProjectLifeCycleListener \"{}\" failed", listener.getClass(), e); + } + }; + } +} diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/project/RekeyedProject.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/project/RekeyedProject.java new file mode 100644 index 00000000000..ecab0b148f8 --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/project/RekeyedProject.java @@ -0,0 +1,68 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.project; + +import java.util.Objects; + +import static com.google.common.base.Preconditions.checkNotNull; + +public final class RekeyedProject { + private final Project project; + private final String previousKey; + + public RekeyedProject(Project project, String previousKey) { + this.project = checkNotNull(project, "project can't be null"); + this.previousKey = checkNotNull(previousKey, "previousKey can't be null"); + } + + public Project getProject() { + return project; + } + + public String getPreviousKey() { + return previousKey; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + RekeyedProject that = (RekeyedProject) o; + return Objects.equals(project, that.project) && + Objects.equals(previousKey, that.previousKey); + } + + @Override + public int hashCode() { + return Objects.hash(project, previousKey); + } + + @Override + public String toString() { + return "RekeyedProject{" + + "project=" + project + + ", previousKey='" + previousKey + '\'' + + '}'; + } +} diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/project/Visibility.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/project/Visibility.java new file mode 100644 index 00000000000..edf05180276 --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/project/Visibility.java @@ -0,0 +1,72 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.project; + +import java.util.List; + +import static java.util.Arrays.stream; +import static org.sonar.core.util.stream.MoreCollectors.toList; + +public enum Visibility { + + PRIVATE(true, "private"), + PUBLIC(false, "public"); + + private static final List<String> LABELS = stream(values()).map(Visibility::getLabel).collect(toList(values().length)); + + private final boolean isPrivate; + private final String label; + + Visibility(boolean isPrivate, String label) { + this.isPrivate = isPrivate; + this.label = label; + } + + public String getLabel() { + return label; + } + + boolean isPrivate() { + return isPrivate; + } + + public static String getLabel(boolean isPrivate) { + return stream(values()) + .filter(v -> v.isPrivate == isPrivate) + .map(Visibility::getLabel) + .findAny() + .orElseThrow(() -> new IllegalStateException("Invalid visibility boolean '" + isPrivate + "'")); + } + + public static boolean isPrivate(String label) { + return parseVisibility(label).isPrivate(); + } + + public static Visibility parseVisibility(String label) { + return stream(values()) + .filter(v -> v.label.equals(label)) + .findAny() + .orElseThrow(() -> new IllegalStateException("Invalid visibility label '" + label + "'")); + } + + public static List<String> getLabels() { + return LABELS; + } +} diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/project/package-info.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/project/package-info.java new file mode 100644 index 00000000000..205d7058e06 --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/project/package-info.java @@ -0,0 +1,23 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.server.project; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/qualitygate/ProjectsInWarning.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/qualitygate/ProjectsInWarning.java new file mode 100644 index 00000000000..7591c138d28 --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/qualitygate/ProjectsInWarning.java @@ -0,0 +1,44 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.qualitygate; + +import static com.google.common.base.Preconditions.checkArgument; + +/** + * Store number of projects in warning in order for the web service api/components/search to know if warning value should be return in the quality gate facet. + * The value is updated each time the daemon {@link ProjectsInWarningDaemon} is executed + */ +public class ProjectsInWarning { + + private Long projectsInWarning; + + public void update(long projectsInWarning) { + this.projectsInWarning = projectsInWarning; + } + + public long count() { + checkArgument(isInitialized(), "Initialization has not be done"); + return projectsInWarning; + } + + boolean isInitialized() { + return projectsInWarning != null; + } +} diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/qualitygate/changeevent/QGChangeEvent.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/qualitygate/changeevent/QGChangeEvent.java new file mode 100644 index 00000000000..6953a77575e --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/qualitygate/changeevent/QGChangeEvent.java @@ -0,0 +1,101 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.qualitygate.changeevent; + +import java.util.Optional; +import java.util.function.Supplier; +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; +import org.sonar.api.config.Configuration; +import org.sonar.api.measures.Metric; +import org.sonar.db.component.BranchDto; +import org.sonar.db.component.ComponentDto; +import org.sonar.db.component.SnapshotDto; +import org.sonar.server.qualitygate.EvaluatedQualityGate; + +import static java.util.Objects.requireNonNull; + +@Immutable +public class QGChangeEvent { + private final ComponentDto project; + private final BranchDto branch; + private final SnapshotDto analysis; + private final Configuration projectConfiguration; + private final Metric.Level previousStatus; + private final Supplier<Optional<EvaluatedQualityGate>> qualityGateSupplier; + + public QGChangeEvent(ComponentDto project, BranchDto branch, SnapshotDto analysis, Configuration projectConfiguration, + @Nullable Metric.Level previousStatus, Supplier<Optional<EvaluatedQualityGate>> qualityGateSupplier) { + this.project = requireNonNull(project, "project can't be null"); + this.branch = requireNonNull(branch, "branch can't be null"); + this.analysis = requireNonNull(analysis, "analysis can't be null"); + this.projectConfiguration = requireNonNull(projectConfiguration, "projectConfiguration can't be null"); + this.previousStatus = previousStatus; + this.qualityGateSupplier = requireNonNull(qualityGateSupplier, "qualityGateSupplier can't be null"); + } + + public BranchDto getBranch() { + return branch; + } + + public ComponentDto getProject() { + return project; + } + + public SnapshotDto getAnalysis() { + return analysis; + } + + public Configuration getProjectConfiguration() { + return projectConfiguration; + } + + public Optional<Metric.Level> getPreviousStatus() { + return Optional.ofNullable(previousStatus); + } + + public Supplier<Optional<EvaluatedQualityGate>> getQualityGateSupplier() { + return qualityGateSupplier; + } + + @Override + public String toString() { + return "QGChangeEvent{" + + "project=" + toString(project) + + ", branch=" + toString(branch) + + ", analysis=" + toString(analysis) + + ", projectConfiguration=" + projectConfiguration + + ", previousStatus=" + previousStatus + + ", qualityGateSupplier=" + qualityGateSupplier + + '}'; + } + + private static String toString(ComponentDto project) { + return project.uuid() + ":" + project.getKey(); + } + + private static String toString(BranchDto branch) { + return branch.getBranchType() + ":" + branch.getUuid() + ":" + branch.getProjectUuid() + ":" + branch.getMergeBranchUuid(); + } + + private static String toString(SnapshotDto analysis) { + return analysis.getUuid() + ":" + analysis.getCreatedAt(); + } +} diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/qualitygate/changeevent/QGChangeEventListener.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/qualitygate/changeevent/QGChangeEventListener.java new file mode 100644 index 00000000000..c3baf201471 --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/qualitygate/changeevent/QGChangeEventListener.java @@ -0,0 +1,66 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.qualitygate.changeevent; + +import java.util.EnumSet; +import java.util.Set; +import org.sonar.api.rules.RuleType; +import org.sonar.api.server.ServerSide; + +@ServerSide +public interface QGChangeEventListener { + /** + * Called consequently to a change done on one or more issue of a given project. + * + * @param qualityGateEvent can not be {@code null} + * @param changedIssues can not be {@code null} nor empty + */ + void onIssueChanges(QGChangeEvent qualityGateEvent, Set<ChangedIssue> changedIssues); + + interface ChangedIssue { + + String getKey(); + + Status getStatus(); + + RuleType getType(); + + String getSeverity(); + + default boolean isNotClosed() { + return !Status.CLOSED_STATUSES.contains(getStatus()); + } + } + + enum Status { + OPEN, + CONFIRMED, + REOPENED, + RESOLVED_FP, + RESOLVED_WF, + RESOLVED_FIXED, + TO_REVIEW, + IN_REVIEW, + REVIEWED; + + protected static final Set<Status> CLOSED_STATUSES = EnumSet.of(CONFIRMED, RESOLVED_FIXED, RESOLVED_FP, RESOLVED_WF); + } + +} diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/qualitygate/changeevent/QGChangeEventListeners.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/qualitygate/changeevent/QGChangeEventListeners.java new file mode 100644 index 00000000000..e2e66b555de --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/qualitygate/changeevent/QGChangeEventListeners.java @@ -0,0 +1,29 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.qualitygate.changeevent; + +import java.util.Collection; +import java.util.List; +import org.sonar.core.issue.DefaultIssue; + +public interface QGChangeEventListeners { + + void broadcastOnIssueChange(List<DefaultIssue> changedIssues, Collection<QGChangeEvent> qgChangeEvents); +} diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/qualitygate/changeevent/QGChangeEventListenersImpl.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/qualitygate/changeevent/QGChangeEventListenersImpl.java new file mode 100644 index 00000000000..76a4c2bebf1 --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/qualitygate/changeevent/QGChangeEventListenersImpl.java @@ -0,0 +1,180 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.qualitygate.changeevent; + +import com.google.common.collect.Multimap; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import org.sonar.api.issue.Issue; +import org.sonar.api.rules.RuleType; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; +import org.sonar.core.issue.DefaultIssue; +import org.sonar.core.util.stream.MoreCollectors; +import org.sonar.server.qualitygate.changeevent.QGChangeEventListener.ChangedIssue; + +import static java.lang.String.format; +import static org.sonar.core.util.stream.MoreCollectors.toSet; + +/** + * Broadcast a given collection of {@link QGChangeEvent} for a specific trigger to all the registered + * {@link QGChangeEventListener} in Pico. + * + * This class ensures that an {@link Exception} occurring calling one of the {@link QGChangeEventListener} doesn't + * prevent from calling the others. + */ +public class QGChangeEventListenersImpl implements QGChangeEventListeners { + private static final Logger LOG = Loggers.get(QGChangeEventListenersImpl.class); + + private final QGChangeEventListener[] listeners; + + /** + * Used by Pico when there is no QGChangeEventListener instance in container. + */ + public QGChangeEventListenersImpl() { + this.listeners = new QGChangeEventListener[0]; + } + + public QGChangeEventListenersImpl(QGChangeEventListener[] listeners) { + this.listeners = listeners; + } + + @Override + public void broadcastOnIssueChange(List<DefaultIssue> issues, Collection<QGChangeEvent> changeEvents) { + if (listeners.length == 0 || issues.isEmpty() || changeEvents.isEmpty()) { + return; + } + + try { + Multimap<String, QGChangeEvent> eventsByComponentUuid = changeEvents.stream() + .collect(MoreCollectors.index(t -> t.getProject().uuid())); + Multimap<String, DefaultIssue> issueByComponentUuid = issues.stream() + .collect(MoreCollectors.index(DefaultIssue::projectUuid)); + + issueByComponentUuid.asMap() + .forEach((componentUuid, value) -> { + Collection<QGChangeEvent> qgChangeEvents = eventsByComponentUuid.get(componentUuid); + if (!qgChangeEvents.isEmpty()) { + Set<ChangedIssue> changedIssues = value.stream() + .map(ChangedIssueImpl::new) + .collect(toSet()); + qgChangeEvents + .forEach(changeEvent -> Arrays.stream(listeners) + .forEach(listener -> broadcastTo(changedIssues, changeEvent, listener))); + } + }); + } catch (Error e) { + LOG.warn(format("Broadcasting to listeners failed for %s events", changeEvents.size()), e); + } + } + + private static void broadcastTo(Set<ChangedIssue> changedIssues, QGChangeEvent changeEvent, QGChangeEventListener listener) { + try { + LOG.trace("calling onChange() on listener {} for events {}...", listener.getClass().getName(), changeEvent); + listener.onIssueChanges(changeEvent, changedIssues); + } catch (Exception e) { + LOG.warn(format("onChange() call failed on listener %s for events %s", listener.getClass().getName(), changeEvent), e); + } + } + + static class ChangedIssueImpl implements ChangedIssue { + private final String key; + private final QGChangeEventListener.Status status; + private final RuleType type; + private final String severity; + + ChangedIssueImpl(DefaultIssue issue) { + this.key = issue.key(); + this.status = statusOf(issue); + this.type = issue.type(); + this.severity = issue.severity(); + } + + static QGChangeEventListener.Status statusOf(DefaultIssue issue) { + switch (issue.status()) { + case Issue.STATUS_OPEN: + return QGChangeEventListener.Status.OPEN; + case Issue.STATUS_CONFIRMED: + return QGChangeEventListener.Status.CONFIRMED; + case Issue.STATUS_REOPENED: + return QGChangeEventListener.Status.REOPENED; + case Issue.STATUS_TO_REVIEW: + return QGChangeEventListener.Status.TO_REVIEW; + case Issue.STATUS_IN_REVIEW: + return QGChangeEventListener.Status.IN_REVIEW; + case Issue.STATUS_REVIEWED: + return QGChangeEventListener.Status.REVIEWED; + case Issue.STATUS_RESOLVED: + return statusOfResolved(issue); + default: + throw new IllegalStateException("Unexpected status: " + issue.status()); + } + } + + private static QGChangeEventListener.Status statusOfResolved(DefaultIssue issue) { + String resolution = issue.resolution(); + Objects.requireNonNull(resolution, "A resolved issue should have a resolution"); + switch (resolution) { + case Issue.RESOLUTION_FALSE_POSITIVE: + return QGChangeEventListener.Status.RESOLVED_FP; + case Issue.RESOLUTION_WONT_FIX: + return QGChangeEventListener.Status.RESOLVED_WF; + case Issue.RESOLUTION_FIXED: + return QGChangeEventListener.Status.RESOLVED_FIXED; + default: + throw new IllegalStateException("Unexpected resolution for a resolved issue: " + resolution); + } + } + + @Override + public String getKey() { + return key; + } + + @Override + public QGChangeEventListener.Status getStatus() { + return status; + } + + @Override + public RuleType getType() { + return type; + } + + @Override + public String getSeverity() { + return severity; + } + + @Override + public String toString() { + return "ChangedIssueImpl{" + + "key='" + key + '\'' + + ", status=" + status + + ", type=" + type + + ", severity=" + severity + + '}'; + } + } + +} diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/qualitygate/changeevent/Trigger.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/qualitygate/changeevent/Trigger.java new file mode 100644 index 00000000000..6ef8397046a --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/qualitygate/changeevent/Trigger.java @@ -0,0 +1,24 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.qualitygate.changeevent; + +public enum Trigger { + ISSUE_CHANGE +} diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/qualitygate/changeevent/package-info.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/qualitygate/changeevent/package-info.java new file mode 100644 index 00000000000..ed52cd87a47 --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/qualitygate/changeevent/package-info.java @@ -0,0 +1,23 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.server.qualitygate.changeevent; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/qualityprofile/BulkChangeResult.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/qualityprofile/BulkChangeResult.java new file mode 100644 index 00000000000..91a31ca76d9 --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/qualityprofile/BulkChangeResult.java @@ -0,0 +1,60 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.qualityprofile; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +public class BulkChangeResult { + + private final List<String> errors = new ArrayList<>(); + private int succeeded = 0; + private int failed = 0; + private final List<ActiveRuleChange> changes = new ArrayList<>(); + + public List<String> getErrors() { + return errors; + } + + public int countSucceeded() { + return succeeded; + } + + public int countFailed() { + return failed; + } + + void incrementSucceeded() { + succeeded++; + } + + void incrementFailed() { + failed++; + } + + void addChanges(Collection<ActiveRuleChange> c) { + this.changes.addAll(c); + } + + public List<ActiveRuleChange> getChanges() { + return changes; + } +} diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/qualityprofile/QProfileRules.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/qualityprofile/QProfileRules.java new file mode 100644 index 00000000000..32a11c072d8 --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/qualityprofile/QProfileRules.java @@ -0,0 +1,64 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.qualityprofile; + +import java.util.Collection; +import java.util.List; +import javax.annotation.Nullable; +import org.sonar.api.server.ServerSide; +import org.sonar.db.DbSession; +import org.sonar.db.qualityprofile.QProfileDto; +import org.sonar.db.rule.RuleDefinitionDto; +import org.sonar.server.rule.index.RuleQuery; + +/** + * Operations related to activation and deactivation of rules on user profiles. + */ +@ServerSide +public interface QProfileRules { + + /** + * Activate multiple rules at once on a Quality profile. + * Db session is committed and Elasticsearch indices are updated. + * If an activation fails to be executed, then all others are + * canceled, db session is not committed and an exception is + * thrown. + */ + List<ActiveRuleChange> activateAndCommit(DbSession dbSession, QProfileDto profile, Collection<RuleActivation> activations); + + /** + * Same as {@link #activateAndCommit(DbSession, QProfileDto, Collection)} except + * that: + * - rules are loaded from search engine + * - rules are activated with default parameters + * - an activation failure does not break others. No exception is thrown. + */ + BulkChangeResult bulkActivateAndCommit(DbSession dbSession, QProfileDto profile, RuleQuery ruleQuery, @Nullable String severity); + + List<ActiveRuleChange> deactivateAndCommit(DbSession dbSession, QProfileDto profile, Collection<Integer> ruleIds); + + BulkChangeResult bulkDeactivateAndCommit(DbSession dbSession, QProfileDto profile, RuleQuery ruleQuery); + + /** + * Delete a rule from all Quality profiles. Db session is not committed. As a + * consequence Elasticsearch indices are NOT updated. + */ + List<ActiveRuleChange> deleteRule(DbSession dbSession, RuleDefinitionDto rule); +} diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/qualityprofile/RuleActivation.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/qualityprofile/RuleActivation.java new file mode 100644 index 00000000000..106fd158b26 --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/qualityprofile/RuleActivation.java @@ -0,0 +1,91 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.qualityprofile; + +import com.google.common.base.Strings; +import java.util.HashMap; +import java.util.Map; +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; +import org.sonar.api.rule.Severity; + +/** + * The request for activation. + */ +@Immutable +public class RuleActivation { + + private final int ruleId; + private final boolean reset; + private final String severity; + private final Map<String, String> parameters = new HashMap<>(); + + private RuleActivation(int ruleId, boolean reset, @Nullable String severity, @Nullable Map<String, String> parameters) { + this.ruleId = ruleId; + this.reset = reset; + this.severity = severity; + if (severity != null && !Severity.ALL.contains(severity)) { + throw new IllegalArgumentException("Unknown severity: " + severity); + } + if (parameters != null) { + for (Map.Entry<String, String> entry : parameters.entrySet()) { + this.parameters.put(entry.getKey(), Strings.emptyToNull(entry.getValue())); + } + } + } + + public static RuleActivation createReset(int ruleId) { + return new RuleActivation(ruleId, true, null, null); + } + + public static RuleActivation create(int ruleId, @Nullable String severity, @Nullable Map<String, String> parameters) { + return new RuleActivation(ruleId, false, severity, parameters); + } + + public static RuleActivation create(int ruleId) { + return create(ruleId, null, null); + } + + /** + * Optional severity. Use the parent severity or default rule severity if null. + */ + @CheckForNull + public String getSeverity() { + return severity; + } + + public int getRuleId() { + return ruleId; + } + + @CheckForNull + public String getParameter(String key) { + return parameters.get(key); + } + + public boolean hasParameter(String key) { + return parameters.containsKey(key); + } + + public boolean isReset() { + return reset; + } +} diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/qualityprofile/package-info.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/qualityprofile/package-info.java new file mode 100644 index 00000000000..5a3c798af36 --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/qualityprofile/package-info.java @@ -0,0 +1,23 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.server.qualityprofile; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/setting/ProjectConfigurationLoader.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/setting/ProjectConfigurationLoader.java new file mode 100644 index 00000000000..e261763e6f5 --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/setting/ProjectConfigurationLoader.java @@ -0,0 +1,49 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.setting; + +import java.util.Collections; +import java.util.Map; +import java.util.Set; +import org.sonar.api.config.Configuration; +import org.sonar.db.DbSession; +import org.sonar.db.component.ComponentDto; + +import static java.lang.String.format; +import static java.util.Objects.requireNonNull; + +public interface ProjectConfigurationLoader { + /** + * Loads configuration for the specified components. + * + * <p> + * Returns the applicable component configuration with most specific configuration overriding more global ones + * (eg. global > project > branch). + * + * <p> + * Any component is accepted but SQ only supports specific properties for projects and branches. + */ + Map<String, Configuration> loadProjectConfigurations(DbSession dbSession, Set<ComponentDto> projects); + + default Configuration loadProjectConfiguration(DbSession dbSession, ComponentDto project) { + Map<String, Configuration> configurations = loadProjectConfigurations(dbSession, Collections.singleton(project)); + return requireNonNull(configurations.get(project.uuid()), () -> format("Configuration for project '%s' is not found", project.getKey())); + } +} diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/setting/ProjectConfigurationLoaderImpl.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/setting/ProjectConfigurationLoaderImpl.java new file mode 100644 index 00000000000..95e11242587 --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/setting/ProjectConfigurationLoaderImpl.java @@ -0,0 +1,73 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.setting; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; +import org.sonar.api.config.Configuration; +import org.sonar.api.config.Settings; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.component.ComponentDto; +import org.sonar.db.property.PropertyDto; + +import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex; + +public class ProjectConfigurationLoaderImpl implements ProjectConfigurationLoader { + private final Settings globalSettings; + private final DbClient dbClient; + + public ProjectConfigurationLoaderImpl(Settings globalSettings, DbClient dbClient) { + this.globalSettings = globalSettings; + this.dbClient = dbClient; + } + + @Override + public Map<String, Configuration> loadProjectConfigurations(DbSession dbSession, Set<ComponentDto> projects) { + Set<String> mainBranchDbKeys = projects.stream().map(ComponentDto::getKey).collect(Collectors.toSet()); + Map<String, ChildSettings> mainBranchSettingsByDbKey = loadMainBranchConfigurations(dbSession, mainBranchDbKeys); + return projects.stream() + .collect(uniqueIndex(ComponentDto::uuid, component -> { + if (component.getDbKey().equals(component.getKey())) { + return mainBranchSettingsByDbKey.get(component.getKey()).asConfiguration(); + } + + ChildSettings settings = new ChildSettings(mainBranchSettingsByDbKey.get(component.getKey())); + dbClient.propertiesDao() + .selectProjectProperties(dbSession, component.getDbKey()) + .forEach(property -> settings.setProperty(property.getKey(), property.getValue())); + return settings.asConfiguration(); + })); + } + + private Map<String, ChildSettings> loadMainBranchConfigurations(DbSession dbSession, Set<String> dbKeys) { + return dbKeys.stream().collect(uniqueIndex(Function.identity(), dbKey -> { + ChildSettings settings = new ChildSettings(globalSettings); + List<PropertyDto> propertyDtos = dbClient.propertiesDao() + .selectProjectProperties(dbSession, dbKey); + propertyDtos + .forEach(property -> settings.setProperty(property.getKey(), property.getValue())); + return settings; + })); + } +} diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/setting/SettingsChangeNotifier.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/setting/SettingsChangeNotifier.java new file mode 100644 index 00000000000..de074ef5232 --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/setting/SettingsChangeNotifier.java @@ -0,0 +1,46 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.setting; + +import com.google.common.annotations.VisibleForTesting; +import org.sonar.api.config.GlobalPropertyChangeHandler; + +import javax.annotation.Nullable; + +public class SettingsChangeNotifier { + + @VisibleForTesting + GlobalPropertyChangeHandler[] changeHandlers; + + public SettingsChangeNotifier(GlobalPropertyChangeHandler[] changeHandlers) { + this.changeHandlers = changeHandlers; + } + + public SettingsChangeNotifier() { + this(new GlobalPropertyChangeHandler[0]); + } + + public void onGlobalPropertyChange(String key, @Nullable String value) { + GlobalPropertyChangeHandler.PropertyChange change = GlobalPropertyChangeHandler.PropertyChange.create(key, value); + for (GlobalPropertyChangeHandler changeHandler : changeHandlers) { + changeHandler.onChange(change); + } + } +} diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/setting/package-info.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/setting/package-info.java new file mode 100644 index 00000000000..e816bf4051c --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/setting/package-info.java @@ -0,0 +1,23 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.server.setting; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/telemetry/LicenseReader.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/telemetry/LicenseReader.java new file mode 100644 index 00000000000..22029d62a36 --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/telemetry/LicenseReader.java @@ -0,0 +1,32 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.telemetry; + +import java.util.Optional; +import org.sonar.api.server.ServerSide; + +@ServerSide +public interface LicenseReader { + Optional<License> read(); + + interface License { + String getType(); + } +} diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/telemetry/package-info.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/telemetry/package-info.java new file mode 100644 index 00000000000..ce3724b93a5 --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/telemetry/package-info.java @@ -0,0 +1,23 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.server.telemetry; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/util/BooleanTypeValidation.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/util/BooleanTypeValidation.java new file mode 100644 index 00000000000..1b3ade32e60 --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/util/BooleanTypeValidation.java @@ -0,0 +1,42 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.util; + +import java.util.List; +import javax.annotation.Nullable; +import org.apache.commons.lang.StringUtils; +import org.sonar.api.PropertyType; + +import static org.sonar.server.exceptions.BadRequestException.checkRequest; + +public class BooleanTypeValidation implements TypeValidation { + + @Override + public String key() { + return PropertyType.BOOLEAN.name(); + } + + @Override + public void validate(String value, @Nullable List<String> options) { + checkRequest(StringUtils.equalsIgnoreCase(value, "true") || StringUtils.equalsIgnoreCase(value, "false"), + "Value '%s' must be one of \"true\" or \"false\".", value); + } + +} diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/util/FloatTypeValidation.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/util/FloatTypeValidation.java new file mode 100644 index 00000000000..fd221dced55 --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/util/FloatTypeValidation.java @@ -0,0 +1,45 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.util; + +import java.util.List; +import javax.annotation.Nullable; +import org.sonar.api.PropertyType; +import org.sonar.server.exceptions.BadRequestException; + +import static java.lang.String.format; + +public class FloatTypeValidation implements TypeValidation { + + @Override + public String key() { + return PropertyType.FLOAT.name(); + } + + @Override + public void validate(String value, @Nullable List<String> options) { + try { + Double.parseDouble(value); + } catch (NumberFormatException e) { + throw BadRequestException.create(format("Value '%s' must be an floating point number.", value)); + } + } + +} diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/util/GlobalLockManager.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/util/GlobalLockManager.java new file mode 100644 index 00000000000..81f13b22a1d --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/util/GlobalLockManager.java @@ -0,0 +1,59 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.util; + +import org.sonar.api.ce.ComputeEngineSide; +import org.sonar.api.server.ServerSide; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; + +/** + * Provide a simple mechanism to manage global locks across multiple nodes running in a cluster. + * In the target use case multiple nodes try to execute something at around the same time, + * and only the first should succeed, and the rest do nothing. + */ +@ComputeEngineSide +@ServerSide +public class GlobalLockManager { + + static final int DEFAULT_LOCK_DURATION_SECONDS = 180; + + private final DbClient dbClient; + + public GlobalLockManager(DbClient dbClient) { + this.dbClient = dbClient; + } + + /** + * Try to acquire a lock on the given name in the default namespace, + * using the generic locking mechanism of {@see org.sonar.db.property.InternalPropertiesDao}. + */ + public boolean tryLock(String name) { + return tryLock(name, DEFAULT_LOCK_DURATION_SECONDS); + } + + public boolean tryLock(String name, int durationSecond) { + try (DbSession dbSession = dbClient.openSession(false)) { + boolean success = dbClient.internalPropertiesDao().tryLock(dbSession, name, durationSecond); + dbSession.commit(); + return success; + } + } +} diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/util/IntegerTypeValidation.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/util/IntegerTypeValidation.java new file mode 100644 index 00000000000..84502df47f7 --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/util/IntegerTypeValidation.java @@ -0,0 +1,45 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.util; + +import java.util.List; +import javax.annotation.Nullable; +import org.sonar.api.PropertyType; +import org.sonar.server.exceptions.BadRequestException; + +import static java.lang.String.format; + +public class IntegerTypeValidation implements TypeValidation { + + @Override + public String key() { + return PropertyType.INTEGER.name(); + } + + @Override + public void validate(String value, @Nullable List<String> options) { + try { + Integer.parseInt(value); + } catch (NumberFormatException e) { + throw BadRequestException.create(format("Value '%s' must be an integer.", value)); + } + } + +} diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/util/LongTypeValidation.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/util/LongTypeValidation.java new file mode 100644 index 00000000000..9ca540a1d1d --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/util/LongTypeValidation.java @@ -0,0 +1,43 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.util; + +import java.util.List; +import javax.annotation.Nullable; +import org.sonar.api.PropertyType; +import org.sonar.server.exceptions.BadRequestException; + +import static java.lang.String.format; + +public class LongTypeValidation implements TypeValidation { + @Override + public String key() { + return PropertyType.LONG.name(); + } + + @Override + public void validate(String value, @Nullable List<String> options) { + try { + Long.parseLong(value); + } catch (NumberFormatException e) { + throw BadRequestException.create(format("Value '%s' must be a long.", value)); + } + } +} diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/util/MetricLevelTypeValidation.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/util/MetricLevelTypeValidation.java new file mode 100644 index 00000000000..ba8598a7786 --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/util/MetricLevelTypeValidation.java @@ -0,0 +1,44 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.util; + +import java.util.List; +import javax.annotation.Nullable; +import org.sonar.api.PropertyType; +import org.sonar.api.measures.Metric; +import org.sonar.server.exceptions.BadRequestException; + +import static java.lang.String.format; + +public class MetricLevelTypeValidation implements TypeValidation { + @Override + public String key() { + return PropertyType.METRIC_LEVEL.name(); + } + + @Override + public void validate(String value, @Nullable List<String> options) { + try { + Metric.Level.valueOf(value); + } catch (IllegalArgumentException e) { + throw BadRequestException.create(format("Value '%s' must be one of \"OK\", \"ERROR\".", value)); + } + } +} diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/util/StringListTypeValidation.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/util/StringListTypeValidation.java new file mode 100644 index 00000000000..acb1c7dca8f --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/util/StringListTypeValidation.java @@ -0,0 +1,41 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.util; + +import java.util.List; +import javax.annotation.Nullable; +import org.apache.commons.lang.StringUtils; +import org.sonar.api.PropertyType; + +import static org.sonar.server.exceptions.BadRequestException.checkRequest; + +public class StringListTypeValidation implements TypeValidation { + + @Override + public String key() { + return PropertyType.SINGLE_SELECT_LIST.name(); + } + + @Override + public void validate(String value, @Nullable List<String> options) { + checkRequest(options == null || options.contains(value), "Value '%s' must be one of : %s.", value, StringUtils.join(options, ", ")); + } + +} diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/util/StringTypeValidation.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/util/StringTypeValidation.java new file mode 100644 index 00000000000..3851ede0e81 --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/util/StringTypeValidation.java @@ -0,0 +1,40 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.util; + +import org.sonar.api.PropertyType; + +import javax.annotation.Nullable; + +import java.util.List; + +public class StringTypeValidation implements TypeValidation { + + @Override + public String key() { + return PropertyType.STRING.name(); + } + + @Override + public void validate(String value, @Nullable List<String> options) { + // Nothing to do + } + +} diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/util/TextTypeValidation.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/util/TextTypeValidation.java new file mode 100644 index 00000000000..c26ad125162 --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/util/TextTypeValidation.java @@ -0,0 +1,40 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.util; + +import org.sonar.api.PropertyType; + +import javax.annotation.Nullable; + +import java.util.List; + +public class TextTypeValidation implements TypeValidation { + + @Override + public String key() { + return PropertyType.TEXT.name(); + } + + @Override + public void validate(String value, @Nullable List<String> options) { + // Nothing to do + } + +} diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/util/TypeValidation.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/util/TypeValidation.java new file mode 100644 index 00000000000..797a3438d8f --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/util/TypeValidation.java @@ -0,0 +1,34 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.util; + +import org.sonar.api.server.ServerSide; + +import javax.annotation.Nullable; + +import java.util.List; + +@ServerSide +public interface TypeValidation { + + String key(); + + void validate(String value, @Nullable List<String> options); +} diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/util/TypeValidationModule.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/util/TypeValidationModule.java new file mode 100644 index 00000000000..3cc4f038e5e --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/util/TypeValidationModule.java @@ -0,0 +1,39 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.util; + +import org.sonar.core.platform.Module; + +public class TypeValidationModule extends Module { + @Override + protected void configureModule() { + add( + TypeValidations.class, + IntegerTypeValidation.class, + FloatTypeValidation.class, + BooleanTypeValidation.class, + TextTypeValidation.class, + StringTypeValidation.class, + StringListTypeValidation.class, + LongTypeValidation.class, + MetricLevelTypeValidation.class + ); + } +} diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/util/TypeValidations.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/util/TypeValidations.java new file mode 100644 index 00000000000..2c8883b77eb --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/util/TypeValidations.java @@ -0,0 +1,70 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.util; + +import com.google.common.base.Predicate; +import com.google.common.collect.Iterables; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.sonar.api.server.ServerSide; + +import static org.sonar.server.exceptions.BadRequestException.checkRequest; + +@ServerSide +public class TypeValidations { + + private final List<TypeValidation> typeValidationList; + + public TypeValidations(List<TypeValidation> typeValidationList) { + this.typeValidationList = typeValidationList; + } + + public void validate(List<String> values, String type, List<String> options) { + TypeValidation typeValidation = findByKey(type); + for (String value : values) { + typeValidation.validate(value, options); + } + } + + public void validate(String value, String type, @Nullable List<String> options) { + TypeValidation typeValidation = findByKey(type); + typeValidation.validate(value, options); + } + + private TypeValidation findByKey(String key) { + TypeValidation typeValidation = Iterables.find(typeValidationList, new TypeValidationMatchKey(key), null); + checkRequest(typeValidation != null, "Type '%s' is not valid.", key); + return typeValidation; + } + + private static class TypeValidationMatchKey implements Predicate<TypeValidation> { + private final String key; + + public TypeValidationMatchKey(String key) { + this.key = key; + } + + @Override + public boolean apply(@Nonnull TypeValidation input) { + return input.key().equals(key); + } + } +} diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/util/Validation.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/util/Validation.java new file mode 100644 index 00000000000..99d28d0bc96 --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/util/Validation.java @@ -0,0 +1,33 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.util; + +public class Validation { + + public static final String CANT_BE_EMPTY_MESSAGE = "%s can't be empty"; + public static final String IS_TOO_SHORT_MESSAGE = "%s is too short (minimum is %s characters)"; + public static final String IS_TOO_LONG_MESSAGE = "%s is too long (maximum is %s characters)"; + public static final String IS_ALREADY_USED_MESSAGE = "%s has already been taken"; + + private Validation() { + // only static methods + } + +} diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/util/package-info.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/util/package-info.java new file mode 100644 index 00000000000..8fd4b5b3582 --- /dev/null +++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/util/package-info.java @@ -0,0 +1,24 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.server.util; + +import javax.annotation.ParametersAreNonnullByDefault; + diff --git a/server/sonar-webserver-api/src/test/java/org/sonar/server/app/ProcessCommandWrapperImplTest.java b/server/sonar-webserver-api/src/test/java/org/sonar/server/app/ProcessCommandWrapperImplTest.java new file mode 100644 index 00000000000..de5f24347fc --- /dev/null +++ b/server/sonar-webserver-api/src/test/java/org/sonar/server/app/ProcessCommandWrapperImplTest.java @@ -0,0 +1,158 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.app; + +import java.io.File; +import java.io.IOException; +import java.util.Random; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.config.internal.MapSettings; +import org.sonar.process.sharedmemoryfile.DefaultProcessCommands; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.sonar.process.ProcessEntryPoint.PROPERTY_PROCESS_INDEX; +import static org.sonar.process.ProcessEntryPoint.PROPERTY_SHARED_PATH; + +public class ProcessCommandWrapperImplTest { + private static final int PROCESS_NUMBER = 2; + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private MapSettings settings = new MapSettings(); + + @Test + public void requestSQRestart_throws_IAE_if_process_index_property_not_set() { + ProcessCommandWrapperImpl processCommandWrapper = new ProcessCommandWrapperImpl(settings.asConfig()); + + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Property process.index is not set"); + + processCommandWrapper.requestSQRestart(); + } + + @Test + public void requestSQRestart_throws_IAE_if_process_shared_path_property_not_set() { + settings.setProperty(PROPERTY_PROCESS_INDEX, 1); + ProcessCommandWrapperImpl processCommandWrapper = new ProcessCommandWrapperImpl(settings.asConfig()); + + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Property process.sharedDir is not set"); + + processCommandWrapper.requestSQRestart(); + } + + @Test + public void requestSQRestart_updates_shareMemory_file() throws IOException { + File tmpDir = temp.newFolder().getAbsoluteFile(); + settings.setProperty(PROPERTY_SHARED_PATH, tmpDir.getAbsolutePath()); + settings.setProperty(PROPERTY_PROCESS_INDEX, PROCESS_NUMBER); + + ProcessCommandWrapperImpl underTest = new ProcessCommandWrapperImpl(settings.asConfig()); + underTest.requestSQRestart(); + + try (DefaultProcessCommands processCommands = DefaultProcessCommands.secondary(tmpDir, PROCESS_NUMBER)) { + assertThat(processCommands.askedForRestart()).isTrue(); + } + } + + @Test + public void requestSQStop_throws_IAE_if_process_shared_path_property_not_set() { + settings.setProperty(PROPERTY_PROCESS_INDEX, 1); + ProcessCommandWrapperImpl processCommandWrapper = new ProcessCommandWrapperImpl(settings.asConfig()); + + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Property process.sharedDir is not set"); + + processCommandWrapper.requestHardStop(); + } + + @Test + public void requestSQStop_updates_shareMemory_file() throws IOException { + File tmpDir = temp.newFolder().getAbsoluteFile(); + settings.setProperty(PROPERTY_SHARED_PATH, tmpDir.getAbsolutePath()); + settings.setProperty(PROPERTY_PROCESS_INDEX, PROCESS_NUMBER); + + ProcessCommandWrapperImpl underTest = new ProcessCommandWrapperImpl(settings.asConfig()); + underTest.requestHardStop(); + + try (DefaultProcessCommands processCommands = DefaultProcessCommands.secondary(tmpDir, PROCESS_NUMBER)) { + assertThat(processCommands.askedForHardStop()).isTrue(); + } + } + + @Test + public void notifyOperational_throws_IAE_if_process_sharedDir_property_not_set() { + settings.setProperty(PROPERTY_PROCESS_INDEX, 1); + ProcessCommandWrapperImpl processCommandWrapper = new ProcessCommandWrapperImpl(settings.asConfig()); + + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Property process.sharedDir is not set"); + + processCommandWrapper.notifyOperational(); + } + + @Test + public void notifyOperational_throws_IAE_if_process_index_property_not_set() { + ProcessCommandWrapperImpl processCommandWrapper = new ProcessCommandWrapperImpl(settings.asConfig()); + + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Property process.index is not set"); + + processCommandWrapper.notifyOperational(); + } + + @Test + public void notifyOperational_updates_shareMemory_file() throws IOException { + File tmpDir = temp.newFolder().getAbsoluteFile(); + settings.setProperty(PROPERTY_SHARED_PATH, tmpDir.getAbsolutePath()); + settings.setProperty(PROPERTY_PROCESS_INDEX, PROCESS_NUMBER); + + ProcessCommandWrapperImpl underTest = new ProcessCommandWrapperImpl(settings.asConfig()); + underTest.notifyOperational(); + + try (DefaultProcessCommands processCommands = DefaultProcessCommands.secondary(tmpDir, PROCESS_NUMBER)) { + assertThat(processCommands.isOperational()).isTrue(); + } + } + + @Test + public void isCeOperational_reads_shared_memory_operational_flag_in_location_3() throws IOException { + File tmpDir = temp.newFolder().getAbsoluteFile(); + settings.setProperty(PROPERTY_SHARED_PATH, tmpDir.getAbsolutePath()); + + boolean expected = new Random().nextBoolean(); + if (expected) { + try (DefaultProcessCommands processCommands = DefaultProcessCommands.secondary(tmpDir, 3)) { + processCommands.setOperational(); + } + } + + ProcessCommandWrapperImpl underTest = new ProcessCommandWrapperImpl(settings.asConfig()); + + assertThat(underTest.isCeOperational()).isEqualTo(expected); + } + +} diff --git a/server/sonar-webserver-api/src/test/java/org/sonar/server/branch/BranchFeatureProxyImplTest.java b/server/sonar-webserver-api/src/test/java/org/sonar/server/branch/BranchFeatureProxyImplTest.java new file mode 100644 index 00000000000..6fb3d9a6449 --- /dev/null +++ b/server/sonar-webserver-api/src/test/java/org/sonar/server/branch/BranchFeatureProxyImplTest.java @@ -0,0 +1,48 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.branch; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class BranchFeatureProxyImplTest { + + private BranchFeatureExtension branchFeatureExtension = mock(BranchFeatureExtension.class); + + @Test + public void return_false_when_no_extension() { + assertThat(new BranchFeatureProxyImpl().isEnabled()).isFalse(); + } + + @Test + public void return_false_when_extension_returns_false() { + when(branchFeatureExtension.isEnabled()).thenReturn(false); + assertThat(new BranchFeatureProxyImpl(branchFeatureExtension).isEnabled()).isFalse(); + } + + @Test + public void return_true_when_extension_returns_ftrue() { + when(branchFeatureExtension.isEnabled()).thenReturn(true); + assertThat(new BranchFeatureProxyImpl(branchFeatureExtension).isEnabled()).isTrue(); + } +} diff --git a/server/sonar-webserver-api/src/test/java/org/sonar/server/branch/BranchFeatureRule.java b/server/sonar-webserver-api/src/test/java/org/sonar/server/branch/BranchFeatureRule.java new file mode 100644 index 00000000000..2bf56463474 --- /dev/null +++ b/server/sonar-webserver-api/src/test/java/org/sonar/server/branch/BranchFeatureRule.java @@ -0,0 +1,46 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.branch; + +import org.junit.rules.ExternalResource; + +public class BranchFeatureRule extends ExternalResource implements BranchFeatureProxy { + + private boolean enabled; + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + @Override + protected void after() { + reset(); + } + + public void reset() { + this.enabled = false; + } + + @Override + public boolean isEnabled() { + return enabled; + } + +} diff --git a/server/sonar-webserver-api/src/test/java/org/sonar/server/exceptions/BadRequestExceptionTest.java b/server/sonar-webserver-api/src/test/java/org/sonar/server/exceptions/BadRequestExceptionTest.java new file mode 100644 index 00000000000..0dc82c559f0 --- /dev/null +++ b/server/sonar-webserver-api/src/test/java/org/sonar/server/exceptions/BadRequestExceptionTest.java @@ -0,0 +1,85 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.exceptions; + +import java.util.Collections; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; + +public class BadRequestExceptionTest { + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + @Test + public void text_error() { + BadRequestException exception = BadRequestException.create("error"); + assertThat(exception.getMessage()).isEqualTo("error"); + } + + @Test + public void create_exception_from_list() { + BadRequestException underTest = BadRequestException.create(asList("error1", "error2")); + + assertThat(underTest.errors()).containsOnly("error1", "error2"); + } + + @Test + public void create_exception_from_var_args() { + BadRequestException underTest = BadRequestException.create("error1", "error2"); + + assertThat(underTest.errors()).containsOnly("error1", "error2"); + } + + @Test + public void getMessage_return_first_error() { + BadRequestException underTest = BadRequestException.create(asList("error1", "error2")); + + assertThat(underTest.getMessage()).isEqualTo("error1"); + } + + @Test + public void fail_when_creating_exception_with_empty_list() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("At least one error message is required"); + + BadRequestException.create(Collections.emptyList()); + } + + @Test + public void fail_when_creating_exception_with_one_empty_element() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Message cannot be empty"); + + BadRequestException.create(asList("error", "")); + } + + @Test + public void fail_when_creating_exception_with_one_null_element() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Message cannot be empty"); + + BadRequestException.create(asList("error", null)); + } +} diff --git a/server/sonar-webserver-api/src/test/java/org/sonar/server/exceptions/MessageTest.java b/server/sonar-webserver-api/src/test/java/org/sonar/server/exceptions/MessageTest.java new file mode 100644 index 00000000000..f05bb55058a --- /dev/null +++ b/server/sonar-webserver-api/src/test/java/org/sonar/server/exceptions/MessageTest.java @@ -0,0 +1,88 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.exceptions; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import static org.assertj.core.api.Assertions.assertThat; + +public class MessageTest { + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + @Test + public void create_message() { + Message message = Message.of("key1 %s", "param1"); + assertThat(message.getMessage()).isEqualTo("key1 param1"); + } + + @Test + public void create_message_without_params() { + Message message = Message.of("key1"); + assertThat(message.getMessage()).isEqualTo("key1"); + } + + @Test + public void fail_when_message_is_null() { + expectedException.expect(IllegalArgumentException.class); + + Message.of(null); + } + + @Test + public void fail_when_message_is_empty() { + expectedException.expect(IllegalArgumentException.class); + + Message.of(""); + } + + @Test + public void test_equals_and_hashcode() { + Message message1 = Message.of("key1%s", "param1"); + Message message2 = Message.of("key2%s", "param2"); + Message message3 = Message.of("key1"); + Message message4 = Message.of("key1%s", "param2"); + Message sameAsMessage1 = Message.of("key1%s", "param1"); + + assertThat(message1).isEqualTo(message1); + assertThat(message1).isNotEqualTo(message2); + assertThat(message1).isNotEqualTo(message3); + assertThat(message1).isNotEqualTo(message4); + assertThat(message1).isEqualTo(sameAsMessage1); + assertThat(message1).isNotEqualTo(null); + assertThat(message1).isNotEqualTo(new Object()); + + assertThat(message1.hashCode()).isEqualTo(message1.hashCode()); + assertThat(message1.hashCode()).isNotEqualTo(message2.hashCode()); + assertThat(message1.hashCode()).isNotEqualTo(message3.hashCode()); + assertThat(message1.hashCode()).isNotEqualTo(message4.hashCode()); + assertThat(message1.hashCode()).isEqualTo(sameAsMessage1.hashCode()); + } + + @Test + public void to_string() { + assertThat(Message.of("key1 %s", "param1").toString()).isEqualTo("key1 param1"); + assertThat(Message.of("key1").toString()).isEqualTo("key1"); + assertThat(Message.of("key1", (Object[])null).toString()).isEqualTo("key1"); + } +} diff --git a/server/sonar-webserver-api/src/test/java/org/sonar/server/exceptions/ServerExceptionTest.java b/server/sonar-webserver-api/src/test/java/org/sonar/server/exceptions/ServerExceptionTest.java new file mode 100644 index 00000000000..ae2ce7846db --- /dev/null +++ b/server/sonar-webserver-api/src/test/java/org/sonar/server/exceptions/ServerExceptionTest.java @@ -0,0 +1,40 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.exceptions; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ServerExceptionTest { + + @Test + public void should_create_exception_with_status() { + ServerException exception = new ServerException(400, "error!"); + assertThat(exception.httpCode()).isEqualTo(400); + } + + @Test + public void should_create_exception_with_status_and_message() { + ServerException exception = new ServerException(404, "Not found"); + assertThat(exception.httpCode()).isEqualTo(404); + assertThat(exception.getMessage()).isEqualTo("Not found"); + } +} diff --git a/server/sonar-webserver-api/src/test/java/org/sonar/server/health/TestStandaloneHealthChecker.java b/server/sonar-webserver-api/src/test/java/org/sonar/server/health/TestStandaloneHealthChecker.java new file mode 100644 index 00000000000..27c2e469dde --- /dev/null +++ b/server/sonar-webserver-api/src/test/java/org/sonar/server/health/TestStandaloneHealthChecker.java @@ -0,0 +1,39 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.health; + +public class TestStandaloneHealthChecker implements HealthChecker { + + private Health health = Health.newHealthCheckBuilder().setStatus(Health.Status.GREEN).build(); + + public void setHealth(Health h) { + this.health = h; + } + + @Override + public Health checkNode() { + return health; + } + + @Override + public ClusterHealth checkCluster() { + throw new IllegalStateException(); + } +} diff --git a/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/PluginDownloaderTest.java b/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/PluginDownloaderTest.java new file mode 100644 index 00000000000..f984cd9bd1e --- /dev/null +++ b/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/PluginDownloaderTest.java @@ -0,0 +1,322 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.plugins; + +import com.google.common.base.Optional; +import java.io.File; +import java.net.URI; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.rules.TemporaryFolder; +import org.mockito.ArgumentMatcher; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.sonar.api.utils.HttpDownloader; +import org.sonar.core.platform.PluginInfo; +import org.sonar.server.exceptions.BadRequestException; +import org.sonar.server.platform.ServerFileSystem; +import org.sonar.updatecenter.common.Plugin; +import org.sonar.updatecenter.common.Release; +import org.sonar.updatecenter.common.UpdateCenter; +import org.sonar.updatecenter.common.Version; + +import static com.google.common.collect.Lists.newArrayList; +import static org.apache.commons.io.FileUtils.copyFileToDirectory; +import static org.apache.commons.io.FileUtils.touch; +import static org.apache.commons.io.FilenameUtils.separatorsToUnix; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.sonar.updatecenter.common.Version.create; + +public class PluginDownloaderTest { + + @Rule + public TemporaryFolder testFolder = new TemporaryFolder(); + @Rule + public ExpectedException expectedException = ExpectedException.none(); + private File downloadDir; + private UpdateCenterMatrixFactory updateCenterMatrixFactory; + private UpdateCenter updateCenter; + private HttpDownloader httpDownloader; + private PluginDownloader pluginDownloader; + + @Before + public void before() throws Exception { + updateCenterMatrixFactory = mock(UpdateCenterMatrixFactory.class); + updateCenter = mock(UpdateCenter.class); + when(updateCenterMatrixFactory.getUpdateCenter(anyBoolean())).thenReturn(Optional.of(updateCenter)); + + httpDownloader = mock(HttpDownloader.class); + doAnswer(new Answer<Void>() { + @Override + public Void answer(InvocationOnMock inv) throws Throwable { + File toFile = (File) inv.getArguments()[1]; + touch(toFile); + return null; + } + }).when(httpDownloader).download(any(URI.class), any(File.class)); + + ServerFileSystem fs = mock(ServerFileSystem.class); + downloadDir = testFolder.newFolder("downloads"); + when(fs.getDownloadedPluginsDir()).thenReturn(downloadDir); + + pluginDownloader = new PluginDownloader(updateCenterMatrixFactory, httpDownloader, fs); + } + + @After + public void stop() { + pluginDownloader.stop(); + } + + @Test + public void clean_temporary_files_at_startup() throws Exception { + touch(new File(downloadDir, "sonar-php.jar")); + touch(new File(downloadDir, "sonar-js.jar.tmp")); + assertThat(downloadDir.listFiles()).hasSize(2); + pluginDownloader.start(); + + File[] files = downloadDir.listFiles(); + assertThat(files).hasSize(1); + assertThat(files[0].getName()).isEqualTo("sonar-php.jar"); + } + + @Test + public void download_from_url() { + Plugin test = Plugin.factory("test"); + Release test10 = new Release(test, "1.0").setDownloadUrl("http://server/test-1.0.jar"); + test.addRelease(test10); + + when(updateCenter.findInstallablePlugins("foo", create("1.0"))).thenReturn(newArrayList(test10)); + + pluginDownloader.start(); + pluginDownloader.download("foo", create("1.0")); + + // SONAR-4523: do not corrupt JAR files when restarting the server while a plugin is being downloaded. + // The JAR file is downloaded in a temp file + verify(httpDownloader).download(any(URI.class), argThat(new HasFileName("test-1.0.jar.tmp"))); + assertThat(new File(downloadDir, "test-1.0.jar")).exists(); + assertThat(new File(downloadDir, "test-1.0.jar.tmp")).doesNotExist(); + } + + @Test + public void download_when_update_center_is_unavailable_with_no_exception_thrown() { + when(updateCenterMatrixFactory.getUpdateCenter(anyBoolean())).thenReturn(Optional.absent()); + + Plugin test = Plugin.factory("test"); + Release test10 = new Release(test, "1.0").setDownloadUrl("http://server/test-1.0.jar"); + test.addRelease(test10); + + pluginDownloader.start(); + pluginDownloader.download("foo", create("1.0")); + } + + /** + * SONAR-4685 + */ + @Test + public void download_from_redirect_url() { + Plugin test = Plugin.factory("plugintest"); + Release test10 = new Release(test, "1.0").setDownloadUrl("http://server/redirect?r=release&g=test&a=test&v=1.0&e=jar"); + test.addRelease(test10); + + when(updateCenter.findInstallablePlugins("foo", create("1.0"))).thenReturn(newArrayList(test10)); + + pluginDownloader.start(); + pluginDownloader.download("foo", create("1.0")); + + // SONAR-4523: do not corrupt JAR files when restarting the server while a plugin is being downloaded. + // The JAR file is downloaded in a temp file + verify(httpDownloader).download(any(URI.class), argThat(new HasFileName("plugintest-1.0.jar.tmp"))); + assertThat(new File(downloadDir, "plugintest-1.0.jar")).exists(); + assertThat(new File(downloadDir, "plugintest-1.0.jar.tmp")).doesNotExist(); + } + + @Test + public void throw_exception_if_download_dir_is_invalid() throws Exception { + ServerFileSystem fs = mock(ServerFileSystem.class); + // download dir is a file instead of being a directory + File downloadDir = testFolder.newFile(); + when(fs.getDownloadedPluginsDir()).thenReturn(downloadDir); + + pluginDownloader = new PluginDownloader(updateCenterMatrixFactory, httpDownloader, fs); + try { + pluginDownloader.start(); + fail(); + } catch (IllegalStateException e) { + // ok + } + } + + @Test + public void fail_if_no_compatible_plugin_found() { + expectedException.expect(BadRequestException.class); + + pluginDownloader.download("foo", create("1.0")); + } + + @Test + public void download_from_file() throws Exception { + Plugin test = Plugin.factory("test"); + File file = testFolder.newFile("test-1.0.jar"); + file.createNewFile(); + Release test10 = new Release(test, "1.0").setDownloadUrl("file://" + separatorsToUnix(file.getCanonicalPath())); + test.addRelease(test10); + + when(updateCenter.findInstallablePlugins("foo", create("1.0"))).thenReturn(newArrayList(test10)); + + pluginDownloader.start(); + pluginDownloader.download("foo", create("1.0")); + verify(httpDownloader, never()).download(any(URI.class), any(File.class)); + assertThat(noDownloadedFiles()).isGreaterThan(0); + } + + @Test + public void throw_exception_if_could_not_download() { + Plugin test = Plugin.factory("test"); + Release test10 = new Release(test, "1.0").setDownloadUrl("file://not_found"); + test.addRelease(test10); + + when(updateCenter.findInstallablePlugins("foo", create("1.0"))).thenReturn(newArrayList(test10)); + + pluginDownloader.start(); + try { + pluginDownloader.download("foo", create("1.0")); + fail(); + } catch (IllegalStateException e) { + // ok + } + } + + @Test + public void throw_exception_if_download_fail() { + Plugin test = Plugin.factory("test"); + Release test10 = new Release(test, "1.0").setDownloadUrl("http://server/test-1.0.jar"); + test.addRelease(test10); + when(updateCenter.findInstallablePlugins("foo", create("1.0"))).thenReturn(newArrayList(test10)); + + doThrow(new RuntimeException()).when(httpDownloader).download(any(URI.class), any(File.class)); + + pluginDownloader.start(); + try { + pluginDownloader.download("foo", create("1.0")); + fail(); + } catch (IllegalStateException e) { + // ok + } + } + + @Test + public void read_download_folder() throws Exception { + pluginDownloader.start(); + assertThat(noDownloadedFiles()).isZero(); + + copyFileToDirectory(TestProjectUtils.jarOf("test-base-plugin"), downloadDir); + + assertThat(pluginDownloader.getDownloadedPlugins()).hasSize(1); + PluginInfo info = pluginDownloader.getDownloadedPlugins().iterator().next(); + assertThat(info.getKey()).isEqualTo("testbase"); + assertThat(info.getName()).isEqualTo("Base Plugin"); + assertThat(info.getVersion()).isEqualTo(Version.create("0.1-SNAPSHOT")); + assertThat(info.getMainClass()).isEqualTo("BasePlugin"); + } + + @Test + public void getDownloadedPluginFilenames_reads_plugin_info_of_files_in_download_folder() throws Exception { + pluginDownloader.start(); + assertThat(pluginDownloader.getDownloadedPlugins()).hasSize(0); + + File file1 = new File(downloadDir, "file1.jar"); + file1.createNewFile(); + File file2 = new File(downloadDir, "file2.jar"); + file2.createNewFile(); + + assertThat(noDownloadedFiles()).isEqualTo(2); + } + + @Test + public void cancel_downloads() throws Exception { + File file1 = new File(downloadDir, "file1.jar"); + file1.createNewFile(); + File file2 = new File(downloadDir, "file2.jar"); + file2.createNewFile(); + + pluginDownloader.start(); + assertThat(noDownloadedFiles()).isGreaterThan(0); + pluginDownloader.cancelDownloads(); + assertThat(noDownloadedFiles()).isZero(); + } + + private int noDownloadedFiles() { + return downloadDir.listFiles((file, name) -> name.endsWith(".jar")).length; + } + + // SONAR-5011 + @Test + public void download_common_transitive_dependency() { + Plugin test1 = Plugin.factory("test1"); + Release test1R = new Release(test1, "1.0").setDownloadUrl("http://server/test1-1.0.jar"); + test1.addRelease(test1R); + + Plugin test2 = Plugin.factory("test2"); + Release test2R = new Release(test2, "1.0").setDownloadUrl("http://server/test2-1.0.jar"); + test2.addRelease(test2R); + + Plugin testDep = Plugin.factory("testdep"); + Release testDepR = new Release(testDep, "1.0").setDownloadUrl("http://server/testdep-1.0.jar"); + testDep.addRelease(testDepR); + + when(updateCenter.findInstallablePlugins("test1", create("1.0"))).thenReturn(newArrayList(test1R, testDepR)); + when(updateCenter.findInstallablePlugins("test2", create("1.0"))).thenReturn(newArrayList(test2R, testDepR)); + + pluginDownloader.start(); + pluginDownloader.download("test1", create("1.0")); + pluginDownloader.download("test2", create("1.0")); + + assertThat(new File(downloadDir, "test1-1.0.jar")).exists(); + assertThat(new File(downloadDir, "test2-1.0.jar")).exists(); + assertThat(new File(downloadDir, "testdep-1.0.jar")).exists(); + } + + class HasFileName implements ArgumentMatcher<File> { + private final String name; + + HasFileName(String name) { + this.name = name; + } + + @Override + public boolean matches(File file) { + return file.getName().equals(name); + } + } + +} diff --git a/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/PluginFileSystemTest.java b/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/PluginFileSystemTest.java new file mode 100644 index 00000000000..bc353f99778 --- /dev/null +++ b/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/PluginFileSystemTest.java @@ -0,0 +1,142 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.plugins; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang.RandomStringUtils; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.config.internal.MapSettings; +import org.sonar.core.platform.PluginInfo; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.sonar.server.plugins.PluginFileSystem.PROPERTY_PLUGIN_COMPRESSION_ENABLE; + +public class PluginFileSystemTest { + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + private MapSettings settings = new MapSettings(); + private Path targetJarPath; + private Path targetFolder; + private Path sourceFolder; + + @Before + public void setUp() throws IOException { + sourceFolder = temp.newFolder("source").toPath(); + targetFolder = temp.newFolder("target").toPath(); + targetJarPath = targetFolder.resolve("test.jar"); + Files.createFile(targetJarPath); + } + + @Test + public void add_plugin_to_list_of_installed_plugins() throws IOException { + File jar = touch(temp.newFolder(), "sonar-foo-plugin.jar"); + PluginInfo info = new PluginInfo("foo"); + + PluginFileSystem underTest = new PluginFileSystem(settings.asConfig()); + underTest.addInstalledPlugin(info, jar); + + assertThat(underTest.getInstalledFiles()).hasSize(1); + InstalledPlugin installedPlugin = underTest.getInstalledPlugin("foo").get(); + assertThat(installedPlugin.getCompressedJar()).isNull(); + assertThat(installedPlugin.getLoadedJar().getFile().toPath()).isEqualTo(jar.toPath()); + assertThat(installedPlugin.getPluginInfo()).isSameAs(info); + } + + @Test + public void compress_jar_if_compression_enabled() throws IOException { + File jar = touch(temp.newFolder(), "sonar-foo-plugin.jar"); + PluginInfo info = new PluginInfo("foo").setJarFile(jar); + // the JAR is copied somewhere else in order to be loaded by classloaders + File loadedJar = touch(temp.newFolder(), "sonar-foo-plugin.jar"); + + settings.setProperty(PROPERTY_PLUGIN_COMPRESSION_ENABLE, true); + PluginFileSystem underTest = new PluginFileSystem(settings.asConfig()); + underTest.addInstalledPlugin(info, loadedJar); + + assertThat(underTest.getInstalledFiles()).hasSize(1); + + InstalledPlugin installedPlugin = underTest.getInstalledPlugin("foo").get(); + assertThat(installedPlugin.getPluginInfo()).isSameAs(info); + assertThat(installedPlugin.getLoadedJar().getFile().toPath()).isEqualTo(loadedJar.toPath()); + assertThat(installedPlugin.getCompressedJar().getFile()) + .exists() + .isFile() + .hasName("sonar-foo-plugin.pack.gz") + .hasParent(loadedJar.getParentFile()); + } + + @Test + public void copy_and_use_existing_packed_jar_if_compression_enabled() throws IOException { + File jar = touch(temp.newFolder(), "sonar-foo-plugin.jar"); + File packedJar = touch(jar.getParentFile(), "sonar-foo-plugin.pack.gz"); + PluginInfo info = new PluginInfo("foo").setJarFile(jar); + // the JAR is copied somewhere else in order to be loaded by classloaders + File loadedJar = touch(temp.newFolder(), "sonar-foo-plugin.jar"); + + settings.setProperty(PROPERTY_PLUGIN_COMPRESSION_ENABLE, true); + PluginFileSystem underTest = new PluginFileSystem(settings.asConfig()); + underTest.addInstalledPlugin(info, loadedJar); + + assertThat(underTest.getInstalledFiles()).hasSize(1); + + InstalledPlugin installedPlugin = underTest.getInstalledPlugin("foo").get(); + assertThat(installedPlugin.getPluginInfo()).isSameAs(info); + assertThat(installedPlugin.getLoadedJar().getFile().toPath()).isEqualTo(loadedJar.toPath()); + assertThat(installedPlugin.getCompressedJar().getFile()) + .exists() + .isFile() + .hasName(packedJar.getName()) + .hasParent(loadedJar.getParentFile()) + .hasSameContentAs(packedJar); + } + + private static File touch(File dir, String filename) throws IOException { + File file = new File(dir, filename); + FileUtils.write(file, RandomStringUtils.random(10)); + return file; + } + + // + // @Test + // public void should_use_deployed_packed_file() throws IOException { + // Path packedPath = sourceFolder.resolve("test.pack.gz"); + // Files.write(packedPath, new byte[] {1, 2, 3}); + // + // settings.setProperty(PROPERTY_PLUGIN_COMPRESSION_ENABLE, true); + // underTest = new PluginFileSystem(settings.asConfig()); + // underTest.compressJar("key", sourceFolder, targetJarPath); + // + // assertThat(Files.list(targetFolder)).containsOnly(targetJarPath, targetFolder.resolve("test.pack.gz")); + // assertThat(underTest.getPlugins()).hasSize(1); + // assertThat(underTest.getPlugins().get("key").getFilename()).isEqualTo("test.pack.gz"); + // + // // check that the file was copied, not generated + // assertThat(targetFolder.resolve("test.pack.gz")).hasSameContentAs(packedPath); + // } + +} diff --git a/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/PluginUninstallerTest.java b/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/PluginUninstallerTest.java new file mode 100644 index 00000000000..20eae5f2781 --- /dev/null +++ b/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/PluginUninstallerTest.java @@ -0,0 +1,105 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.plugins; + +import java.io.File; +import java.io.IOException; +import org.apache.commons.io.FileUtils; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.rules.TemporaryFolder; +import org.sonar.server.platform.ServerFileSystem; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; + +public class PluginUninstallerTest { + @Rule + public TemporaryFolder testFolder = new TemporaryFolder(); + + @Rule + public ExpectedException exception = ExpectedException.none(); + + private File uninstallDir; + private PluginUninstaller underTest; + private ServerPluginRepository serverPluginRepository; + private ServerFileSystem fs; + + @Before + public void setUp() throws IOException { + serverPluginRepository = mock(ServerPluginRepository.class); + uninstallDir = testFolder.newFolder("uninstall"); + fs = mock(ServerFileSystem.class); + when(fs.getUninstalledPluginsDir()).thenReturn(uninstallDir); + underTest = new PluginUninstaller(serverPluginRepository, fs); + } + + @Test + public void uninstall() { + when(serverPluginRepository.hasPlugin("plugin")).thenReturn(true); + underTest.uninstall("plugin"); + verify(serverPluginRepository).uninstall("plugin", uninstallDir); + } + + @Test + public void fail_uninstall_if_plugin_not_installed() { + when(serverPluginRepository.hasPlugin("plugin")).thenReturn(false); + exception.expect(IllegalArgumentException.class); + exception.expectMessage("Plugin [plugin] is not installed"); + underTest.uninstall("plugin"); + verifyZeroInteractions(serverPluginRepository); + } + + @Test + public void create_uninstall_dir() { + File dir = new File(testFolder.getRoot(), "dir"); + when(fs.getUninstalledPluginsDir()).thenReturn(dir); + underTest = new PluginUninstaller(serverPluginRepository, fs); + underTest.start(); + assertThat(dir).isDirectory(); + } + + @Test + public void cancel() { + underTest.cancelUninstalls(); + verify(serverPluginRepository).cancelUninstalls(uninstallDir); + verifyNoMoreInteractions(serverPluginRepository); + } + + @Test + public void list_uninstalled_plugins() throws IOException { + new File(uninstallDir, "file1").createNewFile(); + copyTestPluginTo("test-base-plugin", uninstallDir); + assertThat(underTest.getUninstalledPlugins()).extracting("key").containsOnly("testbase"); + } + + private File copyTestPluginTo(String testPluginName, File toDir) throws IOException { + File jar = TestProjectUtils.jarOf(testPluginName); + // file is copied because it's supposed to be moved by the test + FileUtils.copyFileToDirectory(jar, toDir); + return new File(toDir, jar.getName()); + } +} diff --git a/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/ServerPluginJarExploderTest.java b/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/ServerPluginJarExploderTest.java new file mode 100644 index 00000000000..8f4d928cd4c --- /dev/null +++ b/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/ServerPluginJarExploderTest.java @@ -0,0 +1,67 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.plugins; + +import java.io.File; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonar.core.platform.ExplodedPlugin; +import org.sonar.core.platform.PluginInfo; +import org.sonar.server.platform.ServerFileSystem; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class ServerPluginJarExploderTest { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + private ServerFileSystem fs = mock(ServerFileSystem.class); + private PluginFileSystem pluginFileSystem = mock(PluginFileSystem.class); + private ServerPluginJarExploder underTest = new ServerPluginJarExploder(fs, pluginFileSystem); + + @Test + public void copy_all_classloader_files_to_dedicated_directory() throws Exception { + File deployDir = temp.newFolder(); + when(fs.getDeployedPluginsDir()).thenReturn(deployDir); + File sourceJar = TestProjectUtils.jarOf("test-libs-plugin"); + PluginInfo info = PluginInfo.create(sourceJar); + + ExplodedPlugin exploded = underTest.explode(info); + + // all the files loaded by classloaders (JAR + META-INF/libs/*.jar) are copied to the dedicated directory + // web/deploy/{pluginKey} + File pluginDeployDir = new File(deployDir, "testlibs"); + + assertThat(exploded.getKey()).isEqualTo("testlibs"); + assertThat(exploded.getMain()).isFile().exists().hasParent(pluginDeployDir); + assertThat(exploded.getLibs()).extracting("name").containsOnly("commons-daemon-1.0.15.jar", "commons-email-20030310.165926.jar"); + for (File lib : exploded.getLibs()) { + assertThat(lib).exists().isFile(); + assertThat(lib.getCanonicalPath()).startsWith(pluginDeployDir.getCanonicalPath()); + } + File targetJar = new File(fs.getDeployedPluginsDir(), "testlibs/test-libs-plugin-0.1-SNAPSHOT.jar"); + verify(pluginFileSystem).addInstalledPlugin(info, targetJar); + } +} diff --git a/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/ServerPluginRepositoryTest.java b/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/ServerPluginRepositoryTest.java new file mode 100644 index 00000000000..e426c9ebfea --- /dev/null +++ b/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/ServerPluginRepositoryTest.java @@ -0,0 +1,385 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.plugins; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import java.io.File; +import java.io.IOException; +import java.util.Collections; +import java.util.Map; +import org.apache.commons.io.FileUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.rules.TemporaryFolder; +import org.mockito.Mockito; +import org.sonar.api.SonarRuntime; +import org.sonar.api.utils.MessageException; +import org.sonar.api.utils.log.LogTester; +import org.sonar.core.platform.PluginInfo; +import org.sonar.core.platform.PluginLoader; +import org.sonar.server.platform.ServerFileSystem; +import org.sonar.updatecenter.common.Version; + +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 ServerPluginRepositoryTest { + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + @Rule + public LogTester logs = new LogTester(); + + private SonarRuntime runtime = mock(SonarRuntime.class); + private ServerFileSystem fs = mock(ServerFileSystem.class, Mockito.RETURNS_DEEP_STUBS); + private PluginLoader pluginLoader = mock(PluginLoader.class); + private ServerPluginRepository underTest = new ServerPluginRepository(runtime, fs, pluginLoader); + + @Before + public void setUp() throws IOException { + when(fs.getDeployedPluginsDir()).thenReturn(temp.newFolder()); + when(fs.getDownloadedPluginsDir()).thenReturn(temp.newFolder()); + when(fs.getHomeDir()).thenReturn(temp.newFolder()); + when(fs.getInstalledPluginsDir()).thenReturn(temp.newFolder()); + when(fs.getTempDir()).thenReturn(temp.newFolder()); + when(runtime.getApiVersion()).thenReturn(org.sonar.api.utils.Version.parse("5.2")); + } + + @After + public void tearDown() { + underTest.stop(); + } + + @Test + public void standard_startup_loads_installed_plugins() throws Exception { + copyTestPluginTo("test-base-plugin", fs.getInstalledPluginsDir()); + + underTest.start(); + + assertThat(underTest.getPluginInfosByKeys()).containsOnlyKeys("testbase"); + } + + @Test + public void no_plugins_at_all_on_startup() { + underTest.start(); + + assertThat(underTest.getPluginInfos()).isEmpty(); + assertThat(underTest.getPluginInfosByKeys()).isEmpty(); + assertThat(underTest.hasPlugin("testbase")).isFalse(); + } + + @Test + public void fail_if_multiple_jars_for_same_installed_plugin_on_startup() throws Exception { + copyTestPluginTo("test-base-plugin", fs.getInstalledPluginsDir()); + copyTestPluginTo("test-base-plugin-v2", fs.getInstalledPluginsDir()); + + try { + underTest.start(); + fail(); + } catch (MessageException e) { + assertThat(e) + .hasMessageStartingWith("Found two versions of the plugin Base Plugin [testbase] in the directory extensions/plugins. Please remove one of ") + // order is not guaranteed, so assertion is split + .hasMessageContaining("test-base-plugin-0.1-SNAPSHOT.jar") + .hasMessageContaining("test-base-plugin-0.2-SNAPSHOT.jar"); + } + } + + @Test + public void install_downloaded_plugins_on_startup() throws Exception { + File downloadedJar = copyTestPluginTo("test-base-plugin", fs.getDownloadedPluginsDir()); + + underTest.start(); + + // plugin is moved to extensions/plugins then loaded + assertThat(downloadedJar).doesNotExist(); + assertThat(new File(fs.getInstalledPluginsDir(), downloadedJar.getName())).isFile().exists(); + assertThat(underTest.getPluginInfosByKeys()).containsOnlyKeys("testbase"); + } + + @Test + public void downloaded_file_overrides_existing_installed_file_on_startup() throws Exception { + File installedV1 = copyTestPluginTo("test-base-plugin", fs.getInstalledPluginsDir()); + File downloadedV2 = copyTestPluginTo("test-base-plugin-v2", fs.getDownloadedPluginsDir()); + + underTest.start(); + + // plugin is moved to extensions/plugins and replaces v1 + assertThat(downloadedV2).doesNotExist(); + assertThat(installedV1).doesNotExist(); + assertThat(new File(fs.getInstalledPluginsDir(), downloadedV2.getName())).exists(); + assertThat(underTest.getPluginInfosByKeys()).containsOnlyKeys("testbase"); + assertThat(underTest.getPluginInfo("testbase").getVersion()).isEqualTo(Version.create("0.2-SNAPSHOT")); + } + + @Test + public void blacklisted_plugin_is_automatically_uninstalled_on_startup() throws Exception { + underTest.setBlacklistedPluginKeys(ImmutableSet.of("testbase", "issuesreport")); + File jar = copyTestPluginTo("test-base-plugin", fs.getInstalledPluginsDir()); + + underTest.start(); + + // plugin is not installed and file is deleted + assertThat(underTest.getPluginInfos()).isEmpty(); + assertThat(jar).doesNotExist(); + } + + @Test + public void test_plugin_requirements_at_startup() throws Exception { + copyTestPluginTo("test-base-plugin", fs.getInstalledPluginsDir()); + copyTestPluginTo("test-require-plugin", fs.getInstalledPluginsDir()); + + underTest.start(); + + // both plugins are installed + assertThat(underTest.getPluginInfosByKeys()).containsOnlyKeys("testbase", "testrequire"); + } + + @Test + public void plugin_is_ignored_if_required_plugin_is_missing_at_startup() throws Exception { + copyTestPluginTo("test-require-plugin", fs.getInstalledPluginsDir()); + + underTest.start(); + + // plugin is not installed as test-base-plugin is missing + assertThat(underTest.getPluginInfosByKeys()).isEmpty(); + } + + @Test + public void plugin_is_ignored_if_required_plugin_is_too_old_at_startup() throws Exception { + copyTestPluginTo("test-base-plugin", fs.getInstalledPluginsDir()); + copyTestPluginTo("test-requirenew-plugin", fs.getInstalledPluginsDir()); + + underTest.start(); + + // the plugin "requirenew" is not installed as it requires base 0.2+ to be installed. + assertThat(underTest.getPluginInfosByKeys()).containsOnlyKeys("testbase"); + } + + @Test + public void fail_if_plugin_does_not_support_sq_version() throws Exception { + when(runtime.getApiVersion()).thenReturn(org.sonar.api.utils.Version.parse("1.0")); + copyTestPluginTo("test-base-plugin", fs.getInstalledPluginsDir()); + + try { + underTest.start(); + fail(); + } catch (MessageException e) { + assertThat(e).hasMessage("Plugin Base Plugin [testbase] requires at least SonarQube 4.5.4"); + } + } + + @Test + public void uninstall() throws Exception { + File installedJar = copyTestPluginTo("test-base-plugin", fs.getInstalledPluginsDir()); + File uninstallDir = temp.newFolder("uninstallDir"); + + underTest.start(); + assertThat(underTest.getPluginInfosByKeys()).containsOnlyKeys("testbase"); + underTest.uninstall("testbase", uninstallDir); + + assertThat(installedJar).doesNotExist(); + // still up. Will be dropped after next startup + assertThat(underTest.getPluginInfosByKeys()).containsOnlyKeys("testbase"); + assertThat(uninstallDir.list()).containsOnly(installedJar.getName()); + } + + @Test + public void uninstall_dependents() throws Exception { + File base = copyTestPluginTo("test-base-plugin", fs.getInstalledPluginsDir()); + File extension = copyTestPluginTo("test-require-plugin", fs.getInstalledPluginsDir()); + File uninstallDir = temp.newFolder("uninstallDir"); + + underTest.start(); + assertThat(underTest.getPluginInfos()).hasSize(2); + underTest.uninstall("testbase", uninstallDir); + assertThat(base).doesNotExist(); + assertThat(extension).doesNotExist(); + assertThat(uninstallDir.list()).containsOnly(base.getName(), extension.getName()); + } + + @Test + public void dont_uninstall_non_existing_dependents() throws IOException { + File base = copyTestPluginTo("test-base-plugin", fs.getInstalledPluginsDir()); + File extension = copyTestPluginTo("test-require-plugin", fs.getInstalledPluginsDir()); + File uninstallDir = temp.newFolder("uninstallDir"); + + underTest.start(); + assertThat(underTest.getPluginInfos()).hasSize(2); + underTest.uninstall("testrequire", uninstallDir); + assertThat(underTest.getPluginInfos()).hasSize(2); + + underTest.uninstall("testbase", uninstallDir); + assertThat(base).doesNotExist(); + assertThat(extension).doesNotExist(); + assertThat(uninstallDir.list()).containsOnly(base.getName(), extension.getName()); + } + + @Test + public void dont_uninstall_non_existing_files() throws IOException { + File base = copyTestPluginTo("test-base-plugin", fs.getInstalledPluginsDir()); + File extension = copyTestPluginTo("test-require-plugin", fs.getInstalledPluginsDir()); + File uninstallDir = temp.newFolder("uninstallDir"); + + underTest.start(); + assertThat(underTest.getPluginInfos()).hasSize(2); + underTest.uninstall("testbase", uninstallDir); + assertThat(underTest.getPluginInfos()).hasSize(2); + + underTest.uninstall("testbase", uninstallDir); + assertThat(base).doesNotExist(); + assertThat(extension).doesNotExist(); + assertThat(uninstallDir.list()).containsOnly(base.getName(), extension.getName()); + } + + @Test + public void install_plugin_and_its_extension_plugins_at_startup() throws Exception { + copyTestPluginTo("test-base-plugin", fs.getInstalledPluginsDir()); + copyTestPluginTo("test-extend-plugin", fs.getInstalledPluginsDir()); + + underTest.start(); + + // both plugins are installed + assertThat(underTest.getPluginInfosByKeys()).containsOnlyKeys("testbase", "testextend"); + } + + @Test + public void extension_plugin_is_ignored_if_base_plugin_is_missing_at_startup() throws Exception { + copyTestPluginTo("test-extend-plugin", fs.getInstalledPluginsDir()); + + underTest.start(); + + // plugin is not installed as its base plugin is not installed + assertThat(underTest.getPluginInfos()).isEmpty(); + } + + @Test + public void fail_to_get_missing_plugins() { + underTest.start(); + try { + underTest.getPluginInfo("unknown"); + fail(); + } catch (IllegalArgumentException e) { + assertThat(e).hasMessage("Plugin [unknown] does not exist"); + } + + try { + underTest.getPluginInstance("unknown"); + fail(); + } catch (IllegalArgumentException e) { + assertThat(e).hasMessage("Plugin [unknown] does not exist"); + } + } + + @Test + public void plugin_is_incompatible_if_no_entry_point_class() { + PluginInfo plugin = new PluginInfo("foo").setName("Foo"); + assertThat(ServerPluginRepository.isCompatible(plugin, runtime, Collections.emptyMap())).isFalse(); + assertThat(logs.logs()).contains("Plugin Foo [foo] is ignored because entry point class is not defined"); + } + + @Test + public void fail_when_views_is_installed() throws Exception { + copyTestPluginTo("fake-views-plugin", fs.getInstalledPluginsDir()); + + expectedException.expect(MessageException.class); + expectedException.expectMessage("Plugin 'views' is no longer compatible with this version of SonarQube"); + underTest.start(); + } + + @Test + public void fail_when_sqale_plugin_is_installed() throws Exception { + copyTestPluginTo("fake-sqale-plugin", fs.getInstalledPluginsDir()); + + expectedException.expect(MessageException.class); + expectedException.expectMessage("Plugin 'sqale' is no longer compatible with this version of SonarQube"); + underTest.start(); + } + + @Test + public void fail_when_report_is_installed() throws Exception { + copyTestPluginTo("fake-report-plugin", fs.getInstalledPluginsDir()); + + expectedException.expect(MessageException.class); + expectedException.expectMessage("Plugin 'report' is no longer compatible with this version of SonarQube"); + underTest.start(); + } + + /** + * Some plugins can only extend the classloader of base plugin, without declaring new extensions. + */ + @Test + public void plugin_is_compatible_if_no_entry_point_class_but_extend_other_plugin() { + PluginInfo basePlugin = new PluginInfo("base").setMainClass("org.bar.Bar"); + PluginInfo plugin = new PluginInfo("foo").setBasePlugin("base"); + Map<String, PluginInfo> plugins = ImmutableMap.of("base", basePlugin, "foo", plugin); + + assertThat(ServerPluginRepository.isCompatible(plugin, runtime, plugins)).isTrue(); + } + + @Test + public void getPluginInstance_throws_ISE_if_repo_is_not_started() { + expectedException.expect(IllegalStateException.class); + expectedException.expectMessage("not started yet"); + + underTest.getPluginInstance("foo"); + } + + @Test + public void getPluginInfo_throws_ISE_if_repo_is_not_started() { + expectedException.expect(IllegalStateException.class); + expectedException.expectMessage("not started yet"); + + underTest.getPluginInfo("foo"); + } + + @Test + public void hasPlugin_throws_ISE_if_repo_is_not_started() { + expectedException.expect(IllegalStateException.class); + expectedException.expectMessage("not started yet"); + + underTest.hasPlugin("foo"); + } + + @Test + public void getPluginInfos_throws_ISE_if_repo_is_not_started() { + expectedException.expect(IllegalStateException.class); + expectedException.expectMessage("not started yet"); + + underTest.getPluginInfos(); + } + + private File copyTestPluginTo(String testPluginName, File toDir) throws IOException { + File jar = TestProjectUtils.jarOf(testPluginName); + // file is copied because it's supposed to be moved by the test + FileUtils.copyFileToDirectory(jar, toDir); + return new File(toDir, jar.getName()); + } +} diff --git a/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/TestPluginA.java b/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/TestPluginA.java new file mode 100644 index 00000000000..7952eb5799c --- /dev/null +++ b/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/TestPluginA.java @@ -0,0 +1,29 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.plugins; + +import org.sonar.api.Plugin; + +public class TestPluginA implements Plugin { + @Override + public void define(Context context) { + + } +} diff --git a/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/TestProjectUtils.java b/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/TestProjectUtils.java new file mode 100644 index 00000000000..9710a8689e4 --- /dev/null +++ b/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/TestProjectUtils.java @@ -0,0 +1,40 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.plugins; + +import org.apache.commons.io.FileUtils; + +import java.io.File; +import java.util.Collection; + +public class TestProjectUtils { + + /** + * Get the artifact of plugins stored in src/test/projects + */ + public static File jarOf(String dirName) { + File target = FileUtils.toFile(TestProjectUtils.class.getResource(String.format("/%s/target/", dirName))); + Collection<File> jars = FileUtils.listFiles(target, new String[] {"jar"}, false); + if (jars == null || jars.size() != 1) { + throw new IllegalArgumentException("Test project is badly defined: " + dirName); + } + return jars.iterator().next(); + } +} diff --git a/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/UpdateCenterClientTest.java b/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/UpdateCenterClientTest.java new file mode 100644 index 00000000000..b1e50cba77c --- /dev/null +++ b/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/UpdateCenterClientTest.java @@ -0,0 +1,104 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.plugins; + +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; +import org.junit.Before; +import org.junit.Test; +import org.sonar.api.config.internal.MapSettings; +import org.sonar.api.utils.SonarException; +import org.sonar.api.utils.UriReader; +import org.sonar.process.ProcessProperties; +import org.sonar.updatecenter.common.UpdateCenter; +import org.sonar.updatecenter.common.Version; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.guava.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class UpdateCenterClientTest { + + private static final String BASE_URL = "https://update.sonarsource.org"; + private UriReader reader = mock(UriReader.class); + private MapSettings settings = new MapSettings(); + private UpdateCenterClient underTest; + + @Before + public void startServer() throws Exception { + reader = mock(UriReader.class); + settings.setProperty(UpdateCenterClient.URL_PROPERTY, BASE_URL); + settings.setProperty(ProcessProperties.Property.SONAR_UPDATECENTER_ACTIVATE.getKey(), true); + underTest = new UpdateCenterClient(reader, settings.asConfig()); + } + + @Test + public void downloadUpdateCenter() throws URISyntaxException { + when(reader.readString(new URI(BASE_URL), StandardCharsets.UTF_8)).thenReturn("publicVersions=2.2,2.3"); + UpdateCenter plugins = underTest.getUpdateCenter().get(); + verify(reader, times(1)).readString(new URI(BASE_URL), StandardCharsets.UTF_8); + assertThat(plugins.getSonar().getVersions()).containsOnly(Version.create("2.2"), Version.create("2.3")); + assertThat(underTest.getLastRefreshDate()).isNotNull(); + } + + @Test + public void not_available_before_initialization() { + assertThat(underTest.getLastRefreshDate()).isNull(); + } + + @Test + public void ignore_connection_errors() { + when(reader.readString(any(URI.class), eq(StandardCharsets.UTF_8))).thenThrow(new SonarException()); + assertThat(underTest.getUpdateCenter()).isAbsent(); + } + + @Test + public void cache_data() throws Exception { + when(reader.readString(new URI(BASE_URL), StandardCharsets.UTF_8)).thenReturn("sonar.versions=2.2,2.3"); + + underTest.getUpdateCenter(); + underTest.getUpdateCenter(); + + verify(reader, times(1)).readString(new URI(BASE_URL), StandardCharsets.UTF_8); + } + + @Test + public void forceRefresh() throws Exception { + when(reader.readString(new URI(BASE_URL), StandardCharsets.UTF_8)).thenReturn("sonar.versions=2.2,2.3"); + + underTest.getUpdateCenter(); + underTest.getUpdateCenter(true); + + verify(reader, times(2)).readString(new URI(BASE_URL), StandardCharsets.UTF_8); + } + + @Test + public void update_center_is_null_when_property_is_false() { + settings.setProperty(ProcessProperties.Property.SONAR_UPDATECENTER_ACTIVATE.getKey(), false); + + assertThat(underTest.getUpdateCenter()).isAbsent(); + } +} diff --git a/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/UpdateCenterMatrixFactoryTest.java b/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/UpdateCenterMatrixFactoryTest.java new file mode 100644 index 00000000000..a71522a77d5 --- /dev/null +++ b/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/UpdateCenterMatrixFactoryTest.java @@ -0,0 +1,47 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.plugins; + +import com.google.common.base.Optional; +import org.junit.Test; +import org.sonar.api.SonarRuntime; +import org.sonar.updatecenter.common.UpdateCenter; + +import static org.assertj.guava.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class UpdateCenterMatrixFactoryTest { + + private UpdateCenterMatrixFactory underTest; + + @Test + public void return_absent_update_center() { + UpdateCenterClient updateCenterClient = mock(UpdateCenterClient.class); + when(updateCenterClient.getUpdateCenter(anyBoolean())).thenReturn(Optional.absent()); + + underTest = new UpdateCenterMatrixFactory(updateCenterClient, mock(SonarRuntime.class), mock(InstalledPluginReferentialFactory.class)); + + Optional<UpdateCenter> updateCenter = underTest.getUpdateCenter(false); + + assertThat(updateCenter).isAbsent(); + } +} diff --git a/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/UpdateCenterServlet.java b/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/UpdateCenterServlet.java new file mode 100644 index 00000000000..d6d9904452d --- /dev/null +++ b/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/UpdateCenterServlet.java @@ -0,0 +1,43 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.plugins; + +import javax.servlet.GenericServlet; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.util.Properties; + +public class UpdateCenterServlet extends GenericServlet { + + int count = 0; + + @Override + public void service(ServletRequest request, ServletResponse response) throws IOException { + count++; + Properties props = new Properties(); + props.setProperty("count", String.valueOf(count)); + props.setProperty("agent", ((HttpServletRequest)request).getHeader("User-Agent")); + props.store(response.getOutputStream(), null); + } +} + diff --git a/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/edition/EditionBundledPluginsTest.java b/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/edition/EditionBundledPluginsTest.java new file mode 100644 index 00000000000..907768f56fa --- /dev/null +++ b/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/edition/EditionBundledPluginsTest.java @@ -0,0 +1,178 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.plugins.edition; + +import java.util.Random; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.core.platform.PluginInfo; +import org.sonar.updatecenter.common.Plugin; + +import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric; +import static org.assertj.core.api.Assertions.assertThat; + +public class EditionBundledPluginsTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private final Random random = new Random(); + + @Test + public void isEditionBundled_on_Plugin_fails_with_NPE_if_arg_is_null() { + expectedException.expect(NullPointerException.class); + + EditionBundledPlugins.isEditionBundled((Plugin) null); + } + + @Test + public void isEditionBundled_on_Plugin_returns_false_for_SonarSource_and_non_commercial_license() { + Plugin plugin = newPlugin(randomizeCase("SonarSource"), randomAlphanumeric(3)); + + assertThat(EditionBundledPlugins.isEditionBundled(plugin)).isFalse(); + } + + @Test + public void isEditionBundled_on_Plugin_returns_false_for_license_SonarSource_and_non_SonarSource_organization() { + Plugin plugin = newPlugin(randomAlphanumeric(3), randomizeCase("SonarSource")); + + assertThat(EditionBundledPlugins.isEditionBundled(plugin)).isFalse(); + } + + @Test + public void isEditionBundled_on_Plugin_returns_false_for_license_Commercial_and_non_SonarSource_organization() { + Plugin plugin = newPlugin(randomAlphanumeric(3), randomizeCase("Commercial")); + + assertThat(EditionBundledPlugins.isEditionBundled(plugin)).isFalse(); + } + + @Test + public void isEditionBundled_on_Plugin_returns_true_for_organization_SonarSource_and_license_SonarSource_case_insensitive() { + Plugin plugin = newPlugin(randomizeCase("SonarSource"), randomizeCase("SonarSource")); + + assertThat(EditionBundledPlugins.isEditionBundled(plugin)).isTrue(); + } + + @Test + public void isEditionBundled_on_Plugin_returns_true_for_organization_SonarSource_and_license_Commercial_case_insensitive() { + Plugin plugin = newPlugin(randomizeCase("SonarSource"), randomizeCase("Commercial")); + + assertThat(EditionBundledPlugins.isEditionBundled(plugin)).isTrue(); + } + + @Test + public void isEditionBundled_on_PluginInfo_fails_with_NPE_if_arg_is_null() { + expectedException.expect(NullPointerException.class); + + EditionBundledPlugins.isEditionBundled((PluginInfo) null); + } + + @Test + public void isEditionBundled_on_PluginInfo_returns_false_for_SonarSource_and_non_commercial_license() { + PluginInfo pluginInfo = newPluginInfo(randomizeCase("SonarSource"), randomAlphanumeric(3)); + + assertThat(EditionBundledPlugins.isEditionBundled(pluginInfo)).isFalse(); + } + + @Test + public void isEditionBundled_on_PluginInfo_returns_false_for_license_SonarSource_and_non_SonarSource_organization() { + PluginInfo pluginInfo = newPluginInfo(randomAlphanumeric(3), randomizeCase("SonarSource")); + + assertThat(EditionBundledPlugins.isEditionBundled(pluginInfo)).isFalse(); + } + + @Test + public void isEditionBundled_on_PluginInfo_returns_false_for_license_Commercial_and_non_SonarSource_organization() { + PluginInfo pluginInfo = newPluginInfo(randomAlphanumeric(3), randomizeCase("Commercial")); + + assertThat(EditionBundledPlugins.isEditionBundled(pluginInfo)).isFalse(); + } + + @Test + public void isEditionBundled_on_PluginInfo_returns_true_for_organization_SonarSource_and_license_SonarSource_case_insensitive() { + PluginInfo pluginInfo = newPluginInfo(randomizeCase("SonarSource"), randomizeCase("SonarSource")); + + assertThat(EditionBundledPlugins.isEditionBundled(pluginInfo)).isTrue(); + } + + @Test + public void isEditionBundled_on_PluginINfo_returns_true_for_organization_SonarSource_and_license_Commercial_case_insensitive() { + PluginInfo pluginInfo = newPluginInfo(randomizeCase("SonarSource"), randomizeCase("Commercial")); + + assertThat(EditionBundledPlugins.isEditionBundled(pluginInfo)).isTrue(); + } + + private String randomizeCase(String s) { + return s.chars() + .map(c -> random.nextBoolean() ? Character.toUpperCase(c) : Character.toLowerCase(c)) + .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append) + .toString(); + } + + private PluginInfo newPluginInfo(String organization, String license) { + PluginInfo pluginInfo = new PluginInfo(randomAlphanumeric(2)); + if (random.nextBoolean()) { + pluginInfo.setName(randomAlphanumeric(3)); + } + if (random.nextBoolean()) { + pluginInfo.setOrganizationUrl(randomAlphanumeric(4)); + } + if (random.nextBoolean()) { + pluginInfo.setIssueTrackerUrl(randomAlphanumeric(5)); + } + if (random.nextBoolean()) { + pluginInfo.setIssueTrackerUrl(randomAlphanumeric(6)); + } + if (random.nextBoolean()) { + pluginInfo.setBasePlugin(randomAlphanumeric(7)); + } + if (random.nextBoolean()) { + pluginInfo.setHomepageUrl(randomAlphanumeric(8)); + } + return pluginInfo + .setOrganizationName(organization) + .setLicense(license); + } + + private Plugin newPlugin(String organization, String license) { + Plugin plugin = Plugin.factory(randomAlphanumeric(2)); + if (random.nextBoolean()) { + plugin.setName(randomAlphanumeric(3)); + } + if (random.nextBoolean()) { + plugin.setOrganizationUrl(randomAlphanumeric(4)); + } + if (random.nextBoolean()) { + plugin.setTermsConditionsUrl(randomAlphanumeric(5)); + } + if (random.nextBoolean()) { + plugin.setIssueTrackerUrl(randomAlphanumeric(6)); + } + if (random.nextBoolean()) { + plugin.setCategory(randomAlphanumeric(7)); + } + if (random.nextBoolean()) { + plugin.setHomepageUrl(randomAlphanumeric(8)); + } + return plugin + .setLicense(license) + .setOrganization(organization); + } +} diff --git a/server/sonar-webserver-api/src/test/java/org/sonar/server/project/ProjectLifeCycleListenersImplTest.java b/server/sonar-webserver-api/src/test/java/org/sonar/server/project/ProjectLifeCycleListenersImplTest.java new file mode 100644 index 00000000000..8085b643ede --- /dev/null +++ b/server/sonar-webserver-api/src/test/java/org/sonar/server/project/ProjectLifeCycleListenersImplTest.java @@ -0,0 +1,311 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.project; + +import com.tngtech.java.junit.dataprovider.DataProvider; +import com.tngtech.java.junit.dataprovider.DataProviderRunner; +import com.tngtech.java.junit.dataprovider.UseDataProvider; +import java.util.Collections; +import java.util.Random; +import java.util.Set; +import java.util.stream.IntStream; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.mockito.InOrder; +import org.mockito.Mockito; +import org.sonar.core.util.stream.MoreCollectors; + +import static java.util.Collections.singleton; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.same; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto; +import static org.sonar.db.organization.OrganizationTesting.newOrganizationDto; + +@RunWith(DataProviderRunner.class) +public class ProjectLifeCycleListenersImplTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private ProjectLifeCycleListener listener1 = mock(ProjectLifeCycleListener.class); + private ProjectLifeCycleListener listener2 = mock(ProjectLifeCycleListener.class); + private ProjectLifeCycleListener listener3 = mock(ProjectLifeCycleListener.class); + private ProjectLifeCycleListenersImpl underTestNoListeners = new ProjectLifeCycleListenersImpl(); + private ProjectLifeCycleListenersImpl underTestWithListeners = new ProjectLifeCycleListenersImpl( + new ProjectLifeCycleListener[] {listener1, listener2, listener3}); + + @Test + public void onProjectsDeleted_throws_NPE_if_set_is_null() { + expectedException.expect(NullPointerException.class); + expectedException.expectMessage("projects can't be null"); + + underTestWithListeners.onProjectsDeleted(null); + } + + @Test + public void onProjectsDeleted_throws_NPE_if_set_is_null_even_if_no_listeners() { + expectedException.expect(NullPointerException.class); + expectedException.expectMessage("projects can't be null"); + + underTestNoListeners.onProjectsDeleted(null); + } + + @Test + public void onProjectsDeleted_has_no_effect_if_set_is_empty() { + underTestNoListeners.onProjectsDeleted(Collections.emptySet()); + + underTestWithListeners.onProjectsDeleted(Collections.emptySet()); + verifyZeroInteractions(listener1, listener2, listener3); + } + + @Test + @UseDataProvider("oneOrManyProjects") + public void onProjectsDeleted_does_not_fail_if_there_is_no_listener(Set<Project> projects) { + underTestNoListeners.onProjectsDeleted(projects); + } + + @Test + @UseDataProvider("oneOrManyProjects") + public void onProjectsDeleted_calls_all_listeners_in_order_of_addition_to_constructor(Set<Project> projects) { + InOrder inOrder = Mockito.inOrder(listener1, listener2, listener3); + + underTestWithListeners.onProjectsDeleted(projects); + + inOrder.verify(listener1).onProjectsDeleted(same(projects)); + inOrder.verify(listener2).onProjectsDeleted(same(projects)); + inOrder.verify(listener3).onProjectsDeleted(same(projects)); + inOrder.verifyNoMoreInteractions(); + } + + @Test + @UseDataProvider("oneOrManyProjects") + public void onProjectsDeleted_calls_all_listeners_even_if_one_throws_an_Exception(Set<Project> projects) { + InOrder inOrder = Mockito.inOrder(listener1, listener2, listener3); + doThrow(new RuntimeException("Faking listener2 throwing an exception")) + .when(listener2) + .onProjectsDeleted(any()); + + underTestWithListeners.onProjectsDeleted(projects); + + inOrder.verify(listener1).onProjectsDeleted(same(projects)); + inOrder.verify(listener2).onProjectsDeleted(same(projects)); + inOrder.verify(listener3).onProjectsDeleted(same(projects)); + inOrder.verifyNoMoreInteractions(); + } + + @Test + @UseDataProvider("oneOrManyProjects") + public void onProjectsDeleted_calls_all_listeners_even_if_one_throws_an_Error(Set<Project> projects) { + InOrder inOrder = Mockito.inOrder(listener1, listener2, listener3); + doThrow(new Error("Faking listener2 throwing an Error")) + .when(listener2) + .onProjectsDeleted(any()); + + underTestWithListeners.onProjectsDeleted(projects); + + inOrder.verify(listener1).onProjectsDeleted(same(projects)); + inOrder.verify(listener2).onProjectsDeleted(same(projects)); + inOrder.verify(listener3).onProjectsDeleted(same(projects)); + inOrder.verifyNoMoreInteractions(); + } + + @Test + public void onProjectBranchesDeleted_throws_NPE_if_set_is_null() { + expectedException.expect(NullPointerException.class); + expectedException.expectMessage("projects can't be null"); + + underTestWithListeners.onProjectBranchesDeleted(null); + } + + @Test + public void onProjectBranchesDeleted_throws_NPE_if_set_is_null_even_if_no_listeners() { + expectedException.expect(NullPointerException.class); + expectedException.expectMessage("projects can't be null"); + + underTestNoListeners.onProjectBranchesDeleted(null); + } + + @Test + public void onProjectBranchesDeleted_has_no_effect_if_set_is_empty() { + underTestNoListeners.onProjectBranchesDeleted(Collections.emptySet()); + + underTestWithListeners.onProjectBranchesDeleted(Collections.emptySet()); + verifyZeroInteractions(listener1, listener2, listener3); + } + + @Test + @UseDataProvider("oneOrManyProjects") + public void onProjectBranchesDeleted_does_not_fail_if_there_is_no_listener(Set<Project> projects) { + underTestNoListeners.onProjectBranchesDeleted(projects); + } + + @Test + @UseDataProvider("oneOrManyProjects") + public void onProjectBranchesDeleted_calls_all_listeners_in_order_of_addition_to_constructor(Set<Project> projects) { + InOrder inOrder = Mockito.inOrder(listener1, listener2, listener3); + + underTestWithListeners.onProjectBranchesDeleted(projects); + + inOrder.verify(listener1).onProjectBranchesDeleted(same(projects)); + inOrder.verify(listener2).onProjectBranchesDeleted(same(projects)); + inOrder.verify(listener3).onProjectBranchesDeleted(same(projects)); + inOrder.verifyNoMoreInteractions(); + } + + @Test + @UseDataProvider("oneOrManyProjects") + public void onProjectBranchesDeleted_calls_all_listeners_even_if_one_throws_an_Exception(Set<Project> projects) { + InOrder inOrder = Mockito.inOrder(listener1, listener2, listener3); + doThrow(new RuntimeException("Faking listener2 throwing an exception")) + .when(listener2) + .onProjectBranchesDeleted(any()); + + underTestWithListeners.onProjectBranchesDeleted(projects); + + inOrder.verify(listener1).onProjectBranchesDeleted(same(projects)); + inOrder.verify(listener2).onProjectBranchesDeleted(same(projects)); + inOrder.verify(listener3).onProjectBranchesDeleted(same(projects)); + inOrder.verifyNoMoreInteractions(); + } + + @Test + @UseDataProvider("oneOrManyProjects") + public void onProjectBranchesDeleted_calls_all_listeners_even_if_one_throws_an_Error(Set<Project> projects) { + InOrder inOrder = Mockito.inOrder(listener1, listener2, listener3); + doThrow(new Error("Faking listener2 throwing an Error")) + .when(listener2) + .onProjectBranchesDeleted(any()); + + underTestWithListeners.onProjectBranchesDeleted(projects); + + inOrder.verify(listener1).onProjectBranchesDeleted(same(projects)); + inOrder.verify(listener2).onProjectBranchesDeleted(same(projects)); + inOrder.verify(listener3).onProjectBranchesDeleted(same(projects)); + inOrder.verifyNoMoreInteractions(); + } + + @DataProvider + public static Object[][] oneOrManyProjects() { + return new Object[][] { + {singleton(newUniqueProject())}, + {IntStream.range(0, 1 + new Random().nextInt(10)).mapToObj(i -> newUniqueProject()).collect(MoreCollectors.toSet())} + }; + } + // SDSDS + + @Test + public void onProjectsRekeyed_throws_NPE_if_set_is_null() { + expectedException.expect(NullPointerException.class); + expectedException.expectMessage("rekeyedProjects can't be null"); + + underTestWithListeners.onProjectsRekeyed(null); + } + + @Test + public void onProjectsRekeyed_throws_NPE_if_set_is_null_even_if_no_listeners() { + expectedException.expect(NullPointerException.class); + expectedException.expectMessage("rekeyedProjects can't be null"); + + underTestNoListeners.onProjectsRekeyed(null); + } + + @Test + public void onProjectsRekeyed_has_no_effect_if_set_is_empty() { + underTestNoListeners.onProjectsRekeyed(Collections.emptySet()); + + underTestWithListeners.onProjectsRekeyed(Collections.emptySet()); + verifyZeroInteractions(listener1, listener2, listener3); + } + + @Test + @UseDataProvider("oneOrManyRekeyedProjects") + public void onProjectsRekeyed_does_not_fail_if_there_is_no_listener(Set<RekeyedProject> projects) { + underTestNoListeners.onProjectsRekeyed(projects); + } + + @Test + @UseDataProvider("oneOrManyRekeyedProjects") + public void onProjectsRekeyed_calls_all_listeners_in_order_of_addition_to_constructor(Set<RekeyedProject> projects) { + InOrder inOrder = Mockito.inOrder(listener1, listener2, listener3); + + underTestWithListeners.onProjectsRekeyed(projects); + + inOrder.verify(listener1).onProjectsRekeyed(same(projects)); + inOrder.verify(listener2).onProjectsRekeyed(same(projects)); + inOrder.verify(listener3).onProjectsRekeyed(same(projects)); + inOrder.verifyNoMoreInteractions(); + } + + @Test + @UseDataProvider("oneOrManyRekeyedProjects") + public void onProjectsRekeyed_calls_all_listeners_even_if_one_throws_an_Exception(Set<RekeyedProject> projects) { + InOrder inOrder = Mockito.inOrder(listener1, listener2, listener3); + doThrow(new RuntimeException("Faking listener2 throwing an exception")) + .when(listener2) + .onProjectsRekeyed(any()); + + underTestWithListeners.onProjectsRekeyed(projects); + + inOrder.verify(listener1).onProjectsRekeyed(same(projects)); + inOrder.verify(listener2).onProjectsRekeyed(same(projects)); + inOrder.verify(listener3).onProjectsRekeyed(same(projects)); + inOrder.verifyNoMoreInteractions(); + } + + @Test + @UseDataProvider("oneOrManyRekeyedProjects") + public void onProjectsRekeyed_calls_all_listeners_even_if_one_throws_an_Error(Set<RekeyedProject> projects) { + InOrder inOrder = Mockito.inOrder(listener1, listener2, listener3); + doThrow(new Error("Faking listener2 throwing an Error")) + .when(listener2) + .onProjectsRekeyed(any()); + + underTestWithListeners.onProjectsRekeyed(projects); + + inOrder.verify(listener1).onProjectsRekeyed(same(projects)); + inOrder.verify(listener2).onProjectsRekeyed(same(projects)); + inOrder.verify(listener3).onProjectsRekeyed(same(projects)); + inOrder.verifyNoMoreInteractions(); + } + + @DataProvider + public static Object[][] oneOrManyRekeyedProjects() { + return new Object[][] { + {singleton(newUniqueRekeyedProject())}, + {IntStream.range(0, 1 + new Random().nextInt(10)).mapToObj(i -> newUniqueRekeyedProject()).collect(MoreCollectors.toSet())} + }; + } + + private static Project newUniqueProject() { + return Project.from(newPrivateProjectDto(newOrganizationDto())); + } + + private static int counter = 3_989; + + private static RekeyedProject newUniqueRekeyedProject() { + int base = counter++; + Project project = Project.from(newPrivateProjectDto(newOrganizationDto())); + return new RekeyedProject(project, base + "_old_key"); + } +} diff --git a/server/sonar-webserver-api/src/test/java/org/sonar/server/project/RekeyedProjectTest.java b/server/sonar-webserver-api/src/test/java/org/sonar/server/project/RekeyedProjectTest.java new file mode 100644 index 00000000000..a847d91d0d8 --- /dev/null +++ b/server/sonar-webserver-api/src/test/java/org/sonar/server/project/RekeyedProjectTest.java @@ -0,0 +1,102 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.project; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import static java.util.Collections.emptyList; +import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric; +import static org.assertj.core.api.Assertions.assertThat; +import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto; +import static org.sonar.db.organization.OrganizationTesting.newOrganizationDto; + +public class RekeyedProjectTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + @Test + public void constructor_throws_NPE_if_project_is_null() { + expectedException.expect(NullPointerException.class); + expectedException.expectMessage("project can't be null"); + + new RekeyedProject(null, randomAlphanumeric(3)); + } + + @Test + public void constructor_throws_NPE_if_previousKey_is_null() { + expectedException.expect(NullPointerException.class); + expectedException.expectMessage("previousKey can't be null"); + + new RekeyedProject(newRandomProject(), null); + } + + @Test + public void verify_getters() { + Project project = newRandomProject(); + String previousKey = randomAlphanumeric(6); + RekeyedProject underTest = new RekeyedProject(project, previousKey); + + assertThat(underTest.getProject()).isSameAs(project); + assertThat(underTest.getPreviousKey()).isEqualTo(previousKey); + } + + @Test + public void equals_is_based_on_project_and_previousKey() { + Project project = newRandomProject(); + String previousKey = randomAlphanumeric(6); + RekeyedProject underTest = new RekeyedProject(project, previousKey); + + assertThat(underTest).isEqualTo(underTest); + assertThat(underTest).isEqualTo(new RekeyedProject(project, previousKey)); + assertThat(underTest).isNotEqualTo(new RekeyedProject(project, randomAlphanumeric(11))); + assertThat(underTest).isNotEqualTo(new RekeyedProject(newRandomProject(), previousKey)); + assertThat(underTest).isNotEqualTo(new Object()); + assertThat(underTest).isNotEqualTo(null); + } + + @Test + public void hashCode_is_based_on_project_and_previousKey() { + Project project = newRandomProject(); + String previousKey = randomAlphanumeric(6); + RekeyedProject underTest = new RekeyedProject(project, previousKey); + + assertThat(underTest.hashCode()).isEqualTo(underTest.hashCode()); + assertThat(underTest.hashCode()).isEqualTo(new RekeyedProject(project, previousKey).hashCode()); + assertThat(underTest.hashCode()).isNotEqualTo(new RekeyedProject(project, randomAlphanumeric(11)).hashCode()); + assertThat(underTest.hashCode()).isNotEqualTo(new RekeyedProject(newRandomProject(), previousKey).hashCode()); + assertThat(underTest.hashCode()).isNotEqualTo(new Object().hashCode()); + assertThat(underTest.hashCode()).isNotEqualTo(null); + } + + @Test + public void verify_toString() { + Project project = new Project("A", "B", "C", "D", emptyList()); + String previousKey = "E"; + RekeyedProject underTest = new RekeyedProject(project, previousKey); + + assertThat(underTest.toString()).isEqualTo("RekeyedProject{project=Project{uuid='A', key='B', name='C', description='D'}, previousKey='E'}"); + } + + private static Project newRandomProject() { + return Project.from(newPrivateProjectDto(newOrganizationDto())); + } +} diff --git a/server/sonar-webserver-api/src/test/java/org/sonar/server/qualitygate/changeevent/QGChangeEventListenersImplTest.java b/server/sonar-webserver-api/src/test/java/org/sonar/server/qualitygate/changeevent/QGChangeEventListenersImplTest.java new file mode 100644 index 00000000000..964637487fb --- /dev/null +++ b/server/sonar-webserver-api/src/test/java/org/sonar/server/qualitygate/changeevent/QGChangeEventListenersImplTest.java @@ -0,0 +1,358 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.qualitygate.changeevent; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Random; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.apache.commons.lang.RandomStringUtils; +import org.assertj.core.groups.Tuple; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.InOrder; +import org.mockito.Mockito; +import org.sonar.api.issue.Issue; +import org.sonar.api.rules.RuleType; +import org.sonar.api.utils.log.LogTester; +import org.sonar.api.utils.log.LoggerLevel; +import org.sonar.core.issue.DefaultIssue; +import org.sonar.db.component.ComponentDto; +import org.sonar.server.qualitygate.changeevent.QGChangeEventListener.ChangedIssue; +import org.sonar.server.qualitygate.changeevent.QGChangeEventListenersImpl.ChangedIssueImpl; + +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.emptySet; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.tuple; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.same; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; + +public class QGChangeEventListenersImplTest { + @Rule + public LogTester logTester = new LogTester(); + + private QGChangeEventListener listener1 = mock(QGChangeEventListener.class); + private QGChangeEventListener listener2 = mock(QGChangeEventListener.class); + private QGChangeEventListener listener3 = mock(QGChangeEventListener.class); + private List<QGChangeEventListener> listeners = Arrays.asList(listener1, listener2, listener3); + + private String component1Uuid = RandomStringUtils.randomAlphabetic(6); + private ComponentDto component1 = newComponentDto(component1Uuid); + private DefaultIssue component1Issue = newDefaultIssue(component1Uuid); + private List<DefaultIssue> oneIssueOnComponent1 = singletonList(component1Issue); + private QGChangeEvent component1QGChangeEvent = newQGChangeEvent(component1); + + private InOrder inOrder = Mockito.inOrder(listener1, listener2, listener3); + + private QGChangeEventListenersImpl underTest = new QGChangeEventListenersImpl(new QGChangeEventListener[] {listener1, listener2, listener3}); + + @Test + public void broadcastOnIssueChange_has_no_effect_when_issues_are_empty() { + underTest.broadcastOnIssueChange(emptyList(), singletonList(component1QGChangeEvent)); + + verifyZeroInteractions(listener1, listener2, listener3); + } + + @Test + public void broadcastOnIssueChange_has_no_effect_when_no_changeEvent() { + underTest.broadcastOnIssueChange(oneIssueOnComponent1, emptySet()); + + verifyZeroInteractions(listener1, listener2, listener3); + } + + @Test + public void broadcastOnIssueChange_passes_same_arguments_to_all_listeners_in_order_of_addition_to_constructor() { + underTest.broadcastOnIssueChange(oneIssueOnComponent1, singletonList(component1QGChangeEvent)); + + ArgumentCaptor<Set<ChangedIssue>> changedIssuesCaptor = newSetCaptor(); + inOrder.verify(listener1).onIssueChanges(same(component1QGChangeEvent), changedIssuesCaptor.capture()); + Set<ChangedIssue> changedIssues = changedIssuesCaptor.getValue(); + inOrder.verify(listener2).onIssueChanges(same(component1QGChangeEvent), same(changedIssues)); + inOrder.verify(listener3).onIssueChanges(same(component1QGChangeEvent), same(changedIssues)); + inOrder.verifyNoMoreInteractions(); + } + + @Test + public void broadcastOnIssueChange_calls_all_listeners_even_if_one_throws_an_exception() { + QGChangeEventListener failingListener = new QGChangeEventListener[] {listener1, listener2, listener3}[new Random().nextInt(3)]; + doThrow(new RuntimeException("Faking an exception thrown by onChanges")) + .when(failingListener) + .onIssueChanges(any(), any()); + + underTest.broadcastOnIssueChange(oneIssueOnComponent1, singletonList(component1QGChangeEvent)); + + ArgumentCaptor<Set<ChangedIssue>> changedIssuesCaptor = newSetCaptor(); + inOrder.verify(listener1).onIssueChanges(same(component1QGChangeEvent), changedIssuesCaptor.capture()); + Set<ChangedIssue> changedIssues = changedIssuesCaptor.getValue(); + inOrder.verify(listener2).onIssueChanges(same(component1QGChangeEvent), same(changedIssues)); + inOrder.verify(listener3).onIssueChanges(same(component1QGChangeEvent), same(changedIssues)); + inOrder.verifyNoMoreInteractions(); + assertThat(logTester.logs()).hasSize(4); + assertThat(logTester.logs(LoggerLevel.WARN)).hasSize(1); + } + + @Test + public void broadcastOnIssueChange_stops_calling_listeners_when_one_throws_an_ERROR() { + doThrow(new Error("Faking an error thrown by a listener")) + .when(listener2) + .onIssueChanges(any(), any()); + + underTest.broadcastOnIssueChange(oneIssueOnComponent1, singletonList(component1QGChangeEvent)); + + ArgumentCaptor<Set<ChangedIssue>> changedIssuesCaptor = newSetCaptor(); + inOrder.verify(listener1).onIssueChanges(same(component1QGChangeEvent), changedIssuesCaptor.capture()); + Set<ChangedIssue> changedIssues = changedIssuesCaptor.getValue(); + inOrder.verify(listener2).onIssueChanges(same(component1QGChangeEvent), same(changedIssues)); + inOrder.verifyNoMoreInteractions(); + assertThat(logTester.logs()).hasSize(3); + assertThat(logTester.logs(LoggerLevel.WARN)).hasSize(1); + } + + @Test + public void broadcastOnIssueChange_logs_each_listener_call_at_TRACE_level() { + underTest.broadcastOnIssueChange(oneIssueOnComponent1, singletonList(component1QGChangeEvent)); + + assertThat(logTester.logs()).hasSize(3); + List<String> traceLogs = logTester.logs(LoggerLevel.TRACE); + assertThat(traceLogs).hasSize(3) + .containsOnly( + "calling onChange() on listener " + listener1.getClass().getName() + " for events " + component1QGChangeEvent.toString() + "...", + "calling onChange() on listener " + listener2.getClass().getName() + " for events " + component1QGChangeEvent.toString() + "...", + "calling onChange() on listener " + listener3.getClass().getName() + " for events " + component1QGChangeEvent.toString() + "..."); + } + + @Test + public void broadcastOnIssueChange_passes_immutable_set_of_ChangedIssues() { + QGChangeEventListenersImpl underTest = new QGChangeEventListenersImpl(new QGChangeEventListener[] {listener1}); + + underTest.broadcastOnIssueChange(oneIssueOnComponent1, singletonList(component1QGChangeEvent)); + + ArgumentCaptor<Set<ChangedIssue>> changedIssuesCaptor = newSetCaptor(); + inOrder.verify(listener1).onIssueChanges(same(component1QGChangeEvent), changedIssuesCaptor.capture()); + assertThat(changedIssuesCaptor.getValue()).isInstanceOf(ImmutableSet.class); + } + + @Test + public void broadcastOnIssueChange_has_no_effect_when_no_listener() { + QGChangeEventListenersImpl underTest = new QGChangeEventListenersImpl(); + + underTest.broadcastOnIssueChange(oneIssueOnComponent1, singletonList(component1QGChangeEvent)); + + verifyZeroInteractions(listener1, listener2, listener3); + } + + @Test + public void broadcastOnIssueChange_calls_listener_for_each_component_uuid_with_at_least_one_QGChangeEvent() { + // component2 has multiple issues + ComponentDto component2 = newComponentDto(component1Uuid + "2"); + DefaultIssue[] component2Issues = {newDefaultIssue(component2.uuid()), newDefaultIssue(component2.uuid())}; + QGChangeEvent component2QGChangeEvent = newQGChangeEvent(component2); + + // component 3 has multiple QGChangeEvent and only one issue + ComponentDto component3 = newComponentDto(component1Uuid + "3"); + DefaultIssue component3Issue = newDefaultIssue(component3.uuid()); + QGChangeEvent[] component3QGChangeEvents = {newQGChangeEvent(component3), newQGChangeEvent(component3)}; + + // component 4 has multiple QGChangeEvent and multiples issues + ComponentDto component4 = newComponentDto(component1Uuid + "4"); + DefaultIssue[] component4Issues = {newDefaultIssue(component4.uuid()), newDefaultIssue(component4.uuid())}; + QGChangeEvent[] component4QGChangeEvents = {newQGChangeEvent(component4), newQGChangeEvent(component4)}; + + // component 5 has no QGChangeEvent but one issue + ComponentDto component5 = newComponentDto(component1Uuid + "5"); + DefaultIssue component5Issue = newDefaultIssue(component5.uuid()); + + List<DefaultIssue> issues = Stream.of( + Stream.of(component1Issue), + Arrays.stream(component2Issues), + Stream.of(component3Issue), + Arrays.stream(component4Issues), + Stream.of(component5Issue)) + .flatMap(s -> s) + .collect(Collectors.toList()); + + List<DefaultIssue> changedIssues = randomizedList(issues); + List<QGChangeEvent> qgChangeEvents = Stream.of( + Stream.of(component1QGChangeEvent), + Stream.of(component2QGChangeEvent), + Arrays.stream(component3QGChangeEvents), + Arrays.stream(component4QGChangeEvents)) + .flatMap(s -> s) + .collect(Collectors.toList()); + + underTest.broadcastOnIssueChange(changedIssues, randomizedList(qgChangeEvents)); + + listeners.forEach(listener -> { + verifyListenerCalled(listener, component1QGChangeEvent, component1Issue); + verifyListenerCalled(listener, component2QGChangeEvent, component2Issues); + Arrays.stream(component3QGChangeEvents) + .forEach(component3QGChangeEvent -> verifyListenerCalled(listener, component3QGChangeEvent, component3Issue)); + Arrays.stream(component4QGChangeEvents) + .forEach(component4QGChangeEvent -> verifyListenerCalled(listener, component4QGChangeEvent, component4Issues)); + }); + verifyNoMoreInteractions(listener1, listener2, listener3); + } + + @Test + public void isNotClosed_returns_true_if_issue_in_one_of_opened_states() { + DefaultIssue defaultIssue = new DefaultIssue(); + defaultIssue.setStatus(Issue.STATUS_REOPENED); + defaultIssue.setKey("abc"); + defaultIssue.setType(RuleType.BUG); + defaultIssue.setSeverity("BLOCKER"); + + ChangedIssue changedIssue = new ChangedIssueImpl(defaultIssue); + + assertThat(changedIssue.isNotClosed()).isTrue(); + } + + @Test + public void isNotClosed_returns_false_if_issue_in_one_of_closed_states() { + DefaultIssue defaultIssue = new DefaultIssue(); + defaultIssue.setStatus(Issue.STATUS_CONFIRMED); + defaultIssue.setKey("abc"); + defaultIssue.setType(RuleType.BUG); + defaultIssue.setSeverity("BLOCKER"); + + ChangedIssue changedIssue = new ChangedIssueImpl(defaultIssue); + + assertThat(changedIssue.isNotClosed()).isFalse(); + } + + @Test + public void test_status_mapping() { + assertThat(ChangedIssueImpl.statusOf(new DefaultIssue().setStatus(Issue.STATUS_OPEN))).isEqualTo(QGChangeEventListener.Status.OPEN); + assertThat(ChangedIssueImpl.statusOf(new DefaultIssue().setStatus(Issue.STATUS_REOPENED))).isEqualTo(QGChangeEventListener.Status.REOPENED); + assertThat(ChangedIssueImpl.statusOf(new DefaultIssue().setStatus(Issue.STATUS_CONFIRMED))).isEqualTo(QGChangeEventListener.Status.CONFIRMED); + assertThat(ChangedIssueImpl.statusOf(new DefaultIssue().setStatus(Issue.STATUS_RESOLVED).setResolution(Issue.RESOLUTION_FALSE_POSITIVE))) + .isEqualTo(QGChangeEventListener.Status.RESOLVED_FP); + assertThat(ChangedIssueImpl.statusOf(new DefaultIssue().setStatus(Issue.STATUS_RESOLVED).setResolution(Issue.RESOLUTION_WONT_FIX))) + .isEqualTo(QGChangeEventListener.Status.RESOLVED_WF); + assertThat(ChangedIssueImpl.statusOf(new DefaultIssue().setStatus(Issue.STATUS_RESOLVED).setResolution(Issue.RESOLUTION_FIXED))) + .isEqualTo(QGChangeEventListener.Status.RESOLVED_FIXED); + try { + ChangedIssueImpl.statusOf(new DefaultIssue().setStatus(Issue.STATUS_CLOSED)); + fail("Expected exception"); + } catch (Exception e) { + assertThat(e).hasMessage("Unexpected status: CLOSED"); + } + try { + ChangedIssueImpl.statusOf(new DefaultIssue().setStatus(Issue.STATUS_RESOLVED)); + fail("Expected exception"); + } catch (Exception e) { + assertThat(e).hasMessage("A resolved issue should have a resolution"); + } + try { + ChangedIssueImpl.statusOf(new DefaultIssue().setStatus(Issue.STATUS_RESOLVED).setResolution(Issue.RESOLUTION_REMOVED)); + fail("Expected exception"); + } catch (Exception e) { + assertThat(e).hasMessage("Unexpected resolution for a resolved issue: REMOVED"); + } + } + + @Test + public void test_status_mapping_on_security_hotspots() { + assertThat(ChangedIssueImpl.statusOf(new DefaultIssue().setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW))) + .isEqualTo(QGChangeEventListener.Status.TO_REVIEW); + assertThat(ChangedIssueImpl.statusOf(new DefaultIssue().setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_IN_REVIEW))) + .isEqualTo(QGChangeEventListener.Status.IN_REVIEW); + assertThat(ChangedIssueImpl.statusOf(new DefaultIssue().setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_REVIEWED))) + .isEqualTo(QGChangeEventListener.Status.REVIEWED); + } + + private void verifyListenerCalled(QGChangeEventListener listener, QGChangeEvent changeEvent, DefaultIssue... issues) { + ArgumentCaptor<Set<ChangedIssue>> changedIssuesCaptor = newSetCaptor(); + verify(listener).onIssueChanges(same(changeEvent), changedIssuesCaptor.capture()); + Set<ChangedIssue> changedIssues = changedIssuesCaptor.getValue(); + Tuple[] expected = Arrays.stream(issues) + .map(issue -> tuple(issue.key(), ChangedIssueImpl.statusOf(issue), issue.type())) + .toArray(Tuple[]::new); + assertThat(changedIssues) + .hasSize(issues.length) + .extracting(ChangedIssue::getKey, ChangedIssue::getStatus, ChangedIssue::getType) + .containsOnly(expected); + } + + private static final String[] POSSIBLE_STATUSES = asList(Issue.STATUS_CONFIRMED, Issue.STATUS_REOPENED, Issue.STATUS_RESOLVED).stream().toArray(String[]::new); + private static int issueIdCounter = 0; + + private static DefaultIssue newDefaultIssue(String projectUuid) { + DefaultIssue defaultIssue = new DefaultIssue(); + defaultIssue.setKey("issue_" + issueIdCounter++); + defaultIssue.setProjectUuid(projectUuid); + defaultIssue.setType(RuleType.values()[new Random().nextInt(RuleType.values().length)]); + defaultIssue.setStatus(POSSIBLE_STATUSES[new Random().nextInt(POSSIBLE_STATUSES.length)]); + String[] possibleResolutions = possibleResolutions(defaultIssue.getStatus()); + if (possibleResolutions.length > 0) { + defaultIssue.setResolution(possibleResolutions[new Random().nextInt(possibleResolutions.length)]); + } + return defaultIssue; + } + + private static String[] possibleResolutions(String status) { + switch (status) { + case Issue.STATUS_RESOLVED: + return new String[] {Issue.RESOLUTION_FALSE_POSITIVE, Issue.RESOLUTION_WONT_FIX}; + default: + return new String[0]; + } + } + + private static ComponentDto newComponentDto(String uuid) { + ComponentDto componentDto = new ComponentDto(); + componentDto.setUuid(uuid); + return componentDto; + } + + private static QGChangeEvent newQGChangeEvent(ComponentDto componentDto) { + QGChangeEvent res = mock(QGChangeEvent.class); + when(res.getProject()).thenReturn(componentDto); + return res; + } + + private static <T> ArgumentCaptor<Set<T>> newSetCaptor() { + Class<Set<T>> clazz = (Class<Set<T>>) (Class) Set.class; + return ArgumentCaptor.forClass(clazz); + } + + private static <T> List<T> randomizedList(List<T> issues) { + ArrayList<T> res = new ArrayList<>(issues); + Collections.shuffle(res); + return ImmutableList.copyOf(res); + } + +} diff --git a/server/sonar-webserver-api/src/test/java/org/sonar/server/qualitygate/changeevent/QGChangeEventTest.java b/server/sonar-webserver-api/src/test/java/org/sonar/server/qualitygate/changeevent/QGChangeEventTest.java new file mode 100644 index 00000000000..eb4ce5d6c60 --- /dev/null +++ b/server/sonar-webserver-api/src/test/java/org/sonar/server/qualitygate/changeevent/QGChangeEventTest.java @@ -0,0 +1,133 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.qualitygate.changeevent; + +import java.util.Optional; +import java.util.Random; +import java.util.function.Supplier; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.mockito.Mockito; +import org.sonar.api.config.Configuration; +import org.sonar.api.measures.Metric; +import org.sonar.db.component.BranchDto; +import org.sonar.db.component.BranchType; +import org.sonar.db.component.ComponentDto; +import org.sonar.db.component.SnapshotDto; +import org.sonar.server.qualitygate.EvaluatedQualityGate; + +import static org.assertj.core.api.Assertions.assertThat; + +public class QGChangeEventTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private ComponentDto project = new ComponentDto() + .setDbKey("foo") + .setUuid("bar"); + private BranchDto branch = new BranchDto() + .setBranchType(BranchType.SHORT) + .setUuid("bar") + .setProjectUuid("doh") + .setMergeBranchUuid("zop"); + private SnapshotDto analysis = new SnapshotDto() + .setUuid("pto") + .setCreatedAt(8_999_999_765L); + private Configuration configuration = Mockito.mock(Configuration.class); + private Metric.Level previousStatus = Metric.Level.values()[new Random().nextInt(Metric.Level.values().length)]; + private Supplier<Optional<EvaluatedQualityGate>> supplier = Optional::empty; + + @Test + public void constructor_fails_with_NPE_if_project_is_null() { + expectedException.expect(NullPointerException.class); + expectedException.expectMessage("project can't be null"); + + new QGChangeEvent(null, branch, analysis, configuration, previousStatus, supplier); + } + + @Test + public void constructor_fails_with_NPE_if_branch_is_null() { + expectedException.expect(NullPointerException.class); + expectedException.expectMessage("branch can't be null"); + + new QGChangeEvent(project, null, analysis, configuration, previousStatus, supplier); + } + + @Test + public void constructor_fails_with_NPE_if_analysis_is_null() { + expectedException.expect(NullPointerException.class); + expectedException.expectMessage("analysis can't be null"); + + new QGChangeEvent(project, branch, null, configuration, previousStatus, supplier); + } + + @Test + public void constructor_fails_with_NPE_if_configuration_is_null() { + expectedException.expect(NullPointerException.class); + expectedException.expectMessage("projectConfiguration can't be null"); + + new QGChangeEvent(project, branch, analysis, null, previousStatus, supplier); + } + + @Test + public void constructor_does_not_fail_with_NPE_if_previousStatus_is_null() { + new QGChangeEvent(project, branch, analysis, configuration, null, supplier); + } + + @Test + public void constructor_fails_with_NPE_if_supplier_is_null() { + expectedException.expect(NullPointerException.class); + expectedException.expectMessage("qualityGateSupplier can't be null"); + + new QGChangeEvent(project, branch, analysis, configuration, previousStatus, null); + } + + @Test + public void verify_getters() { + QGChangeEvent underTest = new QGChangeEvent(project, branch, analysis, configuration, previousStatus, supplier); + + assertThat(underTest.getProject()).isSameAs(project); + assertThat(underTest.getBranch()).isSameAs(branch); + assertThat(underTest.getAnalysis()).isSameAs(analysis); + assertThat(underTest.getProjectConfiguration()).isSameAs(configuration); + assertThat(underTest.getPreviousStatus()).contains(previousStatus); + assertThat(underTest.getQualityGateSupplier()).isSameAs(supplier); + } + + @Test + public void getPreviousStatus_returns_empty_when_previousStatus_is_null() { + QGChangeEvent underTest = new QGChangeEvent(project, branch, analysis, configuration, previousStatus, supplier); + + assertThat(underTest.getPreviousStatus()).contains(previousStatus); + } + + @Test + public void overrides_toString() { + QGChangeEvent underTest = new QGChangeEvent(project, branch, analysis, configuration, previousStatus, supplier); + + assertThat(underTest.toString()) + .isEqualTo("QGChangeEvent{project=bar:foo, branch=SHORT:bar:doh:zop, analysis=pto:8999999765" + + ", projectConfiguration=" + configuration.toString() + + ", previousStatus=" + previousStatus + + ", qualityGateSupplier=" + supplier + "}"); + + } +} diff --git a/server/sonar-webserver-api/src/test/java/org/sonar/server/setting/ProjectConfigurationLoaderImplTest.java b/server/sonar-webserver-api/src/test/java/org/sonar/server/setting/ProjectConfigurationLoaderImplTest.java new file mode 100644 index 00000000000..0fb88ee9b05 --- /dev/null +++ b/server/sonar-webserver-api/src/test/java/org/sonar/server/setting/ProjectConfigurationLoaderImplTest.java @@ -0,0 +1,178 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.setting; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import java.util.Collections; +import java.util.Map; +import org.junit.Before; +import org.junit.Test; +import org.sonar.api.config.Configuration; +import org.sonar.api.config.internal.MapSettings; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.component.ComponentDto; +import org.sonar.db.property.PropertiesDao; +import org.sonar.db.property.PropertyDto; + +import static java.util.Collections.emptyList; +import static java.util.Collections.singleton; +import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic; +import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; + +public class ProjectConfigurationLoaderImplTest { + private DbClient dbClient = mock(DbClient.class); + private DbSession dbSession = mock(DbSession.class); + private PropertiesDao propertiesDao = mock(PropertiesDao.class); + private MapSettings globalSettings = new MapSettings(); + private ProjectConfigurationLoaderImpl underTest = new ProjectConfigurationLoaderImpl(globalSettings, dbClient); + + @Before + public void setUp() throws Exception { + when(dbClient.openSession(anyBoolean())) + .thenThrow(new IllegalStateException("ProjectConfigurationLoaderImpl should not open DB session")); + when(dbClient.propertiesDao()).thenReturn(propertiesDao); + } + + @Test + public void returns_empty_map_when_no_component() { + assertThat(underTest.loadProjectConfigurations(dbSession, Collections.emptySet())) + .isEmpty(); + + verifyZeroInteractions(propertiesDao); + } + + @Test + public void return_configuration_with_just_global_settings_when_no_component_settings() { + String key = randomAlphanumeric(3); + String value = randomAlphanumeric(4); + String componentDbKey = randomAlphanumeric(5); + String componentUuid = randomAlphanumeric(6); + globalSettings.setProperty(key, value); + when(propertiesDao.selectProjectProperties(dbSession, componentDbKey)) + .thenReturn(emptyList()); + ComponentDto component = newComponentDto(componentDbKey, componentUuid); + + Map<String, Configuration> configurations = underTest.loadProjectConfigurations(dbSession, singleton(component)); + + assertThat(configurations) + .containsOnlyKeys(componentUuid); + assertThat(configurations.get(componentUuid).get(key)).contains(value); + } + + @Test + public void return_configuration_with_global_settings_and_component_settings() { + String globalKey = randomAlphanumeric(3); + String globalValue = randomAlphanumeric(4); + String componentDbKey = randomAlphanumeric(5); + String componentUuid = randomAlphanumeric(6); + String projectPropKey1 = randomAlphanumeric(7); + String projectPropValue1 = randomAlphanumeric(8); + String projectPropKey2 = randomAlphanumeric(9); + String projectPropValue2 = randomAlphanumeric(10); + globalSettings.setProperty(globalKey, globalValue); + when(propertiesDao.selectProjectProperties(dbSession, componentDbKey)) + .thenReturn(ImmutableList.of(newPropertyDto(projectPropKey1, projectPropValue1), newPropertyDto(projectPropKey2, projectPropValue2))); + ComponentDto component = newComponentDto(componentDbKey, componentUuid); + + Map<String, Configuration> configurations = underTest.loadProjectConfigurations(dbSession, singleton(component)); + + assertThat(configurations) + .containsOnlyKeys(componentUuid); + assertThat(configurations.get(componentUuid).get(globalKey)).contains(globalValue); + assertThat(configurations.get(componentUuid).get(projectPropKey1)).contains(projectPropValue1); + assertThat(configurations.get(componentUuid).get(projectPropKey2)).contains(projectPropValue2); + } + + @Test + public void return_configuration_with_global_settings_main_branch_settings_and_branch_settings() { + String globalKey = randomAlphanumeric(3); + String globalValue = randomAlphanumeric(4); + String mainBranchDbKey = randomAlphanumeric(5); + String branchDbKey = mainBranchDbKey + ComponentDto.BRANCH_KEY_SEPARATOR + randomAlphabetic(5); + String branchUuid = randomAlphanumeric(6); + String mainBranchPropKey = randomAlphanumeric(7); + String mainBranchPropValue = randomAlphanumeric(8); + String branchPropKey = randomAlphanumeric(9); + String branchPropValue = randomAlphanumeric(10); + globalSettings.setProperty(globalKey, globalValue); + when(propertiesDao.selectProjectProperties(dbSession, mainBranchDbKey)) + .thenReturn(ImmutableList.of(newPropertyDto(mainBranchPropKey, mainBranchPropValue))); + when(propertiesDao.selectProjectProperties(dbSession, branchDbKey)) + .thenReturn(ImmutableList.of(newPropertyDto(branchPropKey, branchPropValue))); + ComponentDto component = newComponentDto(branchDbKey, branchUuid); + + Map<String, Configuration> configurations = underTest.loadProjectConfigurations(dbSession, singleton(component)); + + assertThat(configurations) + .containsOnlyKeys(branchUuid); + assertThat(configurations.get(branchUuid).get(globalKey)).contains(globalValue); + assertThat(configurations.get(branchUuid).get(mainBranchPropKey)).contains(mainBranchPropValue); + assertThat(configurations.get(branchUuid).get(branchPropKey)).contains(branchPropValue); + } + + @Test + public void loads_configuration_of_any_given_component_only_once() { + String mainBranch1DbKey = randomAlphanumeric(4); + String mainBranch1Uuid = randomAlphanumeric(5); + String branch1DbKey = mainBranch1DbKey + ComponentDto.BRANCH_KEY_SEPARATOR + randomAlphabetic(5); + String branch1Uuid = randomAlphanumeric(6); + String branch2DbKey = mainBranch1DbKey + ComponentDto.BRANCH_KEY_SEPARATOR + randomAlphabetic(7); + String branch2Uuid = randomAlphanumeric(8); + String mainBranch2DbKey = randomAlphanumeric(14); + String mainBranch2Uuid = randomAlphanumeric(15); + String branch3DbKey = mainBranch2DbKey + ComponentDto.BRANCH_KEY_SEPARATOR + randomAlphabetic(5); + String branch3Uuid = randomAlphanumeric(16); + + ComponentDto mainBranch1 = newComponentDto(mainBranch1DbKey, mainBranch1Uuid); + ComponentDto branch1 = newComponentDto(branch1DbKey, branch1Uuid); + ComponentDto branch2 = newComponentDto(branch2DbKey, branch2Uuid); + ComponentDto mainBranch2 = newComponentDto(mainBranch2DbKey, mainBranch2Uuid); + ComponentDto branch3 = newComponentDto(branch3DbKey, branch3Uuid); + + underTest.loadProjectConfigurations(dbSession, ImmutableSet.of(mainBranch1, mainBranch2, branch1, branch2, branch3)); + + verify(propertiesDao, times(1)).selectProjectProperties(dbSession, mainBranch1DbKey); + verify(propertiesDao, times(1)).selectProjectProperties(dbSession, mainBranch2DbKey); + verify(propertiesDao, times(1)).selectProjectProperties(dbSession, branch1DbKey); + verify(propertiesDao, times(1)).selectProjectProperties(dbSession, branch2DbKey); + verify(propertiesDao, times(1)).selectProjectProperties(dbSession, branch3DbKey); + verifyNoMoreInteractions(propertiesDao); + } + + private ComponentDto newComponentDto(String componentDbKey, String componentUuid) { + return new ComponentDto().setDbKey(componentDbKey).setUuid(componentUuid); + } + + private PropertyDto newPropertyDto(String projectKey1, String projectValue1) { + return new PropertyDto() + .setKey(projectKey1) + .setValue(projectValue1); + } +} diff --git a/server/sonar-webserver-api/src/test/java/org/sonar/server/setting/SettingsChangeNotifierTest.java b/server/sonar-webserver-api/src/test/java/org/sonar/server/setting/SettingsChangeNotifierTest.java new file mode 100644 index 00000000000..577eda83519 --- /dev/null +++ b/server/sonar-webserver-api/src/test/java/org/sonar/server/setting/SettingsChangeNotifierTest.java @@ -0,0 +1,50 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.setting; + +import org.junit.Test; +import org.sonar.api.config.GlobalPropertyChangeHandler; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +public class SettingsChangeNotifierTest { + @Test + public void onGlobalPropertyChange() { + GlobalPropertyChangeHandler handler = mock(GlobalPropertyChangeHandler.class); + SettingsChangeNotifier notifier = new SettingsChangeNotifier(new GlobalPropertyChangeHandler[] {handler}); + + notifier.onGlobalPropertyChange("foo", "bar"); + + verify(handler).onChange(argThat(change -> change.getKey().equals("foo") && change.getNewValue().equals("bar"))); + } + + @Test + public void no_handlers() { + SettingsChangeNotifier notifier = new SettingsChangeNotifier(); + + assertThat(notifier.changeHandlers).isEmpty(); + + // does not fail + notifier.onGlobalPropertyChange("foo", "bar"); + } +} diff --git a/server/sonar-webserver-api/src/test/java/org/sonar/server/setting/TestProjectConfigurationLoader.java b/server/sonar-webserver-api/src/test/java/org/sonar/server/setting/TestProjectConfigurationLoader.java new file mode 100644 index 00000000000..e0c0b6d2854 --- /dev/null +++ b/server/sonar-webserver-api/src/test/java/org/sonar/server/setting/TestProjectConfigurationLoader.java @@ -0,0 +1,45 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.setting; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import org.sonar.api.config.Configuration; +import org.sonar.db.DbSession; +import org.sonar.db.component.ComponentDto; + +public class TestProjectConfigurationLoader implements ProjectConfigurationLoader { + + private final Configuration config; + + public TestProjectConfigurationLoader(Configuration config) { + this.config = config; + } + + @Override + public Map<String, Configuration> loadProjectConfigurations(DbSession dbSession, Set<ComponentDto> projects) { + Map<String, Configuration> map = new HashMap<>(); + for (ComponentDto project : projects) { + map.put(project.uuid(), config); + } + return map; + } +} diff --git a/server/sonar-webserver-api/src/test/java/org/sonar/server/util/BooleanTypeValidationTest.java b/server/sonar-webserver-api/src/test/java/org/sonar/server/util/BooleanTypeValidationTest.java new file mode 100644 index 00000000000..d11c03152b8 --- /dev/null +++ b/server/sonar-webserver-api/src/test/java/org/sonar/server/util/BooleanTypeValidationTest.java @@ -0,0 +1,57 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.util; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.server.exceptions.BadRequestException; + +import static org.assertj.core.api.Assertions.assertThat; + +public class BooleanTypeValidationTest { + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private BooleanTypeValidation underTest = new BooleanTypeValidation(); + + @Test + public void key() { + assertThat(underTest.key()).isEqualTo("BOOLEAN"); + } + + @Test + public void not_fail_on_valid_boolean() { + underTest.validate("true", null); + underTest.validate("True", null); + underTest.validate("false", null); + underTest.validate("FALSE", null); + } + + @Test + public void fail_on_invalid_boolean() { + expectedException.expect(BadRequestException.class); + expectedException.expectMessage("Value 'abc' must be one of \"true\" or \"false\"."); + + underTest.validate("abc", null); + } + +} diff --git a/server/sonar-webserver-api/src/test/java/org/sonar/server/util/FloatTypeValidationTest.java b/server/sonar-webserver-api/src/test/java/org/sonar/server/util/FloatTypeValidationTest.java new file mode 100644 index 00000000000..b88c3fbb651 --- /dev/null +++ b/server/sonar-webserver-api/src/test/java/org/sonar/server/util/FloatTypeValidationTest.java @@ -0,0 +1,56 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.util; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.server.exceptions.BadRequestException; + +import static org.assertj.core.api.Assertions.assertThat; + +public class FloatTypeValidationTest { + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private FloatTypeValidation validation = new FloatTypeValidation(); + + @Test + public void key() { + assertThat(validation.key()).isEqualTo("FLOAT"); + } + + @Test + public void not_fail_on_valid_float() { + validation.validate("10.2", null); + validation.validate("10", null); + validation.validate("-10.3", null); + } + + @Test + public void fail_on_invalid_float() { + expectedException.expect(BadRequestException.class); + expectedException.expectMessage("Value 'abc' must be an floating point number."); + + validation.validate("abc", null); + } + +} diff --git a/server/sonar-webserver-api/src/test/java/org/sonar/server/util/GlobalLockManagerTest.java b/server/sonar-webserver-api/src/test/java/org/sonar/server/util/GlobalLockManagerTest.java new file mode 100644 index 00000000000..8a069e1eae2 --- /dev/null +++ b/server/sonar-webserver-api/src/test/java/org/sonar/server/util/GlobalLockManagerTest.java @@ -0,0 +1,76 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.util; + +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.utils.System2; +import org.sonar.db.DbTester; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.sonar.server.util.GlobalLockManager.DEFAULT_LOCK_DURATION_SECONDS; + +public class GlobalLockManagerTest { + + private final System2 system2 = mock(System2.class); + + @Rule + public final DbTester dbTester = DbTester.create(system2); + + private final GlobalLockManager underTest = new GlobalLockManager(dbTester.getDbClient()); + + @Test + public void tryLock_succeeds_when_created_for_the_first_time() { + assertThat(underTest.tryLock("newName")).isTrue(); + } + + @Test + public void tryLock_fails_when_previous_lock_is_too_recent() { + String name = "newName"; + assertThat(underTest.tryLock(name)).isTrue(); + assertThat(underTest.tryLock(name)).isFalse(); + } + + @Test + public void tryLock_succeeds_when_previous_lock_is_old_enough() { + String name = "newName"; + long firstLock = 0; + long longEnoughAfterFirstLock = firstLock + DEFAULT_LOCK_DURATION_SECONDS * 1000; + long notLongEnoughAfterFirstLock = longEnoughAfterFirstLock - 1; + + when(system2.now()).thenReturn(firstLock); + assertThat(underTest.tryLock(name)).isTrue(); + + when(system2.now()).thenReturn(notLongEnoughAfterFirstLock); + assertThat(underTest.tryLock(name)).isFalse(); + + when(system2.now()).thenReturn(longEnoughAfterFirstLock); + assertThat(underTest.tryLock(name)).isTrue(); + } + + @Test + public void locks_with_different_name_are_independent() { + assertThat(underTest.tryLock("newName1")).isTrue(); + assertThat(underTest.tryLock("newName2")).isTrue(); + } + +} diff --git a/server/sonar-webserver-api/src/test/java/org/sonar/server/util/IntegerTypeValidationTest.java b/server/sonar-webserver-api/src/test/java/org/sonar/server/util/IntegerTypeValidationTest.java new file mode 100644 index 00000000000..e9b1953dd5a --- /dev/null +++ b/server/sonar-webserver-api/src/test/java/org/sonar/server/util/IntegerTypeValidationTest.java @@ -0,0 +1,63 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.util; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.server.exceptions.BadRequestException; + +import static org.assertj.core.api.Assertions.assertThat; + +public class IntegerTypeValidationTest { + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private IntegerTypeValidation validation = new IntegerTypeValidation(); + + @Test + public void key() { + assertThat(validation.key()).isEqualTo("INTEGER"); + } + + @Test + public void not_fail_on_valid_integer() { + validation.validate("10", null); + validation.validate("-10", null); + } + + @Test + public void fail_on_string() { + expectedException.expect(BadRequestException.class); + expectedException.expectMessage("Value 'abc' must be an integer."); + + validation.validate("abc", null); + } + + @Test + public void fail_on_float() { + expectedException.expect(BadRequestException.class); + expectedException.expectMessage("Value '10.1' must be an integer."); + + validation.validate("10.1", null); + } + +} diff --git a/server/sonar-webserver-api/src/test/java/org/sonar/server/util/LongTypeValidationTest.java b/server/sonar-webserver-api/src/test/java/org/sonar/server/util/LongTypeValidationTest.java new file mode 100644 index 00000000000..2f7d7e0946e --- /dev/null +++ b/server/sonar-webserver-api/src/test/java/org/sonar/server/util/LongTypeValidationTest.java @@ -0,0 +1,62 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.util; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.PropertyType; +import org.sonar.server.exceptions.BadRequestException; + +import static org.assertj.core.api.Assertions.assertThat; + +public class LongTypeValidationTest { + + LongTypeValidation underTest = new LongTypeValidation(); + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + @Test + public void key_is_long_type_name() { + assertThat(underTest.key()).isEqualTo(PropertyType.LONG.name()); + } + + @Test + public void do_not_fail_with_long_values() { + underTest.validate("1984", null); + underTest.validate("-1984", null); + } + + @Test + public void fail_when_float() { + expectedException.expect(BadRequestException.class); + expectedException.expectMessage("Value '3.14' must be a long."); + + underTest.validate("3.14", null); + } + + @Test + public void fail_when_string() { + expectedException.expect(BadRequestException.class); + expectedException.expectMessage("Value 'original string' must be a long."); + + underTest.validate("original string", null); + } +} diff --git a/server/sonar-webserver-api/src/test/java/org/sonar/server/util/StringListTypeValidationTest.java b/server/sonar-webserver-api/src/test/java/org/sonar/server/util/StringListTypeValidationTest.java new file mode 100644 index 00000000000..9f2630fd0de --- /dev/null +++ b/server/sonar-webserver-api/src/test/java/org/sonar/server/util/StringListTypeValidationTest.java @@ -0,0 +1,56 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.util; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.server.exceptions.BadRequestException; + +import static com.google.common.collect.Lists.newArrayList; +import static org.assertj.core.api.Assertions.assertThat; + +public class StringListTypeValidationTest { + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private StringListTypeValidation validation = new StringListTypeValidation(); + + @Test + public void key() { + assertThat(validation.key()).isEqualTo("SINGLE_SELECT_LIST"); + } + + @Test + public void not_fail_on_valid_option() { + validation.validate("a", newArrayList("a", "b", "c")); + validation.validate("a", null); + } + + @Test + public void fail_on_invalid_option() { + expectedException.expect(BadRequestException.class); + expectedException.expectMessage("Value 'abc' must be one of : a, b, c."); + + validation.validate("abc", newArrayList("a", "b", "c")); + } + +} diff --git a/server/sonar-webserver-api/src/test/java/org/sonar/server/util/StringTypeValidationTest.java b/server/sonar-webserver-api/src/test/java/org/sonar/server/util/StringTypeValidationTest.java new file mode 100644 index 00000000000..dde3afa3543 --- /dev/null +++ b/server/sonar-webserver-api/src/test/java/org/sonar/server/util/StringTypeValidationTest.java @@ -0,0 +1,47 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.util; + +import org.junit.Before; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class StringTypeValidationTest { + + StringTypeValidation validation; + + @Before + public void setUp() { + validation = new StringTypeValidation(); + } + + @Test + public void key() { + assertThat(validation.key()).isEqualTo("STRING"); + } + + @Test + public void not_fail_on_valid_string() { + validation.validate("10", null); + validation.validate("abc", null); + } + +} diff --git a/server/sonar-webserver-api/src/test/java/org/sonar/server/util/TextTypeValidationTest.java b/server/sonar-webserver-api/src/test/java/org/sonar/server/util/TextTypeValidationTest.java new file mode 100644 index 00000000000..4261f95c580 --- /dev/null +++ b/server/sonar-webserver-api/src/test/java/org/sonar/server/util/TextTypeValidationTest.java @@ -0,0 +1,47 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.util; + +import org.junit.Before; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TextTypeValidationTest { + + TextTypeValidation validation; + + @Before + public void setUp() { + validation = new TextTypeValidation(); + } + + @Test + public void key() { + assertThat(validation.key()).isEqualTo("TEXT"); + } + + @Test + public void not_fail_on_valid_text() { + validation.validate("10", null); + validation.validate("abc", null); + } + +} diff --git a/server/sonar-webserver-api/src/test/java/org/sonar/server/util/TypeValidationModuleTest.java b/server/sonar-webserver-api/src/test/java/org/sonar/server/util/TypeValidationModuleTest.java new file mode 100644 index 00000000000..3ff709c8f0f --- /dev/null +++ b/server/sonar-webserver-api/src/test/java/org/sonar/server/util/TypeValidationModuleTest.java @@ -0,0 +1,34 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.util; + +import org.junit.Test; +import org.sonar.core.platform.ComponentContainer; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TypeValidationModuleTest { + @Test + public void verify_count_of_added_components() { + ComponentContainer container = new ComponentContainer(); + new TypeValidationModule().configure(container); + assertThat(container.size()).isEqualTo(11); + } +} diff --git a/server/sonar-webserver-api/src/test/java/org/sonar/server/util/TypeValidationsTest.java b/server/sonar-webserver-api/src/test/java/org/sonar/server/util/TypeValidationsTest.java new file mode 100644 index 00000000000..4dbae47cb37 --- /dev/null +++ b/server/sonar-webserver-api/src/test/java/org/sonar/server/util/TypeValidationsTest.java @@ -0,0 +1,72 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.util; + +import org.junit.Test; +import org.sonar.server.exceptions.BadRequestException; + +import static com.google.common.collect.Lists.newArrayList; +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.verify; +import static org.mockito.Mockito.when; + +public class TypeValidationsTest { + + @Test + public void validate() { + TypeValidation fakeTypeValidation = mock(TypeValidation.class); + when(fakeTypeValidation.key()).thenReturn("Fake"); + + TypeValidations typeValidations = new TypeValidations(newArrayList(fakeTypeValidation)); + typeValidations.validate("10", "Fake", newArrayList("a")); + + verify(fakeTypeValidation).validate("10", newArrayList("a")); + } + + @Test + public void validate__multiple_values() { + TypeValidation fakeTypeValidation = mock(TypeValidation.class); + when(fakeTypeValidation.key()).thenReturn("Fake"); + + TypeValidations typeValidations = new TypeValidations(newArrayList(fakeTypeValidation)); + typeValidations.validate(newArrayList("10", "11", "12"), "Fake", newArrayList("11")); + + verify(fakeTypeValidation).validate("10", newArrayList("11")); + } + + @Test + public void fail_on_unknown_type() { + TypeValidation fakeTypeValidation = mock(TypeValidation.class); + when(fakeTypeValidation.key()).thenReturn("Fake"); + + try { + TypeValidations typeValidations = new TypeValidations(newArrayList(fakeTypeValidation)); + typeValidations.validate("10", "Unknown", null); + fail(); + } catch (Exception e) { + assertThat(e).isInstanceOf(BadRequestException.class); + BadRequestException badRequestException = (BadRequestException) e; + assertThat(badRequestException.getMessage()).isEqualTo("Type 'Unknown' is not valid."); + } + } + +} diff --git a/server/sonar-webserver-api/src/test/java/org/sonar/server/util/TypeValidationsTesting.java b/server/sonar-webserver-api/src/test/java/org/sonar/server/util/TypeValidationsTesting.java new file mode 100644 index 00000000000..b237ba62aab --- /dev/null +++ b/server/sonar-webserver-api/src/test/java/org/sonar/server/util/TypeValidationsTesting.java @@ -0,0 +1,40 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.util; + +import java.util.Arrays; + +public class TypeValidationsTesting { + private TypeValidationsTesting() { + // utility class + } + + public static TypeValidations newFullTypeValidations() { + return new TypeValidations(Arrays.asList( + new BooleanTypeValidation(), + new IntegerTypeValidation(), + new LongTypeValidation(), + new FloatTypeValidation(), + new StringTypeValidation(), + new StringListTypeValidation(), + new MetricLevelTypeValidation() + )); + } +} diff --git a/server/sonar-webserver-api/src/test/projects/.gitignore b/server/sonar-webserver-api/src/test/projects/.gitignore new file mode 100644 index 00000000000..a945b8525e6 --- /dev/null +++ b/server/sonar-webserver-api/src/test/projects/.gitignore @@ -0,0 +1,7 @@ +# see README.txt +!*/target/ +*/target/classes/ +*/target/maven-archiver/ +*/target/maven-status/ +*/target/test-*/ + diff --git a/server/sonar-webserver-api/src/test/projects/README.txt b/server/sonar-webserver-api/src/test/projects/README.txt new file mode 100644 index 00000000000..c53a66d52f2 --- /dev/null +++ b/server/sonar-webserver-api/src/test/projects/README.txt @@ -0,0 +1,3 @@ +This directory provides the fake plugins used by tests. These tests are rarely changed, so generated +artifacts are stored in Git repository (see .gitignore). It avoids from adding unnecessary modules +to build. diff --git a/server/sonar-webserver-api/src/test/projects/fake-report-plugin/pom.xml b/server/sonar-webserver-api/src/test/projects/fake-report-plugin/pom.xml new file mode 100644 index 00000000000..72a04dbe04f --- /dev/null +++ b/server/sonar-webserver-api/src/test/projects/fake-report-plugin/pom.xml @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <groupId>org.sonarsource.sonarqube.tests</groupId> + <artifactId>fake-report-plugin</artifactId> + <version>0.1-SNAPSHOT</version> + <packaging>sonar-plugin</packaging> + <name>Fake Report Plugin</name> + <description>Fake Report Plugin</description> + + <dependencies> + <dependency> + <groupId>org.codehaus.sonar</groupId> + <artifactId>sonar-plugin-api</artifactId> + <version>4.5.4</version> + <scope>provided</scope> + </dependency> + </dependencies> + <build> + <sourceDirectory>src</sourceDirectory> + <plugins> + <plugin> + <groupId>org.sonarsource.sonar-packaging-maven-plugin</groupId> + <artifactId>sonar-packaging-maven-plugin</artifactId> + <version>1.15</version> + <extensions>true</extensions> + <configuration> + <pluginKey>report</pluginKey> + <pluginClass>BasePlugin</pluginClass> + </configuration> + </plugin> + </plugins> + </build> + +</project> diff --git a/server/sonar-webserver-api/src/test/projects/fake-report-plugin/src/BasePlugin.java b/server/sonar-webserver-api/src/test/projects/fake-report-plugin/src/BasePlugin.java new file mode 100644 index 00000000000..d12daff3e57 --- /dev/null +++ b/server/sonar-webserver-api/src/test/projects/fake-report-plugin/src/BasePlugin.java @@ -0,0 +1,30 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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. + */ +import org.sonar.api.Plugin; + +import java.util.Collections; +import java.util.List; + +public class BasePlugin extends Plugin { + + public void define(Plugin.Context context) { + + } +} diff --git a/server/sonar-webserver-api/src/test/projects/fake-report-plugin/src/org/sonar/plugins/testbase/api/BaseApi.java b/server/sonar-webserver-api/src/test/projects/fake-report-plugin/src/org/sonar/plugins/testbase/api/BaseApi.java new file mode 100644 index 00000000000..e0b54398eaf --- /dev/null +++ b/server/sonar-webserver-api/src/test/projects/fake-report-plugin/src/org/sonar/plugins/testbase/api/BaseApi.java @@ -0,0 +1,25 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.plugins.testbase.api; + +public class BaseApi { + public void doNothing() { + } +} diff --git a/server/sonar-webserver-api/src/test/projects/fake-report-plugin/target/fake-report-plugin-0.1-SNAPSHOT.jar b/server/sonar-webserver-api/src/test/projects/fake-report-plugin/target/fake-report-plugin-0.1-SNAPSHOT.jar Binary files differnew file mode 100644 index 00000000000..6085e44fdca --- /dev/null +++ b/server/sonar-webserver-api/src/test/projects/fake-report-plugin/target/fake-report-plugin-0.1-SNAPSHOT.jar diff --git a/server/sonar-webserver-api/src/test/projects/fake-sqale-plugin/pom.xml b/server/sonar-webserver-api/src/test/projects/fake-sqale-plugin/pom.xml new file mode 100644 index 00000000000..e417dd96fba --- /dev/null +++ b/server/sonar-webserver-api/src/test/projects/fake-sqale-plugin/pom.xml @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <groupId>org.sonarsource.sonarqube.tests</groupId> + <artifactId>fake-sqale-plugin</artifactId> + <version>0.1-SNAPSHOT</version> + <packaging>sonar-plugin</packaging> + <name>Fake SQALE Plugin</name> + <description>Fake SQALE Plugin</description> + + <dependencies> + <dependency> + <groupId>org.codehaus.sonar</groupId> + <artifactId>sonar-plugin-api</artifactId> + <version>4.5.4</version> + <scope>provided</scope> + </dependency> + </dependencies> + <build> + <sourceDirectory>src</sourceDirectory> + <plugins> + <plugin> + <groupId>org.sonarsource.sonar-packaging-maven-plugin</groupId> + <artifactId>sonar-packaging-maven-plugin</artifactId> + <version>1.15</version> + <extensions>true</extensions> + <configuration> + <pluginKey>sqale</pluginKey> + <pluginClass>BasePlugin</pluginClass> + </configuration> + </plugin> + </plugins> + </build> + +</project> diff --git a/server/sonar-webserver-api/src/test/projects/fake-sqale-plugin/src/BasePlugin.java b/server/sonar-webserver-api/src/test/projects/fake-sqale-plugin/src/BasePlugin.java new file mode 100644 index 00000000000..d12daff3e57 --- /dev/null +++ b/server/sonar-webserver-api/src/test/projects/fake-sqale-plugin/src/BasePlugin.java @@ -0,0 +1,30 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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. + */ +import org.sonar.api.Plugin; + +import java.util.Collections; +import java.util.List; + +public class BasePlugin extends Plugin { + + public void define(Plugin.Context context) { + + } +} diff --git a/server/sonar-webserver-api/src/test/projects/fake-sqale-plugin/src/org/sonar/plugins/testbase/api/BaseApi.java b/server/sonar-webserver-api/src/test/projects/fake-sqale-plugin/src/org/sonar/plugins/testbase/api/BaseApi.java new file mode 100644 index 00000000000..e0b54398eaf --- /dev/null +++ b/server/sonar-webserver-api/src/test/projects/fake-sqale-plugin/src/org/sonar/plugins/testbase/api/BaseApi.java @@ -0,0 +1,25 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.plugins.testbase.api; + +public class BaseApi { + public void doNothing() { + } +} diff --git a/server/sonar-webserver-api/src/test/projects/fake-sqale-plugin/target/fake-sqale-plugin-0.1-SNAPSHOT.jar b/server/sonar-webserver-api/src/test/projects/fake-sqale-plugin/target/fake-sqale-plugin-0.1-SNAPSHOT.jar Binary files differnew file mode 100644 index 00000000000..b5c99f721b3 --- /dev/null +++ b/server/sonar-webserver-api/src/test/projects/fake-sqale-plugin/target/fake-sqale-plugin-0.1-SNAPSHOT.jar diff --git a/server/sonar-webserver-api/src/test/projects/fake-views-plugin/pom.xml b/server/sonar-webserver-api/src/test/projects/fake-views-plugin/pom.xml new file mode 100644 index 00000000000..1ef73d2ffda --- /dev/null +++ b/server/sonar-webserver-api/src/test/projects/fake-views-plugin/pom.xml @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <groupId>org.sonarsource.sonarqube.tests</groupId> + <artifactId>fake-views-plugin</artifactId> + <version>0.1-SNAPSHOT</version> + <packaging>sonar-plugin</packaging> + <name>Fake Views Plugin</name> + <description>Fake Views Plugin</description> + + <dependencies> + <dependency> + <groupId>org.codehaus.sonar</groupId> + <artifactId>sonar-plugin-api</artifactId> + <version>4.5.4</version> + <scope>provided</scope> + </dependency> + </dependencies> + <build> + <sourceDirectory>src</sourceDirectory> + <plugins> + <plugin> + <groupId>org.sonarsource.sonar-packaging-maven-plugin</groupId> + <artifactId>sonar-packaging-maven-plugin</artifactId> + <version>1.15</version> + <extensions>true</extensions> + <configuration> + <pluginKey>views</pluginKey> + <pluginClass>BasePlugin</pluginClass> + </configuration> + </plugin> + </plugins> + </build> + +</project> diff --git a/server/sonar-webserver-api/src/test/projects/fake-views-plugin/src/BasePlugin.java b/server/sonar-webserver-api/src/test/projects/fake-views-plugin/src/BasePlugin.java new file mode 100644 index 00000000000..d12daff3e57 --- /dev/null +++ b/server/sonar-webserver-api/src/test/projects/fake-views-plugin/src/BasePlugin.java @@ -0,0 +1,30 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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. + */ +import org.sonar.api.Plugin; + +import java.util.Collections; +import java.util.List; + +public class BasePlugin extends Plugin { + + public void define(Plugin.Context context) { + + } +} diff --git a/server/sonar-webserver-api/src/test/projects/fake-views-plugin/src/org/sonar/plugins/testbase/api/BaseApi.java b/server/sonar-webserver-api/src/test/projects/fake-views-plugin/src/org/sonar/plugins/testbase/api/BaseApi.java new file mode 100644 index 00000000000..e0b54398eaf --- /dev/null +++ b/server/sonar-webserver-api/src/test/projects/fake-views-plugin/src/org/sonar/plugins/testbase/api/BaseApi.java @@ -0,0 +1,25 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.plugins.testbase.api; + +public class BaseApi { + public void doNothing() { + } +} diff --git a/server/sonar-webserver-api/src/test/projects/fake-views-plugin/target/fake-views-plugin-0.1-SNAPSHOT.jar b/server/sonar-webserver-api/src/test/projects/fake-views-plugin/target/fake-views-plugin-0.1-SNAPSHOT.jar Binary files differnew file mode 100644 index 00000000000..a47d93d94a8 --- /dev/null +++ b/server/sonar-webserver-api/src/test/projects/fake-views-plugin/target/fake-views-plugin-0.1-SNAPSHOT.jar diff --git a/server/sonar-webserver-api/src/test/projects/pom.xml b/server/sonar-webserver-api/src/test/projects/pom.xml new file mode 100644 index 00000000000..37338313ac0 --- /dev/null +++ b/server/sonar-webserver-api/src/test/projects/pom.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <groupId>org.sonarsource.sonarqube.tests</groupId> + <artifactId>parent</artifactId> + <version>0.1-SNAPSHOT</version> + <packaging>pom</packaging> + <modules> + <module>test-base-plugin</module> + <module>test-base-plugin-v2</module> + <module>test-core-plugin</module> + <module>test-extend-plugin</module> + <module>test-libs-plugin</module> + <module>test-require-plugin</module> + <module>test-requirenew-plugin</module> + <module>fake-report-plugin</module> + <module>fake-sqale-plugin</module> + <module>fake-views-plugin</module> + </modules> + +</project> diff --git a/server/sonar-webserver-api/src/test/projects/test-base-plugin-v2/pom.xml b/server/sonar-webserver-api/src/test/projects/test-base-plugin-v2/pom.xml new file mode 100644 index 00000000000..982be1c0170 --- /dev/null +++ b/server/sonar-webserver-api/src/test/projects/test-base-plugin-v2/pom.xml @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <groupId>org.sonarsource.sonarqube.tests</groupId> + <artifactId>test-base-plugin</artifactId> + <version>0.2-SNAPSHOT</version> + <packaging>sonar-plugin</packaging> + <name>Base Plugin</name> + <description>Simple standalone plugin. Used by other fake plugins.</description> + + <dependencies> + <dependency> + <groupId>org.codehaus.sonar</groupId> + <artifactId>sonar-plugin-api</artifactId> + <version>4.5.4</version> + <scope>provided</scope> + </dependency> + </dependencies> + <build> + <sourceDirectory>src</sourceDirectory> + <plugins> + <plugin> + <groupId>org.sonarsource.sonar-packaging-maven-plugin</groupId> + <artifactId>sonar-packaging-maven-plugin</artifactId> + <version>1.15</version> + <extensions>true</extensions> + <configuration> + <pluginKey>testbase</pluginKey> + <pluginClass>BasePlugin</pluginClass> + </configuration> + </plugin> + </plugins> + </build> + +</project> diff --git a/server/sonar-webserver-api/src/test/projects/test-base-plugin-v2/src/BasePlugin.java b/server/sonar-webserver-api/src/test/projects/test-base-plugin-v2/src/BasePlugin.java new file mode 100644 index 00000000000..d12daff3e57 --- /dev/null +++ b/server/sonar-webserver-api/src/test/projects/test-base-plugin-v2/src/BasePlugin.java @@ -0,0 +1,30 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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. + */ +import org.sonar.api.Plugin; + +import java.util.Collections; +import java.util.List; + +public class BasePlugin extends Plugin { + + public void define(Plugin.Context context) { + + } +} diff --git a/server/sonar-webserver-api/src/test/projects/test-base-plugin-v2/src/org/sonar/plugins/testbase/api/BaseApi.java b/server/sonar-webserver-api/src/test/projects/test-base-plugin-v2/src/org/sonar/plugins/testbase/api/BaseApi.java new file mode 100644 index 00000000000..e0b54398eaf --- /dev/null +++ b/server/sonar-webserver-api/src/test/projects/test-base-plugin-v2/src/org/sonar/plugins/testbase/api/BaseApi.java @@ -0,0 +1,25 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.plugins.testbase.api; + +public class BaseApi { + public void doNothing() { + } +} diff --git a/server/sonar-webserver-api/src/test/projects/test-base-plugin-v2/target/test-base-plugin-0.2-SNAPSHOT.jar b/server/sonar-webserver-api/src/test/projects/test-base-plugin-v2/target/test-base-plugin-0.2-SNAPSHOT.jar Binary files differnew file mode 100644 index 00000000000..1d4ef5430c7 --- /dev/null +++ b/server/sonar-webserver-api/src/test/projects/test-base-plugin-v2/target/test-base-plugin-0.2-SNAPSHOT.jar diff --git a/server/sonar-webserver-api/src/test/projects/test-base-plugin/pom.xml b/server/sonar-webserver-api/src/test/projects/test-base-plugin/pom.xml new file mode 100644 index 00000000000..c4e95936e74 --- /dev/null +++ b/server/sonar-webserver-api/src/test/projects/test-base-plugin/pom.xml @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <groupId>org.sonarsource.sonarqube.tests</groupId> + <artifactId>test-base-plugin</artifactId> + <version>0.1-SNAPSHOT</version> + <packaging>sonar-plugin</packaging> + <name>Base Plugin</name> + <description>Simple standalone plugin. Used by other fake plugins.</description> + + <dependencies> + <dependency> + <groupId>org.codehaus.sonar</groupId> + <artifactId>sonar-plugin-api</artifactId> + <version>4.5.4</version> + <scope>provided</scope> + </dependency> + </dependencies> + <build> + <sourceDirectory>src</sourceDirectory> + <plugins> + <plugin> + <groupId>org.sonarsource.sonar-packaging-maven-plugin</groupId> + <artifactId>sonar-packaging-maven-plugin</artifactId> + <version>1.15</version> + <extensions>true</extensions> + <configuration> + <pluginKey>testbase</pluginKey> + <pluginClass>BasePlugin</pluginClass> + </configuration> + </plugin> + </plugins> + </build> + +</project> diff --git a/server/sonar-webserver-api/src/test/projects/test-base-plugin/src/BasePlugin.java b/server/sonar-webserver-api/src/test/projects/test-base-plugin/src/BasePlugin.java new file mode 100644 index 00000000000..d12daff3e57 --- /dev/null +++ b/server/sonar-webserver-api/src/test/projects/test-base-plugin/src/BasePlugin.java @@ -0,0 +1,30 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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. + */ +import org.sonar.api.Plugin; + +import java.util.Collections; +import java.util.List; + +public class BasePlugin extends Plugin { + + public void define(Plugin.Context context) { + + } +} diff --git a/server/sonar-webserver-api/src/test/projects/test-base-plugin/src/org/sonar/plugins/testbase/api/BaseApi.java b/server/sonar-webserver-api/src/test/projects/test-base-plugin/src/org/sonar/plugins/testbase/api/BaseApi.java new file mode 100644 index 00000000000..e0b54398eaf --- /dev/null +++ b/server/sonar-webserver-api/src/test/projects/test-base-plugin/src/org/sonar/plugins/testbase/api/BaseApi.java @@ -0,0 +1,25 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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.plugins.testbase.api; + +public class BaseApi { + public void doNothing() { + } +} diff --git a/server/sonar-webserver-api/src/test/projects/test-base-plugin/target/test-base-plugin-0.1-SNAPSHOT.jar b/server/sonar-webserver-api/src/test/projects/test-base-plugin/target/test-base-plugin-0.1-SNAPSHOT.jar Binary files differnew file mode 100644 index 00000000000..739a22fcdae --- /dev/null +++ b/server/sonar-webserver-api/src/test/projects/test-base-plugin/target/test-base-plugin-0.1-SNAPSHOT.jar diff --git a/server/sonar-webserver-api/src/test/projects/test-extend-plugin/pom.xml b/server/sonar-webserver-api/src/test/projects/test-extend-plugin/pom.xml new file mode 100644 index 00000000000..e23667e6318 --- /dev/null +++ b/server/sonar-webserver-api/src/test/projects/test-extend-plugin/pom.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <groupId>org.sonarsource.sonarqube.tests</groupId> + <artifactId>test-extend-plugin</artifactId> + <version>0.1-SNAPSHOT</version> + <packaging>sonar-plugin</packaging> + <name>Test Extend Plugin</name> + <description>Fake plugin that extends the plugin with key "base"</description> + + <dependencies> + <dependency> + <groupId>org.codehaus.sonar</groupId> + <artifactId>sonar-plugin-api</artifactId> + <version>4.5.4</version> + <scope>provided</scope> + </dependency> + </dependencies> + <build> + <sourceDirectory>src</sourceDirectory> + <plugins> + <plugin> + <groupId>org.sonarsource.sonar-packaging-maven-plugin</groupId> + <artifactId>sonar-packaging-maven-plugin</artifactId> + <version>1.15</version> + <extensions>true</extensions> + <configuration> + <pluginKey>testextend</pluginKey> + <pluginClass>ExtendPlugin</pluginClass> + <basePlugin>testbase</basePlugin> + </configuration> + </plugin> + </plugins> + </build> + +</project> diff --git a/server/sonar-webserver-api/src/test/projects/test-extend-plugin/src/ExtendPlugin.java b/server/sonar-webserver-api/src/test/projects/test-extend-plugin/src/ExtendPlugin.java new file mode 100644 index 00000000000..d364a2f9dd4 --- /dev/null +++ b/server/sonar-webserver-api/src/test/projects/test-extend-plugin/src/ExtendPlugin.java @@ -0,0 +1,30 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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. + */ +import org.sonar.api.Plugin; + +import java.util.Collections; +import java.util.List; + +public class ExtendPlugin extends Plugin { + + public void define(Plugin.Context context) { + + } +} diff --git a/server/sonar-webserver-api/src/test/projects/test-extend-plugin/target/test-extend-plugin-0.1-SNAPSHOT.jar b/server/sonar-webserver-api/src/test/projects/test-extend-plugin/target/test-extend-plugin-0.1-SNAPSHOT.jar Binary files differnew file mode 100644 index 00000000000..2f63c2e652c --- /dev/null +++ b/server/sonar-webserver-api/src/test/projects/test-extend-plugin/target/test-extend-plugin-0.1-SNAPSHOT.jar diff --git a/server/sonar-webserver-api/src/test/projects/test-libs-plugin/pom.xml b/server/sonar-webserver-api/src/test/projects/test-libs-plugin/pom.xml new file mode 100644 index 00000000000..2d49cca2cf2 --- /dev/null +++ b/server/sonar-webserver-api/src/test/projects/test-libs-plugin/pom.xml @@ -0,0 +1,49 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <groupId>org.sonarsource.sonarqube.tests</groupId> + <artifactId>test-libs-plugin</artifactId> + <version>0.1-SNAPSHOT</version> + <packaging>sonar-plugin</packaging> + <name>Test Libs Plugin</name> + <description>Fake plugin that embeds some libraries</description> + + <dependencies> + <!-- embedded libs. Chosen because small ! --> + <dependency> + <groupId>commons-email</groupId> + <artifactId>commons-email</artifactId> + <version>20030310.165926</version> + </dependency> + <dependency> + <groupId>commons-daemon</groupId> + <artifactId>commons-daemon</artifactId> + <version>1.0.15</version> + </dependency> + + <dependency> + <groupId>org.codehaus.sonar</groupId> + <artifactId>sonar-plugin-api</artifactId> + <version>4.5.4</version> + <scope>provided</scope> + </dependency> + </dependencies> + + <build> + <sourceDirectory>src</sourceDirectory> + <plugins> + <plugin> + <groupId>org.sonarsource.sonar-packaging-maven-plugin</groupId> + <artifactId>sonar-packaging-maven-plugin</artifactId> + <version>1.15</version> + <extensions>true</extensions> + <configuration> + <pluginKey>testlibs</pluginKey> + <pluginClass>LibsPlugin</pluginClass> + </configuration> + </plugin> + </plugins> + </build> + +</project> diff --git a/server/sonar-webserver-api/src/test/projects/test-libs-plugin/src/LibsPlugin.java b/server/sonar-webserver-api/src/test/projects/test-libs-plugin/src/LibsPlugin.java new file mode 100644 index 00000000000..7e3ebe0909c --- /dev/null +++ b/server/sonar-webserver-api/src/test/projects/test-libs-plugin/src/LibsPlugin.java @@ -0,0 +1,30 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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. + */ +import org.sonar.api.Plugin; + +import java.util.Collections; +import java.util.List; + +public class LibsPlugin extends Plugin { + + public void define(Plugin.Context context) { + + } +} diff --git a/server/sonar-webserver-api/src/test/projects/test-libs-plugin/target/test-libs-plugin-0.1-SNAPSHOT.jar b/server/sonar-webserver-api/src/test/projects/test-libs-plugin/target/test-libs-plugin-0.1-SNAPSHOT.jar Binary files differnew file mode 100644 index 00000000000..6ebe8652d8b --- /dev/null +++ b/server/sonar-webserver-api/src/test/projects/test-libs-plugin/target/test-libs-plugin-0.1-SNAPSHOT.jar diff --git a/server/sonar-webserver-api/src/test/projects/test-require-plugin/pom.xml b/server/sonar-webserver-api/src/test/projects/test-require-plugin/pom.xml new file mode 100644 index 00000000000..62462ffbf34 --- /dev/null +++ b/server/sonar-webserver-api/src/test/projects/test-require-plugin/pom.xml @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <groupId>org.sonarsource.sonarqube.tests</groupId> + <artifactId>test-require-plugin</artifactId> + <version>0.1-SNAPSHOT</version> + <packaging>sonar-plugin</packaging> + <name>Test Require Plugin</name> + <description>This fake plugin depends on test-base-plugin</description> + + <dependencies> + <dependency> + <groupId>org.codehaus.sonar</groupId> + <artifactId>sonar-plugin-api</artifactId> + <version>4.5.4</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.sonarsource.sonarqube.tests</groupId> + <artifactId>test-base-plugin</artifactId> + <version>0.1-SNAPSHOT</version> + <type>sonar-plugin</type> + <scope>provided</scope> + </dependency> + </dependencies> + <build> + <sourceDirectory>src</sourceDirectory> + <plugins> + <plugin> + <groupId>org.sonarsource.sonar-packaging-maven-plugin</groupId> + <artifactId>sonar-packaging-maven-plugin</artifactId> + <version>1.15</version> + <extensions>true</extensions> + <configuration> + <pluginKey>testrequire</pluginKey> + <pluginClass>RequirePlugin</pluginClass> + <requirePlugins>testbase:0.1</requirePlugins> + </configuration> + </plugin> + </plugins> + </build> + +</project> diff --git a/server/sonar-webserver-api/src/test/projects/test-require-plugin/src/RequirePlugin.java b/server/sonar-webserver-api/src/test/projects/test-require-plugin/src/RequirePlugin.java new file mode 100644 index 00000000000..847ae2d994e --- /dev/null +++ b/server/sonar-webserver-api/src/test/projects/test-require-plugin/src/RequirePlugin.java @@ -0,0 +1,35 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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. + */ +import org.sonar.api.Plugin; + +import java.util.Collections; +import java.util.List; + +public class RequirePlugin extends Plugin { + + public RequirePlugin() { + // call a class that is in the api published by the base plugin + new org.sonar.plugins.testbase.api.BaseApi().doNothing(); + } + + public void define(Plugin.Context context) { + + } +} diff --git a/server/sonar-webserver-api/src/test/projects/test-require-plugin/target/test-require-plugin-0.1-SNAPSHOT.jar b/server/sonar-webserver-api/src/test/projects/test-require-plugin/target/test-require-plugin-0.1-SNAPSHOT.jar Binary files differnew file mode 100644 index 00000000000..f5fc95f9d0d --- /dev/null +++ b/server/sonar-webserver-api/src/test/projects/test-require-plugin/target/test-require-plugin-0.1-SNAPSHOT.jar diff --git a/server/sonar-webserver-api/src/test/projects/test-requirenew-plugin/pom.xml b/server/sonar-webserver-api/src/test/projects/test-requirenew-plugin/pom.xml new file mode 100644 index 00000000000..044cd94e8f0 --- /dev/null +++ b/server/sonar-webserver-api/src/test/projects/test-requirenew-plugin/pom.xml @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <groupId>org.sonarsource.sonarqube.tests</groupId> + <artifactId>test-requirenew-plugin</artifactId> + <version>0.1-SNAPSHOT</version> + <packaging>sonar-plugin</packaging> + <name>Test Require New Plugin</name> + <description>This fake plugin requires a version of test-base-plugin that is not installed</description> + + <dependencies> + <dependency> + <groupId>org.codehaus.sonar</groupId> + <artifactId>sonar-plugin-api</artifactId> + <version>4.5.4</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.sonarsource.sonarqube.tests</groupId> + <artifactId>test-base-plugin</artifactId> + <version>0.1-SNAPSHOT</version> + <type>sonar-plugin</type> + <scope>provided</scope> + </dependency> + </dependencies> + <build> + <sourceDirectory>src</sourceDirectory> + <plugins> + <plugin> + <groupId>org.sonarsource.sonar-packaging-maven-plugin</groupId> + <artifactId>sonar-packaging-maven-plugin</artifactId> + <version>1.15</version> + <extensions>true</extensions> + <configuration> + <pluginKey>testrequire</pluginKey> + <pluginClass>RequirePlugin</pluginClass> + <requirePlugins>testbase:0.2</requirePlugins> + </configuration> + </plugin> + </plugins> + </build> + +</project> diff --git a/server/sonar-webserver-api/src/test/projects/test-requirenew-plugin/src/RequirePlugin.java b/server/sonar-webserver-api/src/test/projects/test-requirenew-plugin/src/RequirePlugin.java new file mode 100644 index 00000000000..0d14cde33c1 --- /dev/null +++ b/server/sonar-webserver-api/src/test/projects/test-requirenew-plugin/src/RequirePlugin.java @@ -0,0 +1,36 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info 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. + */ +import org.sonar.api.Plugin; + +import java.util.Collections; +import java.util.List; + +public class RequirePlugin extends Plugin { + + public RequirePlugin() { + // call a class that is in the api published by the base plugin + new org.sonar.plugins.testbase.api.BaseApi().doNothing(); + } + + public void define(Plugin.Context context) { + + } + +} diff --git a/server/sonar-webserver-api/src/test/projects/test-requirenew-plugin/target/test-requirenew-plugin-0.1-SNAPSHOT.jar b/server/sonar-webserver-api/src/test/projects/test-requirenew-plugin/target/test-requirenew-plugin-0.1-SNAPSHOT.jar Binary files differnew file mode 100644 index 00000000000..0dd577fc360 --- /dev/null +++ b/server/sonar-webserver-api/src/test/projects/test-requirenew-plugin/target/test-requirenew-plugin-0.1-SNAPSHOT.jar diff --git a/server/sonar-webserver-api/src/test/resources/logback-test.xml b/server/sonar-webserver-api/src/test/resources/logback-test.xml new file mode 100644 index 00000000000..3e34b0f9fc8 --- /dev/null +++ b/server/sonar-webserver-api/src/test/resources/logback-test.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<configuration debug="false"> + <contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator"/> + + <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> + <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> + <pattern> + %d{yyyy.MM.dd HH:mm:ss} %-5level %msg%n + </pattern> + </encoder> + </appender> + + <root> + <level value="INFO"/> + <appender-ref ref="CONSOLE"/> + </root> + + <logger name="ch.qos.logback"> + <level value="WARN"/> + </logger> + + <logger name="okhttp3.mockwebserver"> + <level value="WARN"/> + </logger> + +</configuration> |