All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.update4j.SingleInstanceManager Maven / Gradle / Ivy

There is a newer version: 1.5.9
Show newest version
/*
 * Copyright 2018 Mordechai Meisels
 * 
 * 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.update4j;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.RandomAccessFile;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.nio.channels.FileLock;
import java.nio.channels.OverlappingFileLockException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;

import org.update4j.util.FileUtils;

/**
 * A convenience class to force only a single instance of the code proceeding
 * {@link #execute()} until JVM shutdown. It also allows for newer instances to
 * send a list of strings to the original single instance.
 * 
 * @author Mordechai Meisels
 *
 */
public class SingleInstanceManager {

    private SingleInstanceManager() {
    }

    /**
     * A call to this method will ensure that everything after that call will not be
     * run by more than one instance of this application. The first will pass, but
     * all others will shut down.
     * 
     * 

* The lock files (special files that this method uses to know that there is a * running instance) will be placed in the current directory. It is highly * discouraged to use the current directory, as one may start the application * from a different directory and circumvent the single instance mechanism. Use * {@link #execute(Path)} instead, for applications that the user may move * around. * *

* You must never call this more than once in the whole application. * * @throws OverlappingFileLockException * If this method is called more than once on the same JVM. */ public static void execute() { execute(null); } /** * A call to this method will ensure that everything after that call will not be * run by more than one instance of this application. The first will pass, but * all others will shut down. * *

* You can specify the location where to place the lock files (special files * that this method uses to know that there is a running instance). It is highly * discouraged to use the current directory, as one may start the application * from a different directory and circumvent the single instance mechanism. * *

* You must never call this more than once in the whole application. * * @param lockFileDir * The location where to place the lock files. If {@code null}, it * will use the current directory. * * @throws OverlappingFileLockException * If this method is called more than once on the same JVM. */ public static void execute(Path lockFileDir) { execute(null, null, lockFileDir); } /** * A call to this method will ensure that everything after that call will not be * run by more than one instance of this application. The first will pass, but * all others will shut down. * *

* The first instance will receive the {@code args} list of strings of the new * instance in the {@code onNewInstance} consumer (called in special instance * message dispatching thread) whenever a new instance is created and * successfully shut down. * *

* The lock files (special files that this method uses to know that there is a * running instance) will be placed in the current directory. It is highly * discouraged to use the current directory, as one may start the application * from a different directory and circumvent the single instance mechanism. Use * {@link #execute(List, Consumer, Path)} instead, for applications that the * user may move around. * *

* You must never call this more than once in the whole application. * * * @param args * The list of strings to pass to the single instance, if this is a * subsequent instance. {@code null} will be passed as an empty list. * * @param onNewInstance * The receiver consumer of the passed list of strings, if this is * the initial instance. May be {@code null}. * * @throws OverlappingFileLockException * If this method is called more than once on the same JVM. */ public static void execute(List args, Consumer> onNewInstance) { execute(args, onNewInstance, null); } /** * A call to this method will ensure that everything after that call will not be * run by more than one instance of this application. The first will pass, but * all others will shut down. * *

* The first instance will receive the {@code args} list of strings of the new * instance in the {@code onNewInstance} consumer (called in special instance * message dispatching thread) whenever a new instance is created and * successfully shut down. * *

* You can specify the location where to place the lock files (special files * that this method uses to know that there is a running instance). It is highly * discouraged to use the current directory, as one may start the application * from a different directory and circumvent the single instance mechanism. * *

* You must never call this more than once in the whole application. * * * @param args * The list of strings to pass to the single instance, if this is a * subsequent instance. {@code null} will be passed as an empty list. * * @param onNewInstance * The receiver consumer of the passed list of strings, if this is * the initial instance. May be {@code null}. * @param lockFileDir * The location where to place the lock files. If {@code null}, it * will use the current directory. * * @throws OverlappingFileLockException * If this method is called more than once on the same JVM. */ public static void execute(List args, Consumer> onNewInstance, Path lockFileDir) { try { tryExecute(args, onNewInstance, lockFileDir); } catch (SingleInstanceException e) { System.exit(1); } } /** * A call to this method will try to create a single instance barrier. The first * instance will pass, but all others will throw a * {@link SingleInstanceException}. * *

* The lock files (special files that this method uses to know that there is a * running instance) will be placed in the current directory. It is highly * discouraged to use the current directory, as one may start the application * from a different directory and circumvent the single instance mechanism. Use * {@link #execute(Path)} instead, for applications that the user may move * around. * *

* You must never call this more than once in the whole application. * * @throws SingleInstanceException * If this method is called when there's already a running instance. * @throws OverlappingFileLockException * If this method is called more than once on the same JVM. */ public static void tryExecute() throws SingleInstanceException { tryExecute(null); } /** * A call to this method will try to create a single instance barrier. The first * instance will pass, but all others will throw a * {@link SingleInstanceException}. * *

* You can specify the location where to place the lock files (special files * that this method uses to know that there is a running instance). It is highly * discouraged to use the current directory, as one may start the application * from a different directory and circumvent the single instance mechanism. * *

* You must never call this more than once in the whole application. * * @param lockFileDir * The location where to place the lock files. If {@code null}, it * will use the current directory. * * @throws SingleInstanceException * If this method is called when there's already a running instance * sharing the same {@code lockFileDir}. * @throws OverlappingFileLockException * If this method is called more than once on the same JVM. */ public static void tryExecute(Path lockFileDir) throws SingleInstanceException { tryExecute(null, null, lockFileDir); } /** * A call to this method will try to create a single instance barrier. The first * instance will pass, but all others will throw a * {@link SingleInstanceException}. * *

* The first instance will receive the {@code args} list of strings of the new * instance in the {@code onNewInstance} consumer (called in special instance * message dispatching thread) whenever a new instance is created and * successfully shut down. * *

* The lock files (special files that this method uses to know that there is a * running instance) will be placed in the current directory. It is highly * discouraged to use the current directory, as one may start the application * from a different directory and circumvent the single instance mechanism. Use * {@link #execute(List, Consumer, Path)} instead, for applications that the * user may move around. * *

* You must never call this more than once in the whole application. * * * @param args * The list of strings to pass to the single instance, if this is a * subsequent instance. {@code null} will be passed as an empty list. * * @param onNewInstance * The receiver consumer of the passed list of strings, if this is * the initial instance. May be {@code null}. * * @throws SingleInstanceException * If this method is called when there's already a running instance. * @throws OverlappingFileLockException * If this method is called more than once on the same JVM. */ public static void tryExecute(List args, Consumer> onNewInstance) throws SingleInstanceException { tryExecute(args, onNewInstance, null); } /** * A call to this method will try to create a single instance barrier. The first * instance will pass, but all others will throw a * {@link SingleInstanceException}. * *

* The first instance will receive the {@code args} list of strings of the new * instance in the {@code onNewInstance} consumer (called in special instance * message dispatching thread) whenever a new instance is created and * successfully shut down. * *

* You can specify the location where to place the lock files (special files * that this method uses to know that there is a running instance). It is highly * discouraged to use the current directory, as one may start the application * from a different directory and circumvent the single instance mechanism. * *

* You must never call this more than once in the whole application. * * * @param args * The list of strings to pass to the single instance, if this is a * subsequent instance. {@code null} will be passed as an empty list. * @param onNewInstance * The receiver consumer of the passed list of strings, if this is * the initial instance. May be {@code null}. * @param lockFileDir * The location where to place the lock files. If {@code null}, it * will use the current directory. * @throws SingleInstanceException * If this method is called when there's already a running instance * sharing the same {@code lockFileDir}. * @throws OverlappingFileLockException * If this method is called more than once on the same JVM. */ public static void tryExecute(List args, Consumer> onNewInstance, Path lockFileDir) throws SingleInstanceException { if (args == null) { args = List.of(); } if (lockFileDir == null) { lockFileDir = Paths.get(System.getProperty("user.dir")); } Path lockFile = lockFileDir.resolve(".lock"); Path portFile = lockFileDir.resolve(".port"); try { RandomAccessFile randomAccess = new RandomAccessFile(lockFile.toFile(), "rw"); FileLock lock = randomAccess.getChannel().tryLock(); if (lock != null) { FileUtils.windowsHidden(lockFile, true); ServerSocket server = new ServerSocket(0, 0, InetAddress.getByName(null)); Runtime.getRuntime().addShutdownHook(new Thread() { public void run() { try { lock.release(); server.close(); randomAccess.close(); Files.delete(lockFile); Files.delete(portFile); } catch (IOException e) { e.printStackTrace(); } } }); try (BufferedWriter out = Files.newBufferedWriter(portFile, StandardOpenOption.CREATE)) { out.write("" + server.getLocalPort()); } FileUtils.windowsHidden(portFile, false); Thread listen = new Thread(() -> { while (!server.isClosed()) { try (Socket socket = server.accept(); BufferedReader in = new BufferedReader( new InputStreamReader(socket.getInputStream()))) { List input = in.lines().collect(Collectors.toList()); if (onNewInstance != null) { onNewInstance.accept(input); } } catch (SocketException e) { if (!server.isClosed()) // otherwise it's normal on shutdown e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }, "Instance Message Dispatcher"); listen.setDaemon(true); listen.start(); } else { try (BufferedReader in = Files.newBufferedReader(portFile)) { randomAccess.close(); int port = Integer.parseInt(in.readLine()); try (Socket signal = new Socket("localhost", port)) { // set timeout in case of a messed up network interface signal.setSoTimeout(1000); try (BufferedWriter out = new BufferedWriter( new OutputStreamWriter(signal.getOutputStream()))) { for (String s : args) { out.write(s + "\n"); } } } } catch (IOException e1) { e1.printStackTrace(); } throw new SingleInstanceException(); } } catch (IOException e) { e.printStackTrace(); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy