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}