/* * Copyright 2016 ThoughtWorks, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.thoughtworks.go.server.launcher; import com.apple.eawt.Application; import com.apple.eawt.ApplicationEvent; import javax.swing.*; import java.awt.*; import java.io.File; import java.io.IOException; import java.net.HttpURLConnection; import java.net.URL; import java.text.MessageFormat; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.logging.Logger; import static java.util.Arrays.asList; public class GoMacLauncher extends JFrame { private static final Logger LOG = Logger.getLogger(GoMacLauncher.class.getCanonicalName()); protected MacAboutBox aboutBox; private Application application = Application.getApplication(); private static final int MAX_SLEEP_SECONDS = 2400; private static final int SLEEP_MILLIS = 1000; private static final String APPLICATION_SUPPORT_PATHNAME = MessageFormat.format("{0}/Library/Application Support/{1}/", System.getProperty("user.home"), System.getProperty("go.application.name", "Go Server")); private static final String GO_CONFIG_DIRECTORY_PATH = APPLICATION_SUPPORT_PATHNAME + "config/"; private static final String CRUISE_SERVER_URL = "http://localhost:8153/go/"; public static void main(String[] args) { new File(GO_CONFIG_DIRECTORY_PATH).mkdirs(); new GoMacLauncher().spawnProcessAndWait(); } public GoMacLauncher() throws HeadlessException { application.addApplicationListener(new MyApplicationAdapter()); application.setEnabledAboutMenu(true); application.setEnabledPreferencesMenu(false); setVisible(false); setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); setResizable(false); setSize(getPreferredSize()); } private int spawnProcessAndWait() { int exitValue = 0; String sep = System.getProperty("file.separator"); String defaultJava = System.getProperty("java.home") + sep + "bin" + sep + "java"; String java = System.getProperty("go.java.to.use", defaultJava); String startMem = System.getProperty("cruise.server.mem", "512"); String maxMem = System.getProperty("cruise.server.maxmem", "1024"); String maxPerm = System.getProperty("cruise.server.maxpermgen", "256"); String lang = System.getProperty("cruise.server.lang", "en"); String country = System.getProperty("cruise.server.country", "US"); boolean dbDebugMode = System.getProperty("cruise.server.db_debug_mode") != null; final List<String> arguments = new ArrayList<>(); arguments.add(java); arguments.add("-Xms" + startMem + "m"); arguments.add("-Xmx" + maxMem + "m"); arguments.add("-XX:MaxMetaspaceSize=" + maxPerm + "m"); if (dbDebugMode) { arguments.add("-DDB_DEBUG_MODE=true"); } arguments.add("-Dapple.awt.UIElement=true"); arguments.add("-Duser.language=" + lang); arguments.add("-Duser.country=" + country); arguments.add("-Dcruise.config.file=" + GO_CONFIG_DIRECTORY_PATH + "cruise-config.xml"); arguments.add("-Dcruise.config.dir=" + GO_CONFIG_DIRECTORY_PATH); arguments.add("-Dcruise.server.port=" + System.getProperty("cruise.server.port", "8153")); arguments.add("-Dcruise.server.ssl.port=" + System.getProperty("cruise.server.ssl.port", "8154")); addOtherArguments(arguments); arguments.add("-jar"); arguments.add(new File(System.getProperty("go.server.mac.go.jar.dir", "."), "go.jar").getAbsolutePath()); LOG.info("Running server as: " + arguments); String[] args = arguments.toArray(new String[arguments.size()]); try { setUpApplicationSupport(); ProcessBuilder processBuilder = new ProcessBuilder(args); processBuilder.directory(new File(APPLICATION_SUPPORT_PATHNAME)); processBuilder.redirectErrorStream(true); processBuilder.redirectOutput(ProcessBuilder.Redirect.appendTo(new File(APPLICATION_SUPPORT_PATHNAME, "go-server.log"))); final Process server = processBuilder.start(); server.getOutputStream().close(); Thread shutdownHook = new Thread(new Shutdown(server)); Runtime.getRuntime().addShutdownHook(shutdownHook); Thread progressThread = new Thread(new Runnable() { public void run() { try { displayLaunchingProgress(server); } catch (InterruptedException e) { // Don't care } catch (IOException e) { LOG.severe("Exception while executing command: " + arguments + " - " + e.toString()); } } }); progressThread.start(); try { exitValue = server.waitFor(); if (exitValue != 0) { LOG.severe(String.format( "Server was terminated with exit code %d. Please see %sgo-server.log for more details.", exitValue, APPLICATION_SUPPORT_PATHNAME)); System.exit(1); } } catch (InterruptedException ie) { LOG.severe("Server was interrupted. Terminating. " + ie.toString()); server.destroy(); } Runtime.getRuntime().removeShutdownHook(shutdownHook); } catch (IOException e) { LOG.severe("Exception while executing command: " + asList(args) + " - " + e.toString()); } return exitValue; } /* Any property prefixed by GO_, is passed along as a property, after stripping the GO_ from it. */ private void addOtherArguments(List<String> currentArguments) { Properties properties = System.getProperties(); for (Map.Entry<Object, Object> entry : properties.entrySet()) { String key = String.valueOf(entry.getKey()); if (key.startsWith("GO_")) { String value = String.valueOf(entry.getValue()); currentArguments.add("-D" + key.substring(3) + "=" + value); } } } private void displayLaunchingProgress(Process server) throws InterruptedException, IOException { //Create and set up the window. final JFrame frame = new JFrame("Go Server"); frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); //Create and set up the content pane. final LaunchProgress progressPane = new LaunchProgress(); progressPane.setOpaque(true); //content panes must be opaque frame.setContentPane(progressPane); //Display the window. frame.pack(); frame.setSize(frame.getPreferredSize()); frame.setVisible(true); // Wait for the server to start int slept = 0; progressPane.progressBar.setIndeterminate(true); boolean startedOK = false; // When this URL gets a 200 code back we know we're good to go URL url = new URL(CRUISE_SERVER_URL); while ((slept * SLEEP_MILLIS / 1000) <= MAX_SLEEP_SECONDS) { Thread.sleep(SLEEP_MILLIS); progressPane.progressBar.setValue(slept); slept++; try { HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); urlConnection.connect(); if (urlConnection.getResponseCode() == 200) { LOG.info("Server up, let's go"); startedOK = true; break; } } catch (Exception e) { // Don't care LOG.info("Server not up yet, sleeping..."); } } if (!startedOK) { String errormsg = "Server not up within " + MAX_SLEEP_SECONDS + " seconds."; LOG.severe(errormsg); server.destroy(); progressPane.progressBar.setVisible(false); progressPane.label.setText(errormsg + " Please see /var/log/system.log for more info."); frame.pack(); frame.setSize(frame.getPreferredSize()); Thread.sleep(15000); System.exit(1); } frame.setVisible(false); frame.dispose(); // Now launch a browser window pointing at it Runtime.getRuntime().exec("open " + CRUISE_SERVER_URL); } private void setUpApplicationSupport() throws IOException { File applicationSupport = new File(APPLICATION_SUPPORT_PATHNAME); applicationSupport.mkdirs(); if (!applicationSupport.isDirectory()) { throw new IOException( "Could not create folder " + APPLICATION_SUPPORT_PATHNAME + ". Please check the permission settings for folder " + applicationSupport.getParentFile().getAbsolutePath()); } } private class LaunchProgress extends JPanel { public JProgressBar progressBar; public JLabel label; private LaunchProgress() { super(new BorderLayout()); progressBar = new JProgressBar(); progressBar.setIndeterminate(false); progressBar.setStringPainted(true); JPanel panel = new JPanel(); label = new JLabel("Starting up"); label.setHorizontalAlignment(JLabel.CENTER); panel.add(label); panel.add(progressBar); add(panel, BorderLayout.PAGE_START); setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20)); } } private static class Shutdown implements Runnable { private final Process server; public Shutdown(Process server) { this.server = server; } public void run() { server.destroy(); } } private class MyApplicationAdapter extends com.apple.eawt.ApplicationAdapter { public void handleAbout(ApplicationEvent e) { if (aboutBox == null) { aboutBox = new MacAboutBox(); } LOG.info("Got About describeChange"); aboutBox.setResizable(false); aboutBox.setVisible(true); e.setHandled(true); } public void handleQuit(ApplicationEvent event) { LOG.info("Got the boot..."); super.handleQuit(event); System.exit(0); } } }