diff options
author | Simon Brandhof <simon.brandhof@sonarsource.com> | 2017-03-10 15:01:01 +0100 |
---|---|---|
committer | Simon Brandhof <simon.brandhof@sonarsource.com> | 2017-03-13 13:54:03 +0100 |
commit | 6fa3d925c688fa8e67480c7a69ded9f86aba5326 (patch) | |
tree | 11f4baac39111ae6822f945faf344e88b99dec7c /sonar-application/src/main | |
parent | 857d12fa9909a5b5fde7a42f231b0b8d42e50303 (diff) | |
download | sonarqube-6fa3d925c688fa8e67480c7a69ded9f86aba5326.tar.gz sonarqube-6fa3d925c688fa8e67480c7a69ded9f86aba5326.zip |
SONAR-8816 automatic election of web leader in cluster mode
Diffstat (limited to 'sonar-application/src/main')
11 files changed, 57 insertions, 1370 deletions
diff --git a/sonar-application/src/main/java/org/sonar/application/App.java b/sonar-application/src/main/java/org/sonar/application/App.java index c1a7e61d566..efc4a89d234 100644 --- a/sonar-application/src/main/java/org/sonar/application/App.java +++ b/sonar-application/src/main/java/org/sonar/application/App.java @@ -19,172 +19,76 @@ */ package org.sonar.application; -import com.google.common.annotations.VisibleForTesting; -import java.io.File; -import java.util.ArrayList; -import java.util.List; -import java.util.Properties; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Supplier; -import org.apache.commons.io.FilenameUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.sonar.process.Lifecycle; -import org.sonar.process.ProcessProperties; -import org.sonar.process.Props; -import org.sonar.process.Stoppable; -import org.sonar.process.monitor.JavaCommand; -import org.sonar.process.monitor.Monitor; - -import static org.sonar.process.Lifecycle.State; -import static org.sonar.process.ProcessId.APP; - -/** - * Entry-point of process that starts and monitors ElasticSearch, the Web Server and the Compute Engine. - */ -public class App implements Stoppable { - - private final Properties commandLineArguments; - private final Function<Properties, Props> propsSupplier; - private final JavaCommandFactory javaCommandFactory; - private final Monitor monitor; - private final Supplier<List<JavaCommand>> javaCommandSupplier; - private final Cluster cluster; - - private App(Properties commandLineArguments) { - this.commandLineArguments = commandLineArguments; - this.propsSupplier = properties -> new PropsBuilder(properties, new JdbcSettings()).build(); - this.javaCommandFactory = new JavaCommandFactoryImpl(); - Props props = propsSupplier.apply(commandLineArguments); - - AppFileSystem appFileSystem = new AppFileSystem(props); - appFileSystem.verifyProps(); - ClusterProperties clusterProperties = new ClusterProperties(props); - clusterProperties.populateProps(props); - AppLogging logging = new AppLogging(); - logging.configure(props); - clusterProperties.validate(); - this.cluster = new Cluster(clusterProperties); - - // used by orchestrator - boolean watchForHardStop = props.valueAsBoolean(ProcessProperties.ENABLE_STOP_COMMAND, false); - this.monitor = Monitor.newMonitorBuilder() - .setProcessNumber(APP.getIpcIndex()) - .setFileSystem(appFileSystem) - .setWatchForHardStop(watchForHardStop) - .setWaitForOperational() - .addListener(new AppLifecycleListener()) - .build(); - this.javaCommandSupplier = new ReloadableCommandSupplier(props, appFileSystem::ensureUnchangedConfiguration); - } - - @VisibleForTesting - App(Properties commandLineArguments, Function<Properties, Props> propsSupplier, Monitor monitor, CheckFSConfigOnReload checkFsConfigOnReload, - JavaCommandFactory javaCommandFactory, Cluster cluster) { - this.commandLineArguments = commandLineArguments; - this.propsSupplier = propsSupplier; - this.javaCommandFactory = javaCommandFactory; - this.monitor = monitor; - this.javaCommandSupplier = new ReloadableCommandSupplier(propsSupplier.apply(commandLineArguments), checkFsConfigOnReload); - this.cluster = cluster; - } - - public void start() throws InterruptedException { - monitor.start(javaCommandSupplier); - monitor.awaitTermination(); - } - - private static boolean isProcessEnabled(Props props, String disabledPropertyKey) { - return !props.valueAsBoolean(ProcessProperties.CLUSTER_ENABLED) || - !props.valueAsBoolean(disabledPropertyKey); - } - - static String starPath(File homeDir, String relativePath) { - File dir = new File(homeDir, relativePath); - return FilenameUtils.concat(dir.getAbsolutePath(), "*"); - } - - public static void main(String[] args) throws InterruptedException { - CommandLineParser cli = new CommandLineParser(); - Properties rawProperties = cli.parseArguments(args); - - App app = new App(rawProperties); - app.start(); - } - - @Override - public void stopAsync() { - if (cluster != null) { - cluster.close(); - } - if (monitor != null) { - monitor.stop(); - } - } - - private static class AppLifecycleListener implements Lifecycle.LifecycleListener { - private static final Logger LOGGER = LoggerFactory.getLogger(App.class); - - @Override - public void successfulTransition(State from, State to) { - if (to == State.OPERATIONAL) { - LOGGER.info("SonarQube is up"); +import java.io.IOException; +import org.sonar.application.config.AppSettings; +import org.sonar.application.config.AppSettingsLoader; +import org.sonar.application.config.AppSettingsLoaderImpl; +import org.sonar.application.process.JavaCommandFactory; +import org.sonar.application.process.JavaCommandFactoryImpl; +import org.sonar.application.process.JavaProcessLauncher; +import org.sonar.application.process.JavaProcessLauncherImpl; +import org.sonar.application.process.StopRequestWatcher; +import org.sonar.application.process.StopRequestWatcherImpl; +import org.sonar.process.SystemExit; + +public class App { + + private final SystemExit systemExit = new SystemExit(); + private StopRequestWatcher stopRequestWatcher; + + public void start(String[] cliArguments) throws IOException { + AppSettingsLoader settingsLoader = new AppSettingsLoaderImpl(cliArguments); + AppSettings settings = settingsLoader.load(); + // order is important - logging must be configured before any other components (AppFileSystem, ...) + AppLogging logging = new AppLogging(settings); + logging.configure(); + AppFileSystem fileSystem = new AppFileSystem(settings); + + try (AppState appState = new AppStateFactory(settings).create()) { + AppReloader appReloader = new AppReloaderImpl(settingsLoader, fileSystem, appState, logging); + JavaCommandFactory javaCommandFactory = new JavaCommandFactoryImpl(settings); + fileSystem.reset(); + + try (JavaProcessLauncher javaProcessLauncher = new JavaProcessLauncherImpl(fileSystem.getTempDir())) { + Scheduler scheduler = new SchedulerImpl(settings, appReloader, javaCommandFactory, javaProcessLauncher, appState); + + // intercepts CTRL-C + Runtime.getRuntime().addShutdownHook(new ShutdownHook(scheduler)); + + scheduler.schedule(); + + stopRequestWatcher = StopRequestWatcherImpl.create(settings, scheduler, fileSystem); + stopRequestWatcher.startWatching(); + + scheduler.awaitTermination(); + stopRequestWatcher.stopWatching(); } + systemExit.exit(0); } } - @FunctionalInterface - interface CheckFSConfigOnReload extends Consumer<Props> { - + public static void main(String... args) throws IOException { + new App().start(args); } - private class ReloadableCommandSupplier implements Supplier<List<JavaCommand>> { - private final Props initialProps; - private final CheckFSConfigOnReload checkFsConfigOnReload; - private boolean initialPropsConsumed = false; + private class ShutdownHook extends Thread { + private final Scheduler scheduler; - ReloadableCommandSupplier(Props initialProps, CheckFSConfigOnReload checkFsConfigOnReload) { - this.initialProps = initialProps; - this.checkFsConfigOnReload = checkFsConfigOnReload; + public ShutdownHook(Scheduler scheduler) { + super("SonarQube Shutdown Hook"); + this.scheduler = scheduler; } @Override - public List<JavaCommand> get() { - if (!initialPropsConsumed) { - initialPropsConsumed = true; - return createCommands(this.initialProps); - } - return recreateCommands(); - } - - private List<JavaCommand> recreateCommands() { - Props reloadedProps = propsSupplier.apply(commandLineArguments); - AppFileSystem appFileSystem = new AppFileSystem(reloadedProps); - appFileSystem.verifyProps(); - checkFsConfigOnReload.accept(reloadedProps); - AppLogging logging = new AppLogging(); - logging.configure(reloadedProps); - - return createCommands(reloadedProps); - } - - private List<JavaCommand> createCommands(Props props) { - File homeDir = props.nonNullValueAsFile(ProcessProperties.PATH_HOME); - List<JavaCommand> commands = new ArrayList<>(3); - if (isProcessEnabled(props, ProcessProperties.CLUSTER_SEARCH_DISABLED)) { - commands.add(javaCommandFactory.createESCommand(props, homeDir)); - } - - if (isProcessEnabled(props, ProcessProperties.CLUSTER_WEB_DISABLED)) { - commands.add(javaCommandFactory.createWebCommand(props, homeDir)); - } + public void run() { + systemExit.setInShutdownHook(); - if (isProcessEnabled(props, ProcessProperties.CLUSTER_CE_DISABLED)) { - commands.add(javaCommandFactory.createCeCommand(props, homeDir)); + if (stopRequestWatcher != null) { + stopRequestWatcher.stopWatching(); } - return commands; + // blocks until everything is corrected terminated + scheduler.terminate(); } } } diff --git a/sonar-application/src/main/java/org/sonar/application/AppFileSystem.java b/sonar-application/src/main/java/org/sonar/application/AppFileSystem.java deleted file mode 100644 index 9b07583314e..00000000000 --- a/sonar-application/src/main/java/org/sonar/application/AppFileSystem.java +++ /dev/null @@ -1,186 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2017 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.application; - -import java.io.File; -import java.io.IOException; -import java.nio.file.FileVisitOption; -import java.nio.file.FileVisitResult; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.SimpleFileVisitor; -import java.nio.file.attribute.BasicFileAttributes; -import java.util.EnumSet; -import java.util.Objects; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.sonar.process.AllProcessesCommands; -import org.sonar.process.Props; -import org.sonar.process.monitor.FileSystem; - -import static java.lang.String.format; -import static java.nio.file.FileVisitResult.CONTINUE; -import static org.apache.commons.io.FileUtils.forceMkdir; -import static org.sonar.process.FileUtils.deleteDirectory; -import static org.sonar.process.ProcessProperties.PATH_DATA; -import static org.sonar.process.ProcessProperties.PATH_HOME; -import static org.sonar.process.ProcessProperties.PATH_LOGS; -import static org.sonar.process.ProcessProperties.PATH_TEMP; -import static org.sonar.process.ProcessProperties.PATH_WEB; - -public class AppFileSystem implements FileSystem { - private static final Logger LOG = LoggerFactory.getLogger(AppFileSystem.class); - - private static final EnumSet<FileVisitOption> FOLLOW_LINKS = EnumSet.of(FileVisitOption.FOLLOW_LINKS); - private static final String DEFAULT_DATA_DIRECTORY_NAME = "data"; - private static final String DEFAULT_WEB_DIRECTORY_NAME = "web"; - private static final String DEFAULT_LOGS_DIRECTORY_NAME = "logs"; - private static final String DEFAULT_TEMP_DIRECTORY_NAME = "temp"; - - private final Props props; - private final File homeDir; - private boolean initialized = false; - - public AppFileSystem(Props props) { - this.props = props; - this.homeDir = props.nonNullValueAsFile(PATH_HOME); - } - - public void verifyProps() { - ensurePropertyIsAbsolutePath(props, PATH_DATA, DEFAULT_DATA_DIRECTORY_NAME); - ensurePropertyIsAbsolutePath(props, PATH_WEB, DEFAULT_WEB_DIRECTORY_NAME); - ensurePropertyIsAbsolutePath(props, PATH_LOGS, DEFAULT_LOGS_DIRECTORY_NAME); - ensurePropertyIsAbsolutePath(props, PATH_TEMP, DEFAULT_TEMP_DIRECTORY_NAME); - this.initialized = true; - } - - /** - * Must be called after {@link #verifyProps()} - */ - @Override - public void reset() throws IOException { - if (!initialized) { - throw new IllegalStateException("method verifyProps must be called first"); - } - createDirectory(props, PATH_DATA); - createDirectory(props, PATH_WEB); - createDirectory(props, PATH_LOGS); - File tempDir = createOrCleanTempDirectory(props, PATH_TEMP); - try (AllProcessesCommands allProcessesCommands = new AllProcessesCommands(tempDir)) { - allProcessesCommands.clean(); - } - } - - @Override - public File getTempDir() { - return props.nonNullValueAsFile(PATH_TEMP); - } - - private File ensurePropertyIsAbsolutePath(Props props, String propKey, String defaultRelativePath) { - String path = props.value(propKey, defaultRelativePath); - File d = new File(path); - if (!d.isAbsolute()) { - d = new File(homeDir, path); - LOG.trace("Overriding property {} from relative path '{}' to absolute path '{}'", path, d.getAbsolutePath()); - props.set(propKey, d.getAbsolutePath()); - } - return d; - } - - private static boolean createDirectory(Props props, String propKey) throws IOException { - File dir = props.nonNullValueAsFile(propKey); - if (dir.exists()) { - ensureIsNotAFile(propKey, dir); - return false; - } else { - LOG.trace("forceMkdir {}", dir.getAbsolutePath()); - forceMkdir(dir); - ensureIsNotAFile(propKey, dir); - return true; - } - } - - private static void ensureIsNotAFile(String propKey, File dir) { - if (!dir.isDirectory()) { - throw new IllegalStateException(format("Property '%s' is not valid, not a directory: %s", - propKey, dir.getAbsolutePath())); - } - } - - private static File createOrCleanTempDirectory(Props props, String propKey) throws IOException { - File dir = props.nonNullValueAsFile(propKey); - LOG.info("Cleaning or creating temp directory {}", dir.getAbsolutePath()); - if (!createDirectory(props, propKey)) { - Files.walkFileTree(dir.toPath(), FOLLOW_LINKS, CleanTempDirFileVisitor.VISIT_MAX_DEPTH, new CleanTempDirFileVisitor(dir.toPath())); - } - return dir; - } - - public void ensureUnchangedConfiguration(Props newProps) { - verifyUnchanged(newProps, PATH_DATA, DEFAULT_DATA_DIRECTORY_NAME); - verifyUnchanged(newProps, PATH_WEB, DEFAULT_WEB_DIRECTORY_NAME); - verifyUnchanged(newProps, PATH_LOGS, DEFAULT_LOGS_DIRECTORY_NAME); - verifyUnchanged(newProps, PATH_TEMP, DEFAULT_TEMP_DIRECTORY_NAME); - } - - private void verifyUnchanged(Props newProps, String propKey, String defaultRelativePath) { - String initialValue = props.value(propKey, defaultRelativePath); - String newValue = newProps.value(propKey, defaultRelativePath); - if (!Objects.equals(newValue, initialValue)) { - throw new IllegalStateException(format("Change of property '%s' is not supported ('%s'=> '%s')", propKey, initialValue, newValue)); - } - } - - private static class CleanTempDirFileVisitor extends SimpleFileVisitor<Path> { - private static final Path SHAREDMEMORY_FILE = Paths.get("sharedmemory"); - public static final int VISIT_MAX_DEPTH = 1; - - private final Path path; - private final boolean symLink; - - public CleanTempDirFileVisitor(Path path) { - this.path = path; - this.symLink = Files.isSymbolicLink(path); - } - - @Override - public FileVisitResult visitFile(Path filePath, BasicFileAttributes attrs) throws IOException { - File file = filePath.toFile(); - if (file.isDirectory()) { - deleteDirectory(file); - } else if (filePath.getFileName().equals(SHAREDMEMORY_FILE)) { - return CONTINUE; - } else if (!symLink || !filePath.equals(path)) { - Files.delete(filePath); - } - return CONTINUE; - } - - @Override - public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { - if (!dir.equals(path)) { - deleteDirectory(dir.toFile()); - } - return CONTINUE; - } - } - -} diff --git a/sonar-application/src/main/java/org/sonar/application/AppLogging.java b/sonar-application/src/main/java/org/sonar/application/AppLogging.java deleted file mode 100644 index 7ea96ff62bb..00000000000 --- a/sonar-application/src/main/java/org/sonar/application/AppLogging.java +++ /dev/null @@ -1,226 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2017 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.application; - -import ch.qos.logback.classic.Level; -import ch.qos.logback.classic.Logger; -import ch.qos.logback.classic.LoggerContext; -import ch.qos.logback.classic.spi.ILoggingEvent; -import ch.qos.logback.core.ConsoleAppender; -import ch.qos.logback.core.FileAppender; -import org.sonar.process.ProcessId; -import org.sonar.process.Props; -import org.sonar.process.logging.LogLevelConfig; -import org.sonar.process.logging.LogbackHelper; -import org.sonar.process.logging.RootLoggerConfig; - -import static org.slf4j.Logger.ROOT_LOGGER_NAME; -import static org.sonar.process.logging.RootLoggerConfig.newRootLoggerConfigBuilder; -import static org.sonar.process.monitor.StreamGobbler.LOGGER_GOBBLER; - -/** - * Configure logback for the APP process. - * - * <p> - * SonarQube's logging use cases: - * <ol> - * <li> - * SQ started as a background process (with {@code sonar.sh start}): - * <ul> - * <li> - * logs produced by the JVM before logback is setup in the APP JVM or which can't be caught by logback - * (such as JVM crash) must be written to sonar.log - * </li> - * <li> - * logs produced by the sub process JVMs before logback is setup in the subprocess JVMs or which can't be caught - * by logback (such as JVM crash) must be written to sonar.log - * </li> - * <li>each JVM writes its own logs into its dedicated file</li> - * </ul> - * </li> - * <li> - * SQ started in console with wrapper (ie. with {@code sonar.sh console}): - * <ul> - * <li> - * logs produced by the APP JVM before logback is setup in the APP JVM or which can't be caught by logback - * (such as JVM crash) must be written to sonar.log - * </li> - * <li> - * logs produced by the sub process JVMs before logback is setup in the subprocess JVMs or which can't be caught - * by logback (such as JVM crash) must be written to sonar.log - * </li> - * <li>each JVM writes its own logs into its dedicated file</li> - * <li>APP JVM logs are written to the APP JVM {@code System.out}</li> - * </ul> - * </li> - * <li> - * SQ started from command line (ie. {@code java -jar sonar-application-X.Y.jar}): - * <ul> - * <li> - * logs produced by the APP JVM before logback is setup in the APP JVM or which can't be caught by logback - * (such as JVM crash) are the responsibility of the user to be dealt with - * </li> - * <li> - * logs produced by the sub process JVMs before logback is setup in the subprocess JVMs or which can't be caught - * by logback (such as JVM crash) must be written to APP's {@code System.out} - * </li> - * <li>each JVM writes its own logs into its dedicated file</li> - * <li>APP JVM logs are written to the APP JVM {@code System.out}</li> - * </ul> - * </li> - * <li> - * SQ started from an IT (ie. from command line with {@code option -Dsonar.log.console=true}): - * <ul> - * <li> - * logs produced by the APP JVM before logback is setup in the APP JVM or which can't be caught by logback - * (such as JVM crash) are the responsibility of the developer or maven to be dealt with - * </li> - * <li> - * logs produced by the sub process JVMs before logback is setup in the subprocess JVMs or which can't be caught - * by logback (such as JVM crash) must be written to APP's {@code System.out} and are the responsibility of the - * developer or maven to be dealt with - * </li> - * <li>each JVM writes its own logs into its dedicated file</li> - * <li>logs of all 4 JVMs are also written to the APP JVM {@code System.out}</li> - * </ul> - * </li> - * </ol> - * </p> - * - */ -class AppLogging { - - private static final String CONSOLE_LOGGER = "console"; - private static final String CONSOLE_PLAIN_APPENDER = "CONSOLE"; - private static final String APP_CONSOLE_APPENDER = "APP_CONSOLE"; - private static final String GOBBLER_PLAIN_CONSOLE = "GOBBLER_CONSOLE"; - private static final RootLoggerConfig APP_ROOT_LOGGER_CONFIG = newRootLoggerConfigBuilder() - .setProcessId(ProcessId.APP) - .build(); - - private final LogbackHelper helper = new LogbackHelper(); - - LoggerContext configure(Props props) { - LoggerContext ctx = helper.getRootContext(); - ctx.reset(); - - helper.enableJulChangePropagation(ctx); - - configureConsole(ctx); - if (helper.isAllLogsToConsoleEnabled(props) || !props.valueAsBoolean("sonar.wrapped", false)) { - configureWithLogbackWritingToFile(props, ctx); - } else { - configureWithWrapperWritingToFile(ctx); - } - helper.apply( - LogLevelConfig.newBuilder() - .rootLevelFor(ProcessId.APP) - .immutableLevel("com.hazelcast", Level.toLevel(props.value(ClusterParameters.HAZELCAST_LOG_LEVEL.getName()))) - .build(), props); - - return ctx; - } - - /** - * Creates a non additive logger dedicated to printing message as is (ie. assuming they are already formatted). - * - * It creates a dedicated appender to the System.out which applies no formatting the logs it receives. - */ - private void configureConsole(LoggerContext loggerContext) { - ConsoleAppender<ILoggingEvent> consoleAppender = helper.newConsoleAppender(loggerContext, CONSOLE_PLAIN_APPENDER, "%msg%n"); - - Logger consoleLogger = loggerContext.getLogger(CONSOLE_LOGGER); - consoleLogger.setAdditive(false); - consoleLogger.addAppender(consoleAppender); - } - - /** - * The process has been started by orchestrator (ie. via {@code java -jar} and optionally passing the option {@code -Dsonar.log.console=true}). - * Therefor, APP's System.out (and System.err) are <strong>not</strong> copied to sonar.log by the wrapper and - * printing to sonar.log must be done at logback level. - */ - private void configureWithLogbackWritingToFile(Props props, LoggerContext ctx) { - // configure all logs (ie. root logger) to be written to sonar.log and also to the console with formatting - // in practice, this will be only APP's own logs as logs from sub processes LOGGER_GOBBLER and LOGGER_GOBBLER - // is configured below to be detached from root - // so, this will make all APP's log to be both written to sonar.log and visible in the console - configureRootWithLogbackWritingToFile(props, ctx); - - // if option -Dsonar.log.console=true has been set, sub processes will write their logs to their own files but also - // copy them to their System.out. - // otherwise, the only logs to be expected in LOGGER_GOBBLER are those before logback is setup in subprocesses or - // when their JVM crashes - // they must be printed to App's System.out as is (as they are already formatted) - // logger is configured to be non additive as we don't want these logs to be written to sonar.log and duplicated in - // the console (with an incorrect formatting) - configureGobbler(ctx); - } - - /** - * SQ has been started by the wrapper (ie. with sonar.sh) therefor, APP's System.out (and System.err) are written to - * sonar.log by the wrapper. - */ - private void configureWithWrapperWritingToFile(LoggerContext ctx) { - // configure all logs (ie. root logger) to be written to console with formatting - // in practice, this will be only APP's own logs as logs from sub processes are written to LOGGER_GOBBLER and - // LOGGER_GOBBLER is configured below to be detached from root - // logs are written to the console because we want them to be in sonar.log and the wrapper will write any log - // from APP's System.out and System.err to sonar.log - Logger rootLogger = ctx.getLogger(ROOT_LOGGER_NAME); - rootLogger.addAppender(createAppConsoleAppender(ctx, helper.buildLogPattern(APP_ROOT_LOGGER_CONFIG))); - - // in regular configuration, sub processes are not copying their logs to their System.out, so, the only logs to be - // expected in LOGGER_GOBBLER are those before logback is setup in subprocesses or when JVM crashes - // so, they must be printed to App's System.out as is (as they are already formatted) and the wrapper will write - // them to sonar.log - // logger is configured to be non additive as we don't want these logs written to sonar.log and duplicated in the - // console with an incorrect formatting - configureGobbler(ctx); - } - - private void configureRootWithLogbackWritingToFile(Props props, LoggerContext ctx) { - Logger rootLogger = ctx.getLogger(ROOT_LOGGER_NAME); - String appLogPattern = helper.buildLogPattern(APP_ROOT_LOGGER_CONFIG); - FileAppender<ILoggingEvent> fileAppender = helper.newFileAppender(ctx, props, APP_ROOT_LOGGER_CONFIG, appLogPattern); - rootLogger.addAppender(fileAppender); - rootLogger.addAppender(createAppConsoleAppender(ctx, appLogPattern)); - } - - /** - * Configure the logger to which logs from sub processes are written to - * (called {@link org.sonar.process.monitor.StreamGobbler#LOGGER_GOBBLER}) by {@link org.sonar.process.monitor.StreamGobbler}, - * to be: - * <ol> - * <li>non additive (ie. these logs will be output by the appender of {@link org.sonar.process.monitor.StreamGobbler#LOGGER_GOBBLER} and only this one)</li> - * <li>write logs as is (ie. without any extra formatting)</li> - * <li>write exclusively to App's System.out</li> - * </ol> - */ - private void configureGobbler(LoggerContext ctx) { - Logger gobblerLogger = ctx.getLogger(LOGGER_GOBBLER); - gobblerLogger.setAdditive(false); - gobblerLogger.addAppender(helper.newConsoleAppender(ctx, GOBBLER_PLAIN_CONSOLE, "%msg%n")); - } - - private ConsoleAppender<ILoggingEvent> createAppConsoleAppender(LoggerContext ctx, String appLogPattern) { - return helper.newConsoleAppender(ctx, APP_CONSOLE_APPENDER, appLogPattern); - } - -} diff --git a/sonar-application/src/main/java/org/sonar/application/Cluster.java b/sonar-application/src/main/java/org/sonar/application/Cluster.java deleted file mode 100644 index 621af696320..00000000000 --- a/sonar-application/src/main/java/org/sonar/application/Cluster.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2017 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.application; - -import com.google.common.annotations.VisibleForTesting; -import com.hazelcast.cluster.ClusterState; -import com.hazelcast.config.Config; -import com.hazelcast.config.JoinConfig; -import com.hazelcast.config.NetworkConfig; -import com.hazelcast.core.Hazelcast; -import com.hazelcast.core.HazelcastInstance; -import javax.annotation.Nonnull; - -/** - * Manager for the cluster communication between Main Processes - */ -public class Cluster implements AutoCloseable { - /** - * The Hazelcast instance. - */ - @VisibleForTesting - final HazelcastInstance hazelcastInstance; - - /** - * Instantiates a new Cluster. - * - * @param clusterProperties The properties of the cluster read from configuration - */ - protected Cluster(@Nonnull ClusterProperties clusterProperties) { - if (clusterProperties.isEnabled()) { - Config hzConfig = new Config(); - // Configure the network instance - NetworkConfig netConfig = hzConfig.getNetworkConfig(); - netConfig.setPort(clusterProperties.getPort()) - .setPortAutoIncrement(clusterProperties.isPortAutoincrement()); - - if (!clusterProperties.getInterfaces().isEmpty()) { - netConfig.getInterfaces() - .setEnabled(true) - .setInterfaces(clusterProperties.getInterfaces()); - } - - // Only allowing TCP/IP configuration - JoinConfig joinConfig = netConfig.getJoin(); - joinConfig.getAwsConfig().setEnabled(false); - joinConfig.getMulticastConfig().setEnabled(false); - joinConfig.getTcpIpConfig().setEnabled(true); - joinConfig.getTcpIpConfig().setMembers(clusterProperties.getMembers()); - - // Tweak HazelCast configuration - hzConfig - // Increase the number of tries - .setProperty("hazelcast.tcp.join.port.try.count", "10") - // Don't bind on all interfaces - .setProperty("hazelcast.socket.bind.any", "false") - // Don't phone home - .setProperty("hazelcast.phone.home.enabled", "false") - // Use slf4j for logging - .setProperty("hazelcast.logging.type", "slf4j"); - - // We are not using the partition group of Hazelcast, so disabling it - hzConfig.getPartitionGroupConfig().setEnabled(false); - - hazelcastInstance = Hazelcast.newHazelcastInstance(hzConfig); - } else { - hazelcastInstance = null; - } - } - - /** - * Is the cluster active - * - * @return the boolean - */ - public boolean isActive() { - return hazelcastInstance != null && hazelcastInstance.getCluster().getClusterState() == ClusterState.ACTIVE; - } - - @Override - public void close() { - if (hazelcastInstance != null) { - hazelcastInstance.shutdown(); - } - } -} diff --git a/sonar-application/src/main/java/org/sonar/application/ClusterParameters.java b/sonar-application/src/main/java/org/sonar/application/ClusterParameters.java deleted file mode 100644 index c0e15e0ca1b..00000000000 --- a/sonar-application/src/main/java/org/sonar/application/ClusterParameters.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2017 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.application; - -import javax.annotation.Nonnull; -import org.apache.commons.lang.StringUtils; - -enum ClusterParameters { - ENABLED("sonar.cluster.enabled", Boolean.FALSE.toString()), - MEMBERS("sonar.cluster.members", ""), - PORT("sonar.cluster.port", Integer.toString(9003)), - PORT_AUTOINCREMENT("sonar.cluster.port_autoincrement", Boolean.FALSE.toString()), - INTERFACES("sonar.cluster.interfaces", ""), - NAME("sonar.cluster.name", ""), - HAZELCAST_LOG_LEVEL("sonar.log.level.app.hazelcast", "WARN"); - - private final String name; - private final String defaultValue; - - ClusterParameters(@Nonnull String name, @Nonnull String defaultValue) { - this.name = name; - this.defaultValue = defaultValue; - } - - String getName() { - return name; - } - - String getDefaultValue() { - return defaultValue; - } - - boolean getDefaultValueAsBoolean() { - return "true".equalsIgnoreCase(defaultValue); - } - - Integer getDefaultValueAsInt() { - if (StringUtils.isNotEmpty(defaultValue)) { - try { - return Integer.parseInt(defaultValue); - } catch (NumberFormatException e) { - throw new IllegalStateException("Default value of property " + name + " is not an integer: " + defaultValue, e); - } - } - return null; - } -} diff --git a/sonar-application/src/main/java/org/sonar/application/ClusterProperties.java b/sonar-application/src/main/java/org/sonar/application/ClusterProperties.java deleted file mode 100644 index 1b86838ac5c..00000000000 --- a/sonar-application/src/main/java/org/sonar/application/ClusterProperties.java +++ /dev/null @@ -1,185 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2017 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.application; - -import java.net.InetAddress; -import java.net.NetworkInterface; -import java.net.SocketException; -import java.util.ArrayList; -import java.util.Enumeration; -import java.util.List; -import java.util.stream.Collectors; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import org.apache.commons.lang.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.sonar.process.Props; - -/** - * Properties of the cluster configuration - */ -final class ClusterProperties { - private static final Logger LOGGER = LoggerFactory.getLogger(ClusterProperties.class); - - private final int port; - private final boolean enabled; - private final boolean portAutoincrement; - private final List<String> members; - private final List<String> interfaces; - private final String name; - private final String logLevel; - - ClusterProperties(@Nonnull Props props) { - port = props.valueAsInt(ClusterParameters.PORT.getName(), ClusterParameters.PORT.getDefaultValueAsInt()); - enabled = props.valueAsBoolean(ClusterParameters.ENABLED.getName(), ClusterParameters.ENABLED.getDefaultValueAsBoolean()); - portAutoincrement = props.valueAsBoolean(ClusterParameters.PORT_AUTOINCREMENT.getName(), ClusterParameters.PORT_AUTOINCREMENT.getDefaultValueAsBoolean()); - interfaces = extractInterfaces( - props.value(ClusterParameters.INTERFACES.getName(), ClusterParameters.INTERFACES.getDefaultValue()) - ); - name = props.value(ClusterParameters.NAME.getName(), ClusterParameters.NAME.getDefaultValue()); - logLevel = props.value(ClusterParameters.HAZELCAST_LOG_LEVEL.getName(), ClusterParameters.HAZELCAST_LOG_LEVEL.getDefaultValue()); - members = extractMembers( - props.value(ClusterParameters.MEMBERS.getName(), ClusterParameters.MEMBERS.getDefaultValue()) - ); - } - - void populateProps(@Nonnull Props props) { - props.set(ClusterParameters.PORT.getName(), Integer.toString(port)); - props.set(ClusterParameters.ENABLED.getName(), Boolean.toString(enabled)); - props.set(ClusterParameters.PORT_AUTOINCREMENT.getName(), Boolean.toString(portAutoincrement)); - props.set(ClusterParameters.INTERFACES.getName(), interfaces.stream().collect(Collectors.joining(","))); - props.set(ClusterParameters.NAME.getName(), name); - props.set(ClusterParameters.HAZELCAST_LOG_LEVEL.getName(), logLevel); - props.set(ClusterParameters.MEMBERS.getName(), members.stream().collect(Collectors.joining(","))); - } - - int getPort() { - return port; - } - - boolean isEnabled() { - return enabled; - } - - boolean isPortAutoincrement() { - return portAutoincrement; - } - - List<String> getMembers() { - return members; - } - - List<String> getInterfaces() { - return interfaces; - } - - String getName() { - return name; - } - - String getLogLevel() { - return logLevel; - } - - void validate() { - if (!enabled) { - return; - } - // Name is required in cluster mode - checkArgument( - StringUtils.isNotEmpty(name), - "Cluster have been enabled but a %s has not been defined.", - ClusterParameters.NAME.getName() - ); - - // Test validity of port - checkArgument( - port > 0 && port < 65_536, - "Cluster port have been set to %d which is outside the range [1-65535].", - port - ); - - // Test the interfaces parameter - try { - List<String> localInterfaces = findAllLocalIPs(); - - interfaces.forEach( - inet -> checkArgument( - StringUtils.isEmpty(inet) || localInterfaces.contains(inet), - "Interface %s is not available on this machine.", - inet - ) - ); - } catch (SocketException e) { - LOGGER.warn("Unable to retrieve network interfaces. Interfaces won't be checked", e); - } - } - - private static List<String> extractMembers(final String members) { - List<String> result = new ArrayList<>(); - for (String member : members.split(",")) { - if (StringUtils.isNotEmpty(member)) { - if (!member.contains(":")) { - result.add( - String.format("%s:%s", member, ClusterParameters.PORT.getDefaultValue()) - ); - } else { - result.add(member); - } - } - } - return result; - } - - private static List<String> extractInterfaces(final String interfaces) { - List<String> result = new ArrayList<>(); - for (String iface : interfaces.split(",")) { - if (StringUtils.isNotEmpty(iface)) { - result.add(iface); - } - } - return result; - } - - private static List<String> findAllLocalIPs() throws SocketException { - Enumeration<NetworkInterface> netInterfaces = NetworkInterface.getNetworkInterfaces(); - List<String> localInterfaces = new ArrayList<>(); - - while (netInterfaces.hasMoreElements()) { - NetworkInterface networkInterface = netInterfaces.nextElement(); - Enumeration<InetAddress> ips = networkInterface.getInetAddresses(); - while (ips.hasMoreElements()) { - InetAddress ip = ips.nextElement(); - localInterfaces.add(ip.getHostAddress()); - } - } - return localInterfaces; - } - - private static void checkArgument(boolean expression, - @Nullable String messageTemplate, - @Nullable Object... args) { - if (!expression) { - throw new IllegalArgumentException(String.format(messageTemplate, args)); - } - } -} diff --git a/sonar-application/src/main/java/org/sonar/application/CommandLineParser.java b/sonar-application/src/main/java/org/sonar/application/CommandLineParser.java deleted file mode 100644 index 35642d22c71..00000000000 --- a/sonar-application/src/main/java/org/sonar/application/CommandLineParser.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2017 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.application; - -import org.apache.commons.lang.StringUtils; - -import java.util.Map; -import java.util.Properties; - -class CommandLineParser { - /** - * Build properties from command-line arguments and system properties - */ - Properties parseArguments(String[] args) { - Properties props = argumentsToProperties(args); - - // complete with only the system properties that start with "sonar." - for (Map.Entry<Object, Object> entry : System.getProperties().entrySet()) { - String key = entry.getKey().toString(); - if (key.startsWith("sonar.")) { - props.setProperty(key, entry.getValue().toString()); - } - } - return props; - } - - /** - * Convert strings "-Dkey=value" to properties - */ - Properties argumentsToProperties(String[] args) { - Properties props = new Properties(); - for (String arg : args) { - if (!arg.startsWith("-D") || !arg.contains("=")) { - throw new IllegalArgumentException(String.format( - "Command-line argument must start with -D, for example -Dsonar.jdbc.username=sonar. Got: %s", arg)); - } - String key = StringUtils.substringBefore(arg, "=").substring(2); - String value = StringUtils.substringAfter(arg, "="); - props.setProperty(key, value); - } - return props; - } -} diff --git a/sonar-application/src/main/java/org/sonar/application/JavaCommandFactory.java b/sonar-application/src/main/java/org/sonar/application/JavaCommandFactory.java deleted file mode 100644 index 0071a3619d8..00000000000 --- a/sonar-application/src/main/java/org/sonar/application/JavaCommandFactory.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2017 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.application; - -import java.io.File; -import org.sonar.process.Props; -import org.sonar.process.monitor.JavaCommand; - -public interface JavaCommandFactory { - JavaCommand createESCommand(Props props, File homeDir); - - JavaCommand createWebCommand(Props props, File homeDir); - - JavaCommand createCeCommand(Props props, File homeDir); -} diff --git a/sonar-application/src/main/java/org/sonar/application/JavaCommandFactoryImpl.java b/sonar-application/src/main/java/org/sonar/application/JavaCommandFactoryImpl.java deleted file mode 100644 index 8058beb1018..00000000000 --- a/sonar-application/src/main/java/org/sonar/application/JavaCommandFactoryImpl.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2017 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.application; - -import java.io.File; -import org.sonar.process.ProcessId; -import org.sonar.process.ProcessProperties; -import org.sonar.process.Props; -import org.sonar.process.monitor.JavaCommand; - -import static org.sonar.process.ProcessProperties.HTTPS_PROXY_HOST; -import static org.sonar.process.ProcessProperties.HTTPS_PROXY_PORT; -import static org.sonar.process.ProcessProperties.HTTP_PROXY_HOST; -import static org.sonar.process.ProcessProperties.HTTP_PROXY_PORT; - -public class JavaCommandFactoryImpl implements JavaCommandFactory { - /** - * Properties about proxy that must be set as system properties - */ - private static final String[] PROXY_PROPERTY_KEYS = new String[] { - HTTP_PROXY_HOST, - HTTP_PROXY_PORT, - "http.nonProxyHosts", - HTTPS_PROXY_HOST, - HTTPS_PROXY_PORT, - "http.auth.ntlm.domain", - "socksProxyHost", - "socksProxyPort"}; - - @Override - public JavaCommand createESCommand(Props props, File workDir) { - return newJavaCommand(ProcessId.ELASTICSEARCH, props, workDir) - .addJavaOptions("-Djava.awt.headless=true") - .addJavaOptions(props.nonNullValue(ProcessProperties.SEARCH_JAVA_OPTS)) - .addJavaOptions(props.nonNullValue(ProcessProperties.SEARCH_JAVA_ADDITIONAL_OPTS)) - .setClassName("org.sonar.search.SearchServer") - .addClasspath("./lib/common/*") - .addClasspath("./lib/search/*"); - } - - @Override - public JavaCommand createWebCommand(Props props, File workDir) { - JavaCommand command = newJavaCommand(ProcessId.WEB_SERVER, props, workDir) - .addJavaOptions(ProcessProperties.WEB_ENFORCED_JVM_ARGS) - .addJavaOptions(props.nonNullValue(ProcessProperties.WEB_JAVA_OPTS)) - .addJavaOptions(props.nonNullValue(ProcessProperties.WEB_JAVA_ADDITIONAL_OPTS)) - // required for logback tomcat valve - .setEnvVariable(ProcessProperties.PATH_LOGS, props.nonNullValue(ProcessProperties.PATH_LOGS)) - .setClassName("org.sonar.server.app.WebServer") - .addClasspath("./lib/common/*") - .addClasspath("./lib/server/*"); - String driverPath = props.value(ProcessProperties.JDBC_DRIVER_PATH); - if (driverPath != null) { - command.addClasspath(driverPath); - } - return command; - } - - @Override - public JavaCommand createCeCommand(Props props, File workDir) { - JavaCommand command = newJavaCommand(ProcessId.COMPUTE_ENGINE, props, workDir) - .addJavaOptions(ProcessProperties.CE_ENFORCED_JVM_ARGS) - .addJavaOptions(props.nonNullValue(ProcessProperties.CE_JAVA_OPTS)) - .addJavaOptions(props.nonNullValue(ProcessProperties.CE_JAVA_ADDITIONAL_OPTS)) - .setClassName("org.sonar.ce.app.CeServer") - .addClasspath("./lib/common/*") - .addClasspath("./lib/server/*") - .addClasspath("./lib/ce/*"); - String driverPath = props.value(ProcessProperties.JDBC_DRIVER_PATH); - if (driverPath != null) { - command.addClasspath(driverPath); - } - return command; - } - - private static JavaCommand newJavaCommand(ProcessId id, Props props, File workDir) { - JavaCommand command = new JavaCommand(id) - .setWorkDir(workDir) - .setArguments(props.rawProperties()); - - for (String key : PROXY_PROPERTY_KEYS) { - if (props.contains(key)) { - command.addJavaOption("-D" + key + "=" + props.value(key)); - } - } - // defaults of HTTPS are the same than HTTP defaults - setSystemPropertyToDefaultIfNotSet(command, props, HTTPS_PROXY_HOST, HTTP_PROXY_HOST); - setSystemPropertyToDefaultIfNotSet(command, props, HTTPS_PROXY_PORT, HTTP_PROXY_PORT); - return command; - } - - private static void setSystemPropertyToDefaultIfNotSet(JavaCommand command, Props props, String httpsProperty, String httpProperty) { - if (!props.contains(httpsProperty) && props.contains(httpProperty)) { - command.addJavaOption("-D" + httpsProperty + "=" + props.value(httpProperty)); - } - } -} diff --git a/sonar-application/src/main/java/org/sonar/application/JdbcSettings.java b/sonar-application/src/main/java/org/sonar/application/JdbcSettings.java deleted file mode 100644 index 82b137a6b02..00000000000 --- a/sonar-application/src/main/java/org/sonar/application/JdbcSettings.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2017 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.application; - -import java.io.File; -import java.util.ArrayList; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import org.apache.commons.io.FileUtils; -import org.apache.commons.lang.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.sonar.process.MessageException; -import org.sonar.process.ProcessProperties; -import org.sonar.process.Props; - -import static org.apache.commons.lang.StringUtils.isEmpty; -import static org.apache.commons.lang.StringUtils.isNotEmpty; -import static org.sonar.api.database.DatabaseProperties.PROP_EMBEDDED_PORT; -import static org.sonar.api.database.DatabaseProperties.PROP_EMBEDDED_PORT_DEFAULT_VALUE; -import static org.sonar.api.database.DatabaseProperties.PROP_URL; -import static org.sonar.process.ProcessProperties.JDBC_URL; - -public class JdbcSettings { - - enum Provider { - H2("lib/jdbc/h2"), SQLSERVER("lib/jdbc/mssql"), MYSQL("lib/jdbc/mysql"), ORACLE("extensions/jdbc-driver/oracle"), - POSTGRESQL("lib/jdbc/postgresql"); - - final String path; - - Provider(String path) { - this.path = path; - } - } - - public void checkAndComplete(File homeDir, Props props) { - Provider provider = resolveProviderAndEnforceNonnullJdbcUrl(props); - String url = props.value(JDBC_URL); - checkUrlParameters(provider, url); - String driverPath = driverPath(homeDir, provider); - props.set(ProcessProperties.JDBC_DRIVER_PATH, driverPath); - } - - String driverPath(File homeDir, Provider provider) { - String dirPath = provider.path; - File dir = new File(homeDir, dirPath); - if (!dir.exists()) { - throw new MessageException("Directory does not exist: " + dirPath); - } - List<File> files = new ArrayList<>(FileUtils.listFiles(dir, new String[] {"jar"}, false)); - if (files.isEmpty()) { - throw new MessageException("Directory does not contain JDBC driver: " + dirPath); - } - if (files.size() > 1) { - throw new MessageException("Directory must contain only one JAR file: " + dirPath); - } - return files.get(0).getAbsolutePath(); - } - - Provider resolveProviderAndEnforceNonnullJdbcUrl(Props props) { - String url = props.value(JDBC_URL); - String embeddedDatabasePort = props.value(PROP_EMBEDDED_PORT); - - if (isNotEmpty(embeddedDatabasePort)) { - String correctUrl = buildH2JdbcUrl(embeddedDatabasePort); - warnIfUrlIsSet(embeddedDatabasePort, url, correctUrl); - props.set(PROP_URL, correctUrl); - return Provider.H2; - } - - if (isEmpty(url)) { - props.set(PROP_URL, buildH2JdbcUrl(PROP_EMBEDDED_PORT_DEFAULT_VALUE)); - props.set(PROP_EMBEDDED_PORT, PROP_EMBEDDED_PORT_DEFAULT_VALUE); - return Provider.H2; - } - - Pattern pattern = Pattern.compile("jdbc:(\\w+):.+"); - Matcher matcher = pattern.matcher(url); - if (!matcher.find()) { - throw new MessageException(String.format("Bad format of JDBC URL: %s", url)); - } - String key = matcher.group(1); - try { - return Provider.valueOf(StringUtils.upperCase(key)); - } catch (IllegalArgumentException e) { - throw new MessageException(String.format("Unsupported JDBC driver provider: %s", key)); - } - } - - private static String buildH2JdbcUrl(String embeddedDatabasePort) { - return "jdbc:h2:tcp://localhost:" + embeddedDatabasePort + "/sonar"; - } - - void checkUrlParameters(Provider provider, String url) { - if (Provider.MYSQL.equals(provider)) { - checkRequiredParameter(url, "useUnicode=true"); - checkRequiredParameter(url, "characterEncoding=utf8"); - checkRecommendedParameter(url, "rewriteBatchedStatements=true"); - checkRecommendedParameter(url, "useConfigs=maxPerformance"); - } - } - - private static void warnIfUrlIsSet(String port, String existing, String expectedUrl) { - if (isNotEmpty(existing)) { - Logger logger = LoggerFactory.getLogger(JdbcSettings.class); - if (expectedUrl.equals(existing)) { - logger.warn("To change H2 database port, only property '{}' should be set (which current value is '{}'). " + - "Remove property '{}' from configuration to remove this warning.", - PROP_EMBEDDED_PORT, port, - PROP_URL); - } else { - logger.warn("Both '{}' and '{}' properties are set. " + - "The value of property '{}' ('{}') is not consistent with the value of property '{}' ('{}'). " + - "The value of property '{}' will be ignored and value '{}' will be used instead. " + - "To remove this warning, either remove property '{}' if your intent was to use the embedded H2 database, otherwise remove property '{}'.", - PROP_EMBEDDED_PORT, PROP_URL, - PROP_URL, existing, PROP_EMBEDDED_PORT, port, - PROP_URL, expectedUrl, - PROP_URL, PROP_EMBEDDED_PORT); - } - } - } - - private static void checkRequiredParameter(String url, String val) { - if (!url.contains(val)) { - throw new MessageException(String.format("JDBC URL must have the property '%s'", val)); - } - } - - private void checkRecommendedParameter(String url, String val) { - if (!url.contains(val)) { - LoggerFactory.getLogger(getClass()).warn("JDBC URL is recommended to have the property '{}'", val); - } - } -} diff --git a/sonar-application/src/main/java/org/sonar/application/PropsBuilder.java b/sonar-application/src/main/java/org/sonar/application/PropsBuilder.java deleted file mode 100644 index 32f371dce83..00000000000 --- a/sonar-application/src/main/java/org/sonar/application/PropsBuilder.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2017 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.application; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.Reader; -import java.net.URISyntaxException; -import java.nio.charset.StandardCharsets; -import java.util.Properties; -import org.sonar.process.ConfigurationUtils; -import org.sonar.process.ProcessProperties; -import org.sonar.process.Props; - -class PropsBuilder { - - private final File homeDir; - private final JdbcSettings jdbcSettings; - private final Properties rawProperties; - - PropsBuilder(Properties rawProperties, JdbcSettings jdbcSettings, File homeDir) { - this.rawProperties = rawProperties; - this.jdbcSettings = jdbcSettings; - this.homeDir = homeDir; - } - - PropsBuilder(Properties rawProperties, JdbcSettings jdbcSettings) { - this(rawProperties, jdbcSettings, detectHomeDir()); - } - - /** - * Load optional conf/sonar.properties, interpolates environment variables - */ - Props build() { - Properties p = loadPropertiesFile(homeDir); - p.putAll(rawProperties); - p.setProperty(ProcessProperties.PATH_HOME, homeDir.getAbsolutePath()); - p = ConfigurationUtils.interpolateVariables(p, System.getenv()); - - // the difference between Properties and Props is that the latter - // supports decryption of values, so it must be used when values - // are accessed - Props props = new Props(p); - ProcessProperties.completeDefaults(props); - - // check JDBC properties and set path to driver - jdbcSettings.checkAndComplete(homeDir, props); - - return props; - } - - static File detectHomeDir() { - try { - File appJar = new File(PropsBuilder.class.getProtectionDomain().getCodeSource().getLocation().toURI()); - return appJar.getParentFile().getParentFile(); - } catch (URISyntaxException e) { - throw new IllegalStateException("Cannot detect path of main jar file", e); - } - } - - private static Properties loadPropertiesFile(File homeDir) { - Properties p = new Properties(); - File propsFile = new File(homeDir, "conf/sonar.properties"); - if (propsFile.exists()) { - try (Reader reader = new InputStreamReader(new FileInputStream(propsFile), StandardCharsets.UTF_8)) { - p.load(reader); - } catch (IOException e) { - throw new IllegalStateException("Cannot open file " + propsFile, e); - } - } - return p; - } -} |