org.ngrinder.recorder.Recorder.java Source code

Java tutorial

Introduction

Here is the source code for org.ngrinder.recorder.Recorder.java

Source

/* 
 * Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License. 
 */
package org.ngrinder.recorder;

import static net.grinder.util.NoOp.noOp;
import static net.grinder.util.TypeUtil.cast;
import static net.grinder.util.UrlUtil.toURL;
import static org.ngrinder.recorder.ui.component.SwingUtils.getFullScreenSize;

import java.awt.Dimension;
import java.awt.Frame;
import java.awt.GraphicsEnvironment;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.net.InetAddress;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Timer;
import java.util.TimerTask;

import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPopupMenu;
import javax.swing.JSplitPane;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.border.EmptyBorder;

import net.grinder.tools.tcpproxy.EndPoint;
import net.grinder.util.NoOp;
import net.grinder.util.Pair;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.SystemUtils;
import org.ngrinder.recorder.browser.BrowserFactoryEx;
import org.ngrinder.recorder.event.MessageBus;
import org.ngrinder.recorder.event.MessageBusConnection;
import org.ngrinder.recorder.event.Topics;
import org.ngrinder.recorder.infra.NGrinderRuntimeException;
import org.ngrinder.recorder.infra.RecorderConfig;
import org.ngrinder.recorder.proxy.ProxyEndPointPair;
import org.ngrinder.recorder.proxy.ScriptRecorderProxy;
import org.ngrinder.recorder.ui.AboutDialog;
import org.ngrinder.recorder.ui.BrowserContent;
import org.ngrinder.recorder.ui.NewBrowserButton;
import org.ngrinder.recorder.ui.RecordingControlPanel;
import org.ngrinder.recorder.ui.Tab;
import org.ngrinder.recorder.ui.TabButton;
import org.ngrinder.recorder.ui.TabButtonFactory;
import org.ngrinder.recorder.ui.TabFactory;
import org.ngrinder.recorder.ui.TabbedPane;
import org.ngrinder.recorder.ui.component.ComponentMover;
import org.ngrinder.recorder.ui.component.ComponentResizer;
import org.ngrinder.recorder.util.AsyncUtil;
import org.ngrinder.recorder.util.ResourceUtil;
import org.python.core.CompileMode;
import org.python.core.CompilerFlags;
import org.python.google.common.base.Charsets;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.teamdev.jxbrowser.Browser;
import com.teamdev.jxbrowser.BrowserServices;
import com.teamdev.jxbrowser.BrowserType;
import com.teamdev.jxbrowser.NewWindowContainer;
import com.teamdev.jxbrowser.NewWindowManager;
import com.teamdev.jxbrowser.NewWindowParams;

import freemarker.cache.StringTemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.DefaultObjectWrapper;
import freemarker.template.Template;

/**
 * Recorder main class.
 * 
 * @author JunHo Yoon
 * @version 1.0
 */
public class Recorder {
    private static final Logger LOGGER = LoggerFactory.getLogger(Recorder.class);

    private static final int NGRINDER_DEFAULT_PORT = 10288;
    private final JFrame frame = new JFrame();
    private ScriptRecorderProxy proxy;
    private final Gson gson = new Gson();

    /**
     * Constructor.
     */
    public Recorder() {
    }

    private RecorderConfig recorderConfig;

    /**
     * Start nGrinder Recorder Instance.
     */
    public void start() {
        initRecordConfig();
        proxy = new ScriptRecorderProxy(this.recorderConfig);
        ProxyEndPointPair endPoints = initProxy();
        TabbedPane tabbedPane = createTabbedPane();
        initFrame(tabbedPane);
        initTabbedPane(tabbedPane, endPoints);
        initialProxyCheck(endPoints);
        initShutdownHook();
    }

    /**
     * Initialize tabbed pane.
     * 
     * @param tabbedPane
     *            base tabbed pane
     * @param proxyEndPoint
     *            proxy endpoints which will be used to show the message how mobile devices connect
     *            this proxy
     */
    protected void initTabbedPane(TabbedPane tabbedPane, ProxyEndPointPair proxyEndPoint) {
        LOGGER.info("Tabbed Pane initalization is initialzed");
        initializeBrowserTab(tabbedPane, proxyEndPoint);
        initMessageHandler(tabbedPane);
        LOGGER.info("Tabbed Pane is initialzed");
    }

    /**
     * Initialize shutdown hook. The created proxy should be stopped for the socket port reusable.
     */
    protected void initShutdownHook() {
        Runnable shutdownHook = new Runnable() {
            public synchronized void run() {
                proxy.stopProxy();
            }
        };

        Runtime.getRuntime().addShutdownHook(new Thread(shutdownHook));
    }

    /**
     * Initialized record configuration.
     */
    protected void initRecordConfig() {
        recorderConfig = new RecorderConfig();
        recorderConfig.init();
        BrowserFactoryEx.setRecorderConfig(recorderConfig);
    }

    /**
     * Initialize proxy and get the created proxy. In addition, make it as a proxy for future
     * browser invoke. If the proxy creation is failed, it exits the application.
     * 
     * @return generated proxy port
     */
    protected ProxyEndPointPair initProxy() {
        ProxyEndPointPair endPoints = null;
        try {
            int port = recorderConfig.getPropertyInt("proxy.port", NGRINDER_DEFAULT_PORT);
            endPoints = proxy.startProxy(port);
            BrowserFactoryEx.setProxyEndPoints(endPoints);
        } catch (Exception e) {
            JOptionPane.showMessageDialog(frame,
                    "Proxy initalization is failed in this system.\n\n" + e.getMessage());
            LOGGER.error("Failed to start proxy", e);
            System.exit(-1);
        }
        return endPoints;
    }

    /**
     * Check the proxy to see it's generated with the default proxy port.
     * 
     * @param endPoints
     *            endPoints
     */
    protected void initialProxyCheck(ProxyEndPointPair endPoints) {
        int port = recorderConfig.getPropertyInt("proxy.port", NGRINDER_DEFAULT_PORT);
        if (endPoints.getHttpEndPoint().getPort() != port) {
            String msg = String.format(
                    "Already poxy port %d is used by the other recorder or processes.\n"
                            + "Recorder will use the %d port instead.",
                    port, endPoints.getHttpEndPoint().getPort());
            JOptionPane.showMessageDialog(frame, msg);
        }
    }

    /**
     * Create and initialized tabbed pane.
     * 
     * @return {@link TabbedPane}
     */
    protected TabbedPane createTabbedPane() {
        LOGGER.info("Creating Tabbed Pane");
        TabFactory tabFactory = new TabFactory();
        final TabbedPane tabbedPane = new TabbedPane(tabFactory);
        insertSupportedBrowserTabs(tabFactory, tabbedPane);

        insertNewBrowserButton(tabFactory, tabbedPane);
        setupNewWindowsManager(tabFactory, tabbedPane);
        LOGGER.info("Tabbed Pane is created");
        return tabbedPane;
    }

    /**
     * Initialize the frame with the given {@link TabbedPane}. It tries to get the last position and
     * location saved in the ${NGRINDER_RECORDER_HOME}/last_frame file.
     * 
     * @param tabbedPane
     *            tabbedPane
     */
    protected void initFrame(final TabbedPane tabbedPane) {
        File frameInfoFile = recorderConfig.getHome().getFile("last_frame");
        Dimension size = new Dimension(800, 600);
        Point location = null;
        if (frameInfoFile.exists()) {
            try {
                String readFileToString = FileUtils.readFileToString(frameInfoFile);
                Gson gson = new Gson();
                TypeToken<Pair<Dimension, Point>> typeToken = new TypeToken<Pair<Dimension, Point>>() {
                };
                Pair<Dimension, Point> frameInfo = gson.fromJson(readFileToString, typeToken.getType());
                size = frameInfo.getFirst();
                location = frameInfo.getSecond();

            } catch (Exception e) {
                LOGGER.error("Failed to load the frame info", e);
            }
        }
        Dimension screenSize = getFullScreenSize();

        if (screenSize.height < size.height || screenSize.width < size.width) {
            size = screenSize;
        }
        boolean frameUse = recorderConfig.getPropertyBoolean("use.frame", false);
        RecordingControlPanel recordingControlPanel = new RecordingControlPanel(proxy.getConnectionFilter());
        frame.getContentPane().add(createSplitPane(tabbedPane, recordingControlPanel));
        if (!frameUse) {
            frame.setUndecorated(true);
            new ComponentResizer(frame).setMinimumSize(new Dimension(800, 600));
            new ComponentMover(frame, tabbedPane);
        }
        frame.setTitle("nGrinder Recorder");
        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        frame.getRootPane().setBorder(new EmptyBorder(5, 5, 5, 5));
        frame.setLocationByPlatform(true);
        frame.getRootPane().setMinimumSize(new Dimension(800, 600));
        frame.setLocationRelativeTo(null);
        frame.setIconImage(ResourceUtil.getIcon("recorder16x16.png").getImage());
        frame.setFocusable(true);
        frame.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosed(WindowEvent e) {
                tabbedPane.disposeAllTabs();
            }
        });

        if (location != null) {
            frame.setLocation(location);
        }

        frame.setSize(size);
        frame.setVisible(true);
        frame.requestFocusInWindow();
        MessageBus.getInstance().getPublisher(Topics.PREPARE_TO_VIEW).propertyChange(
                new PropertyChangeEvent(this, "Prepare to View", null, recorderConfig.getHome().getDirectory()));
        initDisplayDetectionScheduledTask();
    }

    protected void initDisplayDetectionScheduledTask() {
        Timer timer = new Timer(true);
        timer.scheduleAtFixedRate(new TimerTask() {
            private Dimension prevSize;

            @Override
            public void run() {
                if (prevSize == null) {
                    prevSize = getFullScreenSize();
                } else {
                    Dimension currentSize = getFullScreenSize();
                    if (prevSize.equals(currentSize)) {
                        return;
                    }
                    if (prevSize.height > currentSize.height || prevSize.width > currentSize.width) {
                        frame.setSize(new Dimension(600, 500));
                        Point currentLocation = frame.getLocation();
                        if (currentSize.width < currentLocation.x || currentSize.height < currentLocation.y) {
                            frame.setLocation(200, 200);
                        }
                    }
                    prevSize = currentSize;

                }
            }
        }, 2000, 2000);
    }

    /**
     * Initialize global message handlers.
     * 
     * @param tabbedPane
     *            tabbedPane
     */
    protected void initMessageHandler(final TabbedPane tabbedPane) {
        final File home = recorderConfig.getHome().getDirectory();
        final MessageBus messageBusInstance = MessageBus.getInstance();
        MessageBusConnection connect = messageBusInstance.connect();
        connect.subscribe(Topics.HOME, new PropertyChangeListener() {
            public void propertyChange(PropertyChangeEvent evt) {
                Browser browser = cast(evt.getSource());
                browser.navigate(toURL(tempFile).toString());
            }
        });
        connect.subscribe(Topics.APPLICATION_CLOSE, new PropertyChangeListener() {
            public void propertyChange(PropertyChangeEvent evt) {
                proxy.stopProxy();
                frame.setExtendedState(Frame.NORMAL);
                File frameInfoFile = recorderConfig.getHome().getFile("last_frame");
                try {
                    Pair<Dimension, Point> pair = Pair.of(frame.getSize(), frame.getLocation());
                    String frameInfo = gson.toJson(pair);
                    FileUtils.writeStringToFile(frameInfoFile, frameInfo);
                } catch (Exception e) {
                    LOGGER.error("Failed to save the frame info", e);
                }

                messageBusInstance.getPublisher(Topics.PREPARE_TO_CLOSE)
                        .propertyChange(new PropertyChangeEvent(this, "PREPARE_TO_CLOSE", null, home));

                tabbedPane.disposeAllTabs();
                frame.setVisible(false);
                frame.dispose();

                System.exit(0);
            }
        });
        connect.subscribe(Topics.WINDOW_MAXIMIZE, new PropertyChangeListener() {
            public void propertyChange(PropertyChangeEvent evt) {
                if (frame.getExtendedState() == Frame.MAXIMIZED_BOTH) {
                    frame.setExtendedState(Frame.NORMAL);
                } else {
                    frame.setExtendedState(Frame.MAXIMIZED_BOTH);
                }
            }
        });

        connect.subscribe(Topics.WINDOW_MINIMIZE, new PropertyChangeListener() {
            public void propertyChange(PropertyChangeEvent evt) {
                if (frame.getExtendedState() == Frame.ICONIFIED) {
                    frame.setExtendedState(Frame.NORMAL);
                } else {
                    frame.setExtendedState(Frame.ICONIFIED);
                }
            }
        });

        connect.subscribe(Topics.SHOW_ABOUT_DIALOG, new PropertyChangeListener() {
            public void propertyChange(PropertyChangeEvent evt) {
                AboutDialog dialog = AboutDialog.getInstance(frame, recorderConfig);
                dialog.setVisible(true);
            }
        });
    }

    private StringTemplateLoader loader = new StringTemplateLoader();

    /**
     * Prepare the initial browser content template.
     * 
     * @param resourceName
     *            resource name to be loaded.
     * @param lang
     *            language name
     * @return loaded template
     */
    protected Template prepareTemplate(String resourceName, String lang) {
        String fullResourceName = "/html/" + resourceName + "_" + lang + ".html";
        InputStream htmlResourceStream = Recorder.class.getResourceAsStream(fullResourceName);
        if (htmlResourceStream == null) {
            htmlResourceStream = Recorder.class.getResourceAsStream("/html/" + resourceName + ".html");

        }
        try {
            loader.putTemplate(resourceName, IOUtils.toString(htmlResourceStream, "UTF-8"));
            Configuration freemarkerConfig = new Configuration();
            freemarkerConfig.setTemplateLoader(loader);
            DefaultObjectWrapper objectWrapper = new DefaultObjectWrapper();
            objectWrapper.setExposureLevel(DefaultObjectWrapper.EXPOSE_ALL);
            freemarkerConfig.setObjectWrapper(objectWrapper);
            return freemarkerConfig.getTemplate(resourceName);
        } catch (IOException e) {
            NoOp.noOp();
        } finally {
            IOUtils.closeQuietly(htmlResourceStream);
        }
        return null;
    }

    private File tempFile;

    /**
     * Initialize browser tabs with initial page.
     * 
     * @param tabbedPane
     *            tabbedPane
     * @param proxyEndPointPair
     *            proxy endpoint info
     */
    protected void initializeBrowserTab(TabbedPane tabbedPane, ProxyEndPointPair proxyEndPointPair) {

        Template template = prepareTemplate("initial", Locale.getDefault().getLanguage());
        HashMap<String, Object> hashMap = new HashMap<String, Object>();
        FileOutputStream fileOutputStream = null;
        OutputStreamWriter writer = null;
        try {
            tempFile = File.createTempFile("ngrinder", "init_page.html");
            tempFile.deleteOnExit();
            fileOutputStream = new FileOutputStream(tempFile);
            writer = new OutputStreamWriter(fileOutputStream, Charsets.UTF_8);

            EndPoint httpEndPoint = proxyEndPointPair.getHttpEndPoint();

            httpEndPoint = new EndPoint(InetAddress.getByName(httpEndPoint.getHost()).getHostAddress(),
                    httpEndPoint.getPort());
            hashMap.put("http_port", httpEndPoint);
            hashMap.put("https_port", httpEndPoint);
            hashMap.put("embedded_browser_recoding_enabled", isInAppBrowserRecodingSupportEnv());
            template.process(hashMap, writer);
        } catch (Exception e) {
            noOp();
        } finally {
            IOUtils.closeQuietly(fileOutputStream);
            IOUtils.closeQuietly(writer);
        }
        List<Tab> tabs = tabbedPane.getTabs();
        for (Tab each : tabs) {
            if (each.getTabItemContent() instanceof BrowserContent) {
                ((BrowserContent) each.getTabItemContent()).moveTo(toURL(tempFile));
            }
        }
    }

    protected boolean isInAppBrowserRecodingSupportEnv() {
        return SystemUtils.IS_OS_WINDOWS;
    }

    /**
     * Setup new windows manager. This defines how to handle new windows in the browser.
     * 
     * @param tabFactory
     *            {@link TabFactory}
     * @param tabbedPane
     *            {@link TabbedPane}
     */
    protected void setupNewWindowsManager(final TabFactory tabFactory, final TabbedPane tabbedPane) {
        BrowserServices.getInstance().setNewWindowManager(new NewWindowManager() {
            public NewWindowContainer evaluateWindow(final NewWindowParams params) {
                return new NewWindowContainer() {
                    public void insertBrowser(final Browser browser) {
                        final Tab createBrowserTab = tabFactory.createBrowserTab(browser);
                        SwingUtilities.invokeLater(new Runnable() {
                            @Override
                            public void run() {
                                tabbedPane.addTab(createBrowserTab);
                                tabbedPane.selectTab(createBrowserTab);
                            }
                        });
                    }
                };
            }
        });
    }

    /**
     * Create a split pane with the give left and right components.
     * 
     * @param left
     *            component located in left
     * @param right
     *            component located in right
     * @return JSplitPane instance
     */
    protected JSplitPane createSplitPane(final JComponent left, final JComponent right) {
        JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, left, right);
        splitPane.setDividerLocation(
                splitPane.getSize().width - splitPane.getInsets().right - splitPane.getDividerSize() - 200);
        splitPane.setResizeWeight(1);
        splitPane.setMinimumSize(new Dimension(600, 600));
        splitPane.setOneTouchExpandable(true);
        splitPane.setDividerSize(10);
        return splitPane;
    }

    /**
     * Program entry port.
     * 
     * @param args
     *            arguments
     */
    public static void main(String[] args) {

        try {
            initEnvironment();
            Recorder recorder = new Recorder();
            recorder.start();
        } catch (Exception e) {
            LOGGER.error("Error while starting Recorder", e);
        }
    }

    private static void initEnvironment() throws Exception {
        if (GraphicsEnvironment.isHeadless()) {
            throw new NGrinderRuntimeException("nGrinder Recorder can not run in the headless environment");
        }
        System.setProperty("com.apple.eawt.CocoaComponent.CompatibilityMode", "false");
        System.setProperty("apple.laf.useScreenMenuBar", "false");
        System.setProperty("com.apple.mrj.application.apple.menu.about.name", "nGrinder Recorder");
        System.setProperty("python.cachedir.skip", "true");
        System.setProperty("jxbrowser.ie.compatibility-disabled", "true");
        JPopupMenu.setDefaultLightWeightPopupEnabled(false);
        UIManager.setLookAndFeel(getLookAndFeelClassName());
        // Make the jython is loaded in the background
        AsyncUtil.invokeAsync(new Runnable() {
            @Override
            public void run() {
                org.python.core.ParserFacade.parse("print 'hello'", CompileMode.exec, "unnamed",
                        new CompilerFlags(CompilerFlags.PyCF_DONT_IMPLY_DEDENT | CompilerFlags.PyCF_ONLY_AST));
            }

        });
    }

    private static String getLookAndFeelClassName() {
        return UIManager.getSystemLookAndFeelClassName();
    }

    private void insertSupportedBrowserTabs(TabFactory tabFactory, final TabbedPane tabbedPane) {
        List<Tab> tabs = tabFactory.createSupportedBrowserTabs();
        for (Tab tab : tabs) {
            tabbedPane.addTab(tab);
            tabbedPane.selectTab(tab);
        }
    }

    private void insertNewBrowserButton(final TabFactory tabFactory, final TabbedPane tabbedPane) {
        TabButtonFactory factory = new TabButtonFactory();
        List<TabButton> buttons = factory.createNewBrowserButtons();
        for (TabButton button : buttons) {
            button.addActionListener(new ActionListener() {
                public void actionPerformed(final ActionEvent e) {
                    AsyncUtil.invokeAsync(new Runnable() {
                        public void run() {
                            NewBrowserButton browserButton = (NewBrowserButton) e.getSource();
                            BrowserType browserType = browserButton.getBrowserType();
                            final Tab tab = tabFactory.createBrowserTab(browserType);

                            SwingUtilities.invokeLater(new Runnable() {
                                public void run() {
                                    tabbedPane.addTab(tab);
                                    tabbedPane.selectTab(tab);
                                }
                            });
                        }
                    });
                }
            });
            tabbedPane.addTabButton(button);
        }
    }
}