同时运行多个Netbeans RCP应用程序实例

时间:2022-02-24 21:11:30

We have an application based on the netbeans rich client platform.

我们有一个基于netbeans富客户端平台的应用程序。

Default behaviour is that only one instance of the software can run, and this can be overridden by specifying a different user-dir as argument when starting the application.

默认行为是只能运行一个软件实例,这可以通过在启动应用程序时指定不同的user-dir作为参数来覆盖。

Are there any alternatives to this? Our clients rely on being able to run several instances of the application.

有没有其他选择?我们的客户依赖于能够运行应用程序的多个实例。

(We would prefer if we didnt have to implement our own launcher that examines netbeans userdir for locks to pass alternative userdirs)

(我们希望,如果我们不必实现我们自己的启动器,检查netbeans userdir锁定以通过替代用户)

Is there a build-in way in the netbeans RCP to easily support multiple instances?

netbeans RCP中是否有一种内置方式可以轻松支持多个实例?

cheers /B

2 个解决方案

#1


So, unable to find a proper way to do this (seems like it will be available with nb 7 though through a new launcher input parameter), my workmate implemented it in a somewhat hackish way.

因此,无法找到一个正确的方法来执行此操作(看起来它可以通过新的启动器输入参数与nb 7一起使用),我的同事以一种有点hackish的方式实现它。

He created an alternative main-class, which checks and manipulates the user-dir property so each instance gets its own userdir.

他创建了一个替代的主类,它检查和操作user-dir属性,以便每个实例获得自己的userdir。

Im still surprised that netbeans does not have this functionality!

我仍然感到惊讶,netbeans没有这个功能!

import java.io.File;

import org.netbeans.Main;

/**
 * Launches Netbeans Rich Client Platform application where mulitple instances of 
 * the application can run simultaneously for the same user.
 * <p>
 * Netbeans does not allow to run several instances of an application for the same 
 * user simultaneously, but this class does the job by fooling Netbeans.
 * If an instance is already running then a new folder is created and used for this instance.
 * </p>
 * <p>
 * This is quite tricky and this class is indeed a hack to override Netbeans behaviour.
 * An example makes it a little easier to understand, when application is first started 
 * Netbeans userdir is set from a configuration file like {@code etc/xxxxx.conf} to something like:<br>
 * {@code /home/username/.xxxxx/dev/} which includes a lock file.
 * <br>
 * If application is started again then this lock file is checked and Netbeans tries to connect to the other instance through a socket.
 * This class does the trick by never using this folder but instead creating unique directories for each instance like:<br>
 * {@code /home/username/.xxxxx/instance_01/netbeans/}<br>
 * {@code /home/username/.xxxxx/instance_02/netbeans/}<br>
 * {@code /home/username/.xxxxx/instance_03/netbeans/}<br>
 * ...
 * </p>
 * 
 */
public class MultipleInstancesMain
{
    /** Key for Netbeans default user dir */
    private static final String NETBEANS_USER = "netbeans.user";

    /** Argument to Netbeans for alternate user dir */
    private static final String USERDIR_ARG = "--userdir";

    /** Like "C:\Documents and Settings\username\Application Data\.xxxxx" or "/home/username/.xxxxx" */
    private static final File MAIN_DIR = getMainDir();

    /** Sub dir of MAIN_DIR for each instance of application */
    private static final String INSTANCE_DIR = "instance";

    /** Sub dir of INSTANCE_DIR where netbeans stores it's settings, logs and cache */
    private static final String NETBEANS_SUBDIR = "netbeans";

    /** Name of Netbeans lock file inside of NETBEANS_SUBDIR */
    private static final String LOCKFILE = "lock";

    /** Max number of instance directories we allow */
    private static final int MAX_DIR_COUNT = 999;

    /** Padding of instance dir */
    private static final String PAD = "000";
    private static final int PAD_LENGTH = PAD.length();

    /** 
     * Lock file could be present even though application is not running (after crash).
     * So we treat it as "dead" after some time, to prevent "dead directories".
     */
    private static final long LOCKFILE_MAX_AGE_IN_MILLIS = 1000L * 60L * 60L * 24L * 30L; // 30 days

    /**
     * Launches application with multiple instances support.
     * @param args command line arguments
     */
    public static void main(String[] args) throws Exception
    {
        // Get directory for this instance
        String[] userDir = new String[2];
        userDir[0] = USERDIR_ARG;
        userDir[1] = getNetbeansDir();

        // Remove default dir and set this class not to run again
        deleteDefaultNetbeansDir();
        System.clearProperty("netbeans.mainclass");

        // Start Netbeans again with new userdir and default main class
        startNetbeans(args, userDir);
    }

    /**
     * Starts Netbeans.
     * @param oldArgs command line arguments
     * @param newArgs new arguments added
     */
    private static void startNetbeans(String[] oldArgs, String[] newArgs)  throws Exception
    {
        String[] args = new String[oldArgs.length + newArgs.length];

        for (int i = 0; i <oldArgs.length; i++)
        {
            args[i] = oldArgs[i];
        }

        for (int i = 0; i < newArgs.length; i++)
        {
            args[oldArgs.length + i] = newArgs[i];
        }

        Main.main(args);
    }

    /**
     * @return the directory that Netbeans will use for this instance of the application 
     */
    private static String getNetbeansDir()
    {
        for(int i = 1; i <= MAX_DIR_COUNT; i++)
        {
            File instanceDir = getSuffixedInstanceDir(i);
            if (isLockFileFree(instanceDir))
            {
                File dirToUse = new File(instanceDir, NETBEANS_SUBDIR);
                return dirToUse.getAbsolutePath();
            }
        }

        // This would probably never happen, but we don't want an eternal loop above
        String errorMessage = String.format("Unable to find Netbeans userdir, %s dirs checked in '%s'", 
                MAX_DIR_COUNT, MAIN_DIR.getAbsolutePath());
        throw new RuntimeException(errorMessage);
    }

    private static File getSuffixedInstanceDir(int count)
    {
        String suffix = PAD + count;
        suffix = suffix.substring(suffix.length() - PAD_LENGTH);
        File suffixedDir = new File(MAIN_DIR, INSTANCE_DIR + "_" + suffix);
        return suffixedDir;
    }

    /**
     * Checks is if Netbeans lock file is free.
     * @param instanceDir directory to look for Netbeans directory and lock file in 
     * @return true if {@code instanceDir} does not have a Netbeans folder with a occupied lock file
     */
    private static boolean isLockFileFree(File instanceDir)
    {
        File netbeansDir = new File(instanceDir, NETBEANS_SUBDIR);
        File lockFile = new File(netbeansDir, LOCKFILE);

        if (lockFile.exists())
        {
            return deleteLockFileIfOldEnough(lockFile);
        }
        else
        {
            return true;
        }
    }

    /**
     * Deletes the lock file if it's old enough.
     * @param lockFile lock file to delete
     * @return true if it was deleted
     * @see #LOCKFILE_MAX_AGE_IN_MILLIS
     */
    private static boolean deleteLockFileIfOldEnough(File lockFile)
    {
        long currentTime = System.currentTimeMillis();
        long fileCreated = lockFile.lastModified();
        long ageInMillis = currentTime - fileCreated;

        if (ageInMillis > LOCKFILE_MAX_AGE_IN_MILLIS)
        {
            return lockFile.delete();
        }
        else
        {
            return false;
        }
    }

    /**
     * Netbeans is started with a default userdir, but we need to have a unique dir for each instance.
     * Main dir is the directory where directories of all instances are.
     * @return main directory in users home area where application settings, logs and cache is stored
     */
    private static File getMainDir()
    {
        String defaultNetbeansDir = System.getProperty(NETBEANS_USER);
        File mainDir = new File(defaultNetbeansDir).getParentFile();
        return mainDir;
    }

    /**
     * Since we don't use default Netbeans dir at all, we remove it completely.
     */
    private static void deleteDefaultNetbeansDir()
    {
        File defaultNetbeansDir = new File(System.getProperty(NETBEANS_USER));
        Thread t = new Thread(new DirectoryDeleter(defaultNetbeansDir), "NetbeansDirDeleter");
        t.start();
    }

    /**
     * There are unpredictable behaviour when deleting Netbeans default directory, sometime it succeeds and sometimes not.
     * But after some attempts it always succeeds, by deleting it in the background it will eventually be deleted.
     * @author username
     */
    private static class DirectoryDeleter implements Runnable
    {
        private static final long SLEEP_TIME = 3000;
        private final File dirToDelete;

        DirectoryDeleter(File dirToDelete)
        {
            this.dirToDelete = dirToDelete;
        }

        /**
         * @see java.lang.Runnable#run()
         */
        public void run()
        {
            while(!deleteDirOrFile(dirToDelete))
            {
                try
                {
                    Thread.sleep(SLEEP_TIME);
                }
                catch (InterruptedException e)
                {
                    // No idea to do anything here, should never happen anyway...
                    continue;
                }
            }
        }

        /**
         * Deletes a file or directory
         * @param dirFile directory or file to delete
         * @return true if deletion succeeded
         */
        private boolean deleteDirOrFile(File dirFile)
        {
            if (dirFile.isDirectory())
            {
                for (File f : dirFile.listFiles())
                {
                    boolean deleted = deleteDirOrFile(f);
                    if (!deleted)
                    {
                        return false;
                    }
                }
            }

            // The directory is now empty so delete it
            return dirFile.delete();
        }
    }
}

#2


I'm the mysterious developer who wrote the MultipleInstancesMain class above =)

我是上面编写MultipleInstancesMain类的神秘开发人员=)

As answer to the questions by Swati Sharma and uvaraj above, these notes might help:

作为Swati Sharma和uvaraj上述问题的答案,这些说明可能有所帮助:

(1) The alternative main class above should work for applications built on Netbeans Platform 6.5.1 (our does), other versions I don't know about.

(1)上面的替代主类应该适用于在Netbeans Platform 6.5.1(我们的)上构建的应用程序,其他版本我不知道。

(2) We keep this class in a tiny project that is built by the main build.xml ant script of our application (the one in the "suite" module). The jar is then copied to the /[appname]/modules/ folder (so it's on Netbeans classpath)

(2)我们将这个类保存在一个由我们的应用程序的主build.xml ant脚本(“suite”模块中的那个)构建的小项目中。然后将jar复制到/ [appname] / modules /文件夹(因此它位于Netbeans类路径上)

(3) The separate project can NOT be a Netbeans module, I guess there will be cyclic dependency or Netbeans classloader can't handle it or something like that.

(3)单独的项目不能是Netbeans模块,我想会有循环依赖或Netbeans类加载器无法处理它或类似的东西。

(4) Our application is then started by adding parameter to configuration file /[appname]/etc/.conf:

(4)然后通过在配置文件/[appname]/etc/.conf中添加参数来启动我们的应用程序:

default_options="-J-Dnetbeans.mainclass=com.appname.startup.MultipleInstancesMain"

Hope it helps // Uhlén

希望它有助于// @Uhlén

#1


So, unable to find a proper way to do this (seems like it will be available with nb 7 though through a new launcher input parameter), my workmate implemented it in a somewhat hackish way.

因此,无法找到一个正确的方法来执行此操作(看起来它可以通过新的启动器输入参数与nb 7一起使用),我的同事以一种有点hackish的方式实现它。

He created an alternative main-class, which checks and manipulates the user-dir property so each instance gets its own userdir.

他创建了一个替代的主类,它检查和操作user-dir属性,以便每个实例获得自己的userdir。

Im still surprised that netbeans does not have this functionality!

我仍然感到惊讶,netbeans没有这个功能!

import java.io.File;

import org.netbeans.Main;

/**
 * Launches Netbeans Rich Client Platform application where mulitple instances of 
 * the application can run simultaneously for the same user.
 * <p>
 * Netbeans does not allow to run several instances of an application for the same 
 * user simultaneously, but this class does the job by fooling Netbeans.
 * If an instance is already running then a new folder is created and used for this instance.
 * </p>
 * <p>
 * This is quite tricky and this class is indeed a hack to override Netbeans behaviour.
 * An example makes it a little easier to understand, when application is first started 
 * Netbeans userdir is set from a configuration file like {@code etc/xxxxx.conf} to something like:<br>
 * {@code /home/username/.xxxxx/dev/} which includes a lock file.
 * <br>
 * If application is started again then this lock file is checked and Netbeans tries to connect to the other instance through a socket.
 * This class does the trick by never using this folder but instead creating unique directories for each instance like:<br>
 * {@code /home/username/.xxxxx/instance_01/netbeans/}<br>
 * {@code /home/username/.xxxxx/instance_02/netbeans/}<br>
 * {@code /home/username/.xxxxx/instance_03/netbeans/}<br>
 * ...
 * </p>
 * 
 */
public class MultipleInstancesMain
{
    /** Key for Netbeans default user dir */
    private static final String NETBEANS_USER = "netbeans.user";

    /** Argument to Netbeans for alternate user dir */
    private static final String USERDIR_ARG = "--userdir";

    /** Like "C:\Documents and Settings\username\Application Data\.xxxxx" or "/home/username/.xxxxx" */
    private static final File MAIN_DIR = getMainDir();

    /** Sub dir of MAIN_DIR for each instance of application */
    private static final String INSTANCE_DIR = "instance";

    /** Sub dir of INSTANCE_DIR where netbeans stores it's settings, logs and cache */
    private static final String NETBEANS_SUBDIR = "netbeans";

    /** Name of Netbeans lock file inside of NETBEANS_SUBDIR */
    private static final String LOCKFILE = "lock";

    /** Max number of instance directories we allow */
    private static final int MAX_DIR_COUNT = 999;

    /** Padding of instance dir */
    private static final String PAD = "000";
    private static final int PAD_LENGTH = PAD.length();

    /** 
     * Lock file could be present even though application is not running (after crash).
     * So we treat it as "dead" after some time, to prevent "dead directories".
     */
    private static final long LOCKFILE_MAX_AGE_IN_MILLIS = 1000L * 60L * 60L * 24L * 30L; // 30 days

    /**
     * Launches application with multiple instances support.
     * @param args command line arguments
     */
    public static void main(String[] args) throws Exception
    {
        // Get directory for this instance
        String[] userDir = new String[2];
        userDir[0] = USERDIR_ARG;
        userDir[1] = getNetbeansDir();

        // Remove default dir and set this class not to run again
        deleteDefaultNetbeansDir();
        System.clearProperty("netbeans.mainclass");

        // Start Netbeans again with new userdir and default main class
        startNetbeans(args, userDir);
    }

    /**
     * Starts Netbeans.
     * @param oldArgs command line arguments
     * @param newArgs new arguments added
     */
    private static void startNetbeans(String[] oldArgs, String[] newArgs)  throws Exception
    {
        String[] args = new String[oldArgs.length + newArgs.length];

        for (int i = 0; i <oldArgs.length; i++)
        {
            args[i] = oldArgs[i];
        }

        for (int i = 0; i < newArgs.length; i++)
        {
            args[oldArgs.length + i] = newArgs[i];
        }

        Main.main(args);
    }

    /**
     * @return the directory that Netbeans will use for this instance of the application 
     */
    private static String getNetbeansDir()
    {
        for(int i = 1; i <= MAX_DIR_COUNT; i++)
        {
            File instanceDir = getSuffixedInstanceDir(i);
            if (isLockFileFree(instanceDir))
            {
                File dirToUse = new File(instanceDir, NETBEANS_SUBDIR);
                return dirToUse.getAbsolutePath();
            }
        }

        // This would probably never happen, but we don't want an eternal loop above
        String errorMessage = String.format("Unable to find Netbeans userdir, %s dirs checked in '%s'", 
                MAX_DIR_COUNT, MAIN_DIR.getAbsolutePath());
        throw new RuntimeException(errorMessage);
    }

    private static File getSuffixedInstanceDir(int count)
    {
        String suffix = PAD + count;
        suffix = suffix.substring(suffix.length() - PAD_LENGTH);
        File suffixedDir = new File(MAIN_DIR, INSTANCE_DIR + "_" + suffix);
        return suffixedDir;
    }

    /**
     * Checks is if Netbeans lock file is free.
     * @param instanceDir directory to look for Netbeans directory and lock file in 
     * @return true if {@code instanceDir} does not have a Netbeans folder with a occupied lock file
     */
    private static boolean isLockFileFree(File instanceDir)
    {
        File netbeansDir = new File(instanceDir, NETBEANS_SUBDIR);
        File lockFile = new File(netbeansDir, LOCKFILE);

        if (lockFile.exists())
        {
            return deleteLockFileIfOldEnough(lockFile);
        }
        else
        {
            return true;
        }
    }

    /**
     * Deletes the lock file if it's old enough.
     * @param lockFile lock file to delete
     * @return true if it was deleted
     * @see #LOCKFILE_MAX_AGE_IN_MILLIS
     */
    private static boolean deleteLockFileIfOldEnough(File lockFile)
    {
        long currentTime = System.currentTimeMillis();
        long fileCreated = lockFile.lastModified();
        long ageInMillis = currentTime - fileCreated;

        if (ageInMillis > LOCKFILE_MAX_AGE_IN_MILLIS)
        {
            return lockFile.delete();
        }
        else
        {
            return false;
        }
    }

    /**
     * Netbeans is started with a default userdir, but we need to have a unique dir for each instance.
     * Main dir is the directory where directories of all instances are.
     * @return main directory in users home area where application settings, logs and cache is stored
     */
    private static File getMainDir()
    {
        String defaultNetbeansDir = System.getProperty(NETBEANS_USER);
        File mainDir = new File(defaultNetbeansDir).getParentFile();
        return mainDir;
    }

    /**
     * Since we don't use default Netbeans dir at all, we remove it completely.
     */
    private static void deleteDefaultNetbeansDir()
    {
        File defaultNetbeansDir = new File(System.getProperty(NETBEANS_USER));
        Thread t = new Thread(new DirectoryDeleter(defaultNetbeansDir), "NetbeansDirDeleter");
        t.start();
    }

    /**
     * There are unpredictable behaviour when deleting Netbeans default directory, sometime it succeeds and sometimes not.
     * But after some attempts it always succeeds, by deleting it in the background it will eventually be deleted.
     * @author username
     */
    private static class DirectoryDeleter implements Runnable
    {
        private static final long SLEEP_TIME = 3000;
        private final File dirToDelete;

        DirectoryDeleter(File dirToDelete)
        {
            this.dirToDelete = dirToDelete;
        }

        /**
         * @see java.lang.Runnable#run()
         */
        public void run()
        {
            while(!deleteDirOrFile(dirToDelete))
            {
                try
                {
                    Thread.sleep(SLEEP_TIME);
                }
                catch (InterruptedException e)
                {
                    // No idea to do anything here, should never happen anyway...
                    continue;
                }
            }
        }

        /**
         * Deletes a file or directory
         * @param dirFile directory or file to delete
         * @return true if deletion succeeded
         */
        private boolean deleteDirOrFile(File dirFile)
        {
            if (dirFile.isDirectory())
            {
                for (File f : dirFile.listFiles())
                {
                    boolean deleted = deleteDirOrFile(f);
                    if (!deleted)
                    {
                        return false;
                    }
                }
            }

            // The directory is now empty so delete it
            return dirFile.delete();
        }
    }
}

#2


I'm the mysterious developer who wrote the MultipleInstancesMain class above =)

我是上面编写MultipleInstancesMain类的神秘开发人员=)

As answer to the questions by Swati Sharma and uvaraj above, these notes might help:

作为Swati Sharma和uvaraj上述问题的答案,这些说明可能有所帮助:

(1) The alternative main class above should work for applications built on Netbeans Platform 6.5.1 (our does), other versions I don't know about.

(1)上面的替代主类应该适用于在Netbeans Platform 6.5.1(我们的)上构建的应用程序,其他版本我不知道。

(2) We keep this class in a tiny project that is built by the main build.xml ant script of our application (the one in the "suite" module). The jar is then copied to the /[appname]/modules/ folder (so it's on Netbeans classpath)

(2)我们将这个类保存在一个由我们的应用程序的主build.xml ant脚本(“suite”模块中的那个)构建的小项目中。然后将jar复制到/ [appname] / modules /文件夹(因此它位于Netbeans类路径上)

(3) The separate project can NOT be a Netbeans module, I guess there will be cyclic dependency or Netbeans classloader can't handle it or something like that.

(3)单独的项目不能是Netbeans模块,我想会有循环依赖或Netbeans类加载器无法处理它或类似的东西。

(4) Our application is then started by adding parameter to configuration file /[appname]/etc/.conf:

(4)然后通过在配置文件/[appname]/etc/.conf中添加参数来启动我们的应用程序:

default_options="-J-Dnetbeans.mainclass=com.appname.startup.MultipleInstancesMain"

Hope it helps // Uhlén

希望它有助于// @Uhlén