001/* 002 * (C) Copyright 2011-2018 Nuxeo (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * Julien Carsique 018 * 019 */ 020 021package org.nuxeo.launcher.gui; 022 023import java.awt.Dimension; 024import java.awt.HeadlessException; 025import java.awt.Toolkit; 026import java.io.File; 027import java.io.FileReader; 028import java.io.IOException; 029import java.text.MessageFormat; 030import java.util.HashMap; 031import java.util.Locale; 032import java.util.Map; 033import java.util.MissingResourceException; 034import java.util.Properties; 035import java.util.ResourceBundle; 036import java.util.concurrent.ExecutorService; 037import java.util.concurrent.Executors; 038 039import javax.swing.SwingUtilities; 040 041import org.apache.commons.lang3.SystemUtils; 042import org.apache.commons.logging.Log; 043import org.apache.commons.logging.LogFactory; 044import org.apache.commons.vfs2.FileChangeEvent; 045import org.apache.commons.vfs2.FileListener; 046import org.apache.commons.vfs2.FileObject; 047import org.apache.commons.vfs2.FileSystemException; 048import org.apache.commons.vfs2.VFS; 049import org.apache.commons.vfs2.impl.DefaultFileMonitor; 050 051import org.nuxeo.connect.update.PackageException; 052import org.nuxeo.launcher.NuxeoLauncher; 053import org.nuxeo.launcher.config.ConfigurationGenerator; 054import org.nuxeo.launcher.daemon.DaemonThreadFactory; 055import org.nuxeo.launcher.gui.logs.LogsHandler; 056import org.nuxeo.launcher.gui.logs.LogsSource; 057import org.nuxeo.launcher.gui.logs.LogsSourceThread; 058 059/** 060 * Launcher controller for graphical user interface 061 * 062 * @author jcarsique 063 * @since 5.4.2 064 * @see NuxeoLauncher 065 */ 066public class NuxeoLauncherGUI { 067 static final Log log = LogFactory.getLog(NuxeoLauncherGUI.class); 068 069 protected static final long UPDATE_FREQUENCY = 3000; 070 071 private ExecutorService executor = newExecutor(); 072 073 /** 074 * @since 5.6 075 */ 076 protected ExecutorService newExecutor() { 077 return Executors.newCachedThreadPool(new DaemonThreadFactory("NuxeoLauncherGUITask")); 078 } 079 080 protected NuxeoLauncher launcher; 081 082 protected NuxeoFrame nuxeoFrame; 083 084 protected HashMap<String, LogsSourceThread> logsMap = new HashMap<>(); 085 086 /** 087 * @since 5.6 088 */ 089 public final Map<String, LogsSourceThread> getLogsMap() { 090 return logsMap; 091 } 092 093 private DefaultFileMonitor dumpedConfigMonitor; 094 095 private Thread nuxeoFrameUpdater; 096 097 /** 098 * @param aLauncher Launcher being used in background 099 */ 100 public NuxeoLauncherGUI(NuxeoLauncher aLauncher) { 101 launcher = aLauncher; 102 // Set OS-specific decorations 103 if (SystemUtils.IS_OS_MAC) { 104 System.setProperty("apple.laf.useScreenMenuBar", "true"); 105 System.setProperty("com.apple.mrj.application.growbox.intrudes", "false"); 106 System.setProperty("com.apple.mrj.application.live-resize", "true"); 107 System.setProperty("com.apple.macos.smallTabs", "true"); 108 } 109 initFrame(); 110 dumpedConfigMonitor = new DefaultFileMonitor(new FileListener() { 111 @Override 112 public void fileDeleted(FileChangeEvent event) { 113 // Ignore 114 } 115 116 @Override 117 public void fileCreated(FileChangeEvent event) { 118 updateNuxeoFrame(); 119 } 120 121 @Override 122 public void fileChanged(FileChangeEvent event) { 123 updateNuxeoFrame(); 124 } 125 126 synchronized private void updateNuxeoFrame() { 127 waitForFrameLoaded(); 128 log.debug("Configuration changed. Reloading frame..."); 129 launcher.init(); 130 updateServerStatus(); 131 try { 132 Properties props = new Properties(); 133 props.load(new FileReader(getConfigurationGenerator().getDumpedConfig())); 134 nuxeoFrame.updateLogsTab(props.getProperty("log.id")); 135 } catch (IOException e) { 136 log.error(e); 137 } 138 } 139 }); 140 try { 141 dumpedConfigMonitor.setRecursive(false); 142 FileObject dumpedConfig = VFS.getManager().resolveFile( 143 getConfigurationGenerator().getDumpedConfig().getPath()); 144 dumpedConfigMonitor.addFile(dumpedConfig); 145 dumpedConfigMonitor.start(); 146 } catch (FileSystemException e) { 147 throw new RuntimeException("Couldn't find " + getConfigurationGenerator().getNuxeoConf(), e); 148 } 149 } 150 151 protected void initFrame() { 152 final NuxeoLauncherGUI controller = this; 153 SwingUtilities.invokeLater(() -> { 154 try { 155 if (nuxeoFrame != null) { 156 executor.shutdownNow(); 157 nuxeoFrame.close(); 158 executor = newExecutor(); 159 } 160 nuxeoFrame = createNuxeoFrame(controller); 161 nuxeoFrame.pack(); 162 // Center frame 163 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); 164 nuxeoFrame.setLocation(screenSize.width / 2 - (nuxeoFrame.getWidth() / 2), screenSize.height / 2 165 - (nuxeoFrame.getHeight() / 2)); 166 nuxeoFrame.setVisible(true); 167 } catch (HeadlessException e) { 168 log.error(e); 169 } 170 }); 171 if (nuxeoFrameUpdater == null) { 172 nuxeoFrameUpdater = new Thread(() -> { 173 while (true) { 174 updateServerStatus(); 175 try { 176 Thread.sleep(UPDATE_FREQUENCY); 177 } catch (InterruptedException e) { 178 Thread.currentThread().interrupt(); 179 return; 180 } 181 } 182 }); 183 nuxeoFrameUpdater.start(); 184 } 185 } 186 187 /** 188 * Instantiate a new {@link NuxeoFrame}. Can be overridden if needed. 189 */ 190 protected NuxeoFrame createNuxeoFrame(NuxeoLauncherGUI controller) { 191 return new NuxeoFrame(controller); 192 } 193 194 public void initLogsManagement(String logFile, ColoredTextPane textArea) { 195 File file = new File(logFile); 196 LogsSource logsSource = new LogsSource(file); 197 logsSource.skip(file.length() - NuxeoFrame.LOG_MAX_SIZE); 198 logsSource.addObserver(new LogsHandler(textArea)); 199 LogsSourceThread logsSourceThread = new LogsSourceThread(logsSource); 200 logsSourceThread.setDaemon(true); 201 executor.execute(logsSourceThread); 202 logsMap.put(logFile, logsSourceThread); 203 } 204 205 /** 206 * @see NuxeoLauncher#stop() 207 */ 208 public void stop() { 209 waitForFrameLoaded(); 210 nuxeoFrame.stopping = true; 211 nuxeoFrame.mainButton.setText(getMessage("mainbutton.stop.inprogress")); 212 nuxeoFrame.mainButton.setToolTipText(NuxeoLauncherGUI.getMessage("mainbutton.stop.tooltip")); 213 nuxeoFrame.mainButton.setIcon(nuxeoFrame.stopIcon); 214 executor.execute(() -> { 215 launcher.stop(); 216 nuxeoFrame.stopping = false; 217 updateServerStatus(); 218 }); 219 } 220 221 /** 222 * Update interface information with current server status. 223 * 224 * @see NuxeoFrame#updateMainButton() 225 * @see NuxeoFrame#updateSummary() 226 */ 227 public void updateServerStatus() { 228 waitForFrameLoaded(); 229 nuxeoFrame.updateMainButton(); 230 nuxeoFrame.updateLaunchBrowserButton(); 231 nuxeoFrame.updateSummary(); 232 } 233 234 /** 235 * Waits for the Launcher GUI frame being initialized. Should be called before any access to {@link NuxeoFrame} from 236 * this controller. 237 */ 238 public void waitForFrameLoaded() { 239 while (nuxeoFrame == null) { 240 try { 241 Thread.sleep(500); 242 } catch (InterruptedException e) { 243 Thread.currentThread().interrupt(); 244 throw new RuntimeException(e); 245 } 246 } 247 } 248 249 /** 250 * @see NuxeoLauncher#doStart() NuxeoLauncher#doStartAndWait() 251 */ 252 public void start() { 253 waitForFrameLoaded(); 254 nuxeoFrame.stopping = false; 255 nuxeoFrame.mainButton.setText(NuxeoLauncherGUI.getMessage("mainbutton.start.inprogress")); 256 nuxeoFrame.mainButton.setToolTipText(NuxeoLauncherGUI.getMessage("mainbutton.stop.tooltip")); 257 nuxeoFrame.mainButton.setIcon(nuxeoFrame.stopIcon); 258 executor.execute(() -> { 259 try { 260 launcher.doStartAndWait(); 261 } catch (PackageException e) { 262 log.error("Could not initialize the packaging subsystem", e); 263 System.exit(launcher == null || launcher.getErrorValue() == NuxeoLauncher.EXIT_CODE_OK ? NuxeoLauncher.EXIT_CODE_INVALID 264 : launcher.getErrorValue()); 265 } 266 updateServerStatus(); 267 }); 268 } 269 270 /** 271 * @param logFile LogFile managed by the involved reader 272 * @param isActive Set logs reader active or not 273 */ 274 public void notifyLogsObserver(String logFile, boolean isActive) { 275 LogsSourceThread logsSourceThread = logsMap.get(logFile); 276 if (isActive) { 277 logsSourceThread.getSource().resume(); 278 } else { 279 logsSourceThread.getSource().pause(); 280 } 281 } 282 283 /** 284 * @return Configuration generator used by {@link #launcher} 285 */ 286 public ConfigurationGenerator getConfigurationGenerator() { 287 return launcher.getConfigurationGenerator(); 288 } 289 290 /** 291 * Get internationalized message 292 * 293 * @param key Message key 294 * @return Localized message value 295 */ 296 public static String getMessage(String key) { 297 String message; 298 try { 299 message = ResourceBundle.getBundle("i18n/messages").getString(key); 300 } catch (MissingResourceException e) { 301 log.debug(getMessage("missing.translation") + key); 302 message = ResourceBundle.getBundle("i18n/messages", Locale.ENGLISH).getString(key); 303 } 304 return message; 305 } 306 307 /** 308 * Get internationalized message with parameters 309 * 310 * @param key Message key 311 * @return Localized message value 312 * @since 5.9.2 313 */ 314 public static String getMessage(String key, Object... params) { 315 return MessageFormat.format(getMessage(key), params); 316 } 317 318 /** 319 * @return the NuxeoLauncher managed by the current GUI 320 * @since 5.5 321 */ 322 public NuxeoLauncher getLauncher() { 323 return launcher; 324 } 325 326}