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