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}