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