diff --git a/client/build.gradle b/client/build.gradle index a3ffca4acc..bcb71aff13 100644 --- a/client/build.gradle +++ b/client/build.gradle @@ -129,8 +129,6 @@ tasks.register('devClient', JavaExec) { '--add-opens=java.base/sun.security.pkcs=ALL-UNNAMED', '--add-opens=java.base/sun.security.rsa=ALL-UNNAMED', '--add-opens=java.base/sun.security.x509=ALL-UNNAMED', - '--add-opens=java.desktop/com.apple.eawt=ALL-UNNAMED', - '--add-opens=java.desktop/com.apple.eio=ALL-UNNAMED', '--add-opens=java.desktop/java.awt=ALL-UNNAMED', '--add-opens=java.desktop/java.awt.color=ALL-UNNAMED', '--add-opens=java.desktop/java.awt.font=ALL-UNNAMED', diff --git a/client/src/main/java/com/mirth/connect/client/ui/BareBonesBrowserLaunch.java b/client/src/main/java/com/mirth/connect/client/ui/BareBonesBrowserLaunch.java index a45efb7e79..f171475c63 100644 --- a/client/src/main/java/com/mirth/connect/client/ui/BareBonesBrowserLaunch.java +++ b/client/src/main/java/com/mirth/connect/client/ui/BareBonesBrowserLaunch.java @@ -4,6 +4,8 @@ package com.mirth.connect.client.ui; +import java.awt.Desktop; +import java.net.URI; import java.util.Arrays; /** @@ -40,31 +42,17 @@ public class BareBonesBrowserLaunch { * "https://dna-engine.org/") */ public static void openURL(String url) { - try { // attempt to use Desktop library from JDK 1.6+ - Class d = Class.forName("java.awt.Desktop"); - d.getDeclaredMethod("browse", - new Class[] { java.net.URI.class }).invoke( - d.getDeclaredMethod("getDesktop").invoke(null), - new Object[] { java.net.URI.create(url) }); - } catch (Exception ignore) { // library not available or failed - String osName = System.getProperty("os.name"); + try { + Desktop.getDesktop().browse(URI.create(url)); + } catch (Exception ignore) { // Desktop not supported or failed, assume Unix or Linux try { - if (osName.startsWith("Mac OS")) { - Class.forName("com.apple.eio.FileManager").getDeclaredMethod( - "openURL", new Class[] { String.class }).invoke(null, - new Object[] { url }); - } else if (osName.startsWith("Windows")) - Runtime.getRuntime().exec(new String[] { - "rundll32", "url.dll,FileProtocolHandler", url }); - else { // assume Unix or Linux - String browser = null; - for (String b : browsers) - if (browser == null - && Runtime.getRuntime().exec(new String[] { "which", b }).getInputStream().read() != -1) - Runtime.getRuntime().exec(new String[] { browser = b, url }); - if (browser == null) - throw new Exception(Arrays.toString(browsers)); - } + String browser = null; + for (String b : browsers) + if (browser == null + && Runtime.getRuntime().exec(new String[] { "which", b }).getInputStream().read() != -1) + Runtime.getRuntime().exec(new String[] { browser = b, url }); + if (browser == null) + throw new Exception(Arrays.toString(browsers)); } catch (Exception e) { PlatformUI.MIRTH_FRAME.alertThrowable(PlatformUI.MIRTH_FRAME, e); } diff --git a/client/src/main/java/com/mirth/connect/client/ui/Mirth.java b/client/src/main/java/com/mirth/connect/client/ui/Mirth.java index 0145252b6f..0dca370135 100644 --- a/client/src/main/java/com/mirth/connect/client/ui/Mirth.java +++ b/client/src/main/java/com/mirth/connect/client/ui/Mirth.java @@ -175,9 +175,20 @@ public static void initUIManager() { createAlternateKeyBindings(); } - if (SystemUtils.IS_OS_MAC) { - OSXAdapter.setAboutHandler(Mirth.class, Mirth.class.getDeclaredMethod("aboutMac", (Class[]) null)); - OSXAdapter.setQuitHandler(Mirth.class, Mirth.class.getDeclaredMethod("quitMac", (Class[]) null)); + if (Desktop.isDesktopSupported()) { + Desktop desktop = Desktop.getDesktop(); + if (desktop.isSupported(Desktop.Action.APP_ABOUT)) { + desktop.setAboutHandler(e -> aboutMac()); + } + if (desktop.isSupported(Desktop.Action.APP_QUIT_HANDLER)) { + desktop.setQuitHandler((e, response) -> { + if (quitMac()) { + response.performQuit(); + } else { + response.cancelQuit(); + } + }); + } } } catch (Exception e) { LogManager.getLogger(Mirth.class).error("Error initializing look and feel", e); diff --git a/client/src/main/java/com/mirth/connect/client/ui/OSXAdapter.java b/client/src/main/java/com/mirth/connect/client/ui/OSXAdapter.java deleted file mode 100644 index 403ab3f3e0..0000000000 --- a/client/src/main/java/com/mirth/connect/client/ui/OSXAdapter.java +++ /dev/null @@ -1,302 +0,0 @@ -/* - * - * File: OSXAdapter.java - * - * Abstract: Hooks existing preferences/about/quit functionality from an existing Java app into - * handlers for the Mac OS X application menu. Uses a Proxy object to dynamically implement the - * com.apple.eawt.ApplicationListener interface and register it with the com.apple.eawt.Application - * object. This allows the complete project to be both built and run on any platform without any - * stubs or placeholders. Useful for developers looking to implement Mac OS X features while - * supporting multiple platforms with minimal impact. - * - * Version: 2.0 - * - * Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Inc. ("Apple") in - * consideration of your agreement to the following terms, and your use, installation, modification - * or redistribution of this Apple software constitutes acceptance of these terms. If you do not - * agree with these terms, please do not use, install, modify or redistribute this Apple software. - * - * In consideration of your agreement to abide by the following terms, and subject to these terms, - * Apple grants you a personal, non-exclusive license, under Apple's copyrights in this original - * Apple software (the "Apple Software"), to use, reproduce, modify and redistribute the Apple - * Software, with or without modifications, in source and/or binary forms; provided that if you - * redistribute the Apple Software in its entirety and without modifications, you must retain this - * notice and the following text and disclaimers in all such redistributions of the Apple Software. - * Neither the name, trademarks, service marks or logos of Apple Inc. may be used to endorse or - * promote products derived from the Apple Software without specific prior written permission from - * Apple. Except as expressly stated in this notice, no other rights or licenses, express or - * implied, are granted by Apple herein, including but not limited to any patent rights that may be - * infringed by your derivative works or by other works in which the Apple Software may be - * incorporated. - * - * The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO WARRANTIES, EXPRESS - * OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND - * OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. - * - * IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, - * OR PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, - * MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY - * OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * Copyright (c) 2003-2007 Apple, Inc., All Rights Reserved - * - */ - -package com.mirth.connect.client.ui; - -import java.io.File; -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Proxy; -import java.security.AccessController; -import java.util.List; - -import org.apache.commons.lang3.math.NumberUtils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.glassfish.jersey.internal.util.ReflectionHelper; - -public class OSXAdapter implements InvocationHandler { - private static Logger logger = LogManager.getLogger(OSXAdapter.class); - - protected Object targetObject; - protected Method targetMethod; - protected String proxySignature; - - static Object macOSXApplication; - - // Pass this method an Object and Method equipped to perform application shutdown logic - // The method passed should return a boolean stating whether or not the quit should occur - public static void setQuitHandler(final Object target, final Method quitHandler) { - try { - if (isJava9OrGreater()) { - InvocationHandler invocationHandler = new InvocationHandler() { - @Override - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - if (method.getName().equals("handleQuitRequestWith")) { - boolean quit = (boolean) quitHandler.invoke(target); - Object quitResponse = args[1]; - quitResponse.getClass().getMethod(quit ? "performQuit" : "cancelQuit").invoke(quitResponse); - } - return null; - } - }; - - setHandler(invocationHandler, "java.awt.desktop.QuitHandler", "setQuitHandler"); - } else { - setHandler(new OSXAdapter("handleQuit", target, quitHandler)); - } - } catch (Exception ex) { - logger.error("OSXAdapter could not set quit handler", ex); - } - } - - // Pass this method an Object and Method equipped to display application info - // They will be called when the About menu item is selected from the application menu - public static void setAboutHandler(Object target, Method aboutHandler) { - boolean enableAboutMenu = (target != null && aboutHandler != null); - - try { - if (isJava9OrGreater()) { - if (enableAboutMenu) { - setHandler(new OSXAdapter("handleAbout", target, aboutHandler), "java.awt.desktop.AboutHandler", "setAboutHandler"); - } - } else { - if (enableAboutMenu) { - setHandler(new OSXAdapter("handleAbout", target, aboutHandler)); - } - // If we're setting a handler, enable the About menu item by calling - // com.apple.eawt.Application reflectively - Method enableAboutMethod = macOSXApplication.getClass().getDeclaredMethod("setEnabledAboutMenu", new Class[] { - boolean.class }); - enableAboutMethod.invoke(macOSXApplication, new Object[] { - Boolean.valueOf(enableAboutMenu) }); - } - } catch (Exception ex) { - logger.error("OSXAdapter could not access the About Menu", ex); - } - } - - // Pass this method an Object and a Method equipped to display application options - // They will be called when the Preferences menu item is selected from the application menu - public static void setPreferencesHandler(Object target, Method prefsHandler) { - boolean enablePrefsMenu = (target != null && prefsHandler != null); - try { - if (isJava9OrGreater()) { - if (enablePrefsMenu) { - setHandler(new OSXAdapter("handlePreferences", target, prefsHandler), "java.awt.desktop.PreferencesHandler", "setPreferencesHandler"); - } - } else { - if (enablePrefsMenu) { - setHandler(new OSXAdapter("handlePreferences", target, prefsHandler)); - } - // If we're setting a handler, enable the Preferences menu item by calling - // com.apple.eawt.Application reflectively - Method enablePrefsMethod = macOSXApplication.getClass().getDeclaredMethod("setEnabledPreferencesMenu", new Class[] { - boolean.class }); - enablePrefsMethod.invoke(macOSXApplication, new Object[] { - Boolean.valueOf(enablePrefsMenu) }); - } - } catch (Exception ex) { - logger.error("OSXAdapter could not set preferences handler", ex); - } - } - - // Pass this method an Object and a Method equipped to handle document events from the Finder - // Documents are registered with the Finder via the CFBundleDocumentTypes dictionary in the - // application bundle's Info.plist - public static void setFileHandler(final Object target, final Method fileHandler) { - try { - if (isJava9OrGreater()) { - InvocationHandler invocationHandler = new InvocationHandler() { - @Override - @SuppressWarnings("unchecked") - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - if (method.getName().equals("openFiles")) { - Object event = args[0]; - List files = (List) event.getClass().getMethod("getFiles").invoke(event); - String searchTerm = (String) event.getClass().getMethod("getSearchTerm").invoke(event); - fileHandler.invoke(target, files, searchTerm); - } - return null; - } - }; - - setHandler(invocationHandler, "java.awt.desktop.OpenFilesHandler", "setOpenFileHandler"); - } else { - setHandler(new OSXAdapter("handleOpenFile", target, fileHandler) { - // Override OSXAdapter.callTarget to send information on the - // file to be opened - public boolean callTarget(Object appleEvent) { - if (appleEvent != null) { - try { - Method getFilenameMethod = appleEvent.getClass().getDeclaredMethod("getFilename", (Class[]) null); - String filename = (String) getFilenameMethod.invoke(appleEvent, (Object[]) null); - this.targetMethod.invoke(this.targetObject, new Object[] { - filename }); - } catch (Exception ex) { - - } - } - return true; - } - }); - } - } catch (Exception ex) { - logger.error("OSXAdapter could not set open files handler", ex); - } - } - - // setHandler creates a Proxy object from the passed OSXAdapter and adds it as an ApplicationListener - public static void setHandler(OSXAdapter adapter) { - try { - Object application = getApplication(); - Class applicationListenerClass = Class.forName("com.apple.eawt.ApplicationListener"); - Method addListenerMethod = application.getClass().getDeclaredMethod("addApplicationListener", new Class[] { - applicationListenerClass }); - // Create a proxy object around this handler that can be reflectively added as an Apple ApplicationListener - Object osxAdapterProxy = Proxy.newProxyInstance(OSXAdapter.class.getClassLoader(), new Class[] { - applicationListenerClass }, adapter); - addListenerMethod.invoke(application, new Object[] { osxAdapterProxy }); - } catch (ClassNotFoundException cnfe) { - System.err.println("This version of Mac OS X does not support the Apple EAWT. ApplicationEvent handling has been disabled (" + cnfe + ")"); - } catch (Exception ex) { // Likely a NoSuchMethodException or an IllegalAccessException loading/invoking eawt.Application methods - logger.error("Mac OS X Adapter could not talk to EAWT", ex); - } - } - - // Each OSXAdapter has the name of the EAWT method it intends to listen for (handleAbout, for example), - // the Object that will ultimately perform the task, and the Method to be called on that Object - protected OSXAdapter(String proxySignature, Object target, Method handler) { - this.proxySignature = proxySignature; - this.targetObject = target; - this.targetMethod = handler; - } - - // Override this method to perform any operations on the event - // that comes with the various callbacks - // See setFileHandler above for an example - public boolean callTarget(Object appleEvent) throws InvocationTargetException, IllegalAccessException { - Object result = targetMethod.invoke(targetObject, (Object[]) null); - if (result == null) { - return true; - } - return Boolean.valueOf(result.toString()).booleanValue(); - } - - // InvocationHandler implementation - // This is the entry point for our proxy object; it is called every time an ApplicationListener method is invoked - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - if (isCorrectMethod(method, args)) { - boolean handled = callTarget(args[0]); - setApplicationEventHandled(args[0], handled); - } - // All of the ApplicationListener methods are void; return null regardless of what happens - return null; - } - - // Compare the method that was called to the intended method when the OSXAdapter instance was created - // (e.g. handleAbout, handleQuit, handleOpenFile, etc.) - protected boolean isCorrectMethod(Method method, Object[] args) { - return (targetMethod != null && proxySignature.equals(method.getName()) && args.length == 1); - } - - // It is important to mark the ApplicationEvent as handled and cancel the default behavior - // This method checks for a boolean result from the proxy method and sets the event accordingly - protected void setApplicationEventHandled(Object event, boolean handled) { - if (event != null && !isJava9OrGreater()) { - try { - Method setHandledMethod = event.getClass().getDeclaredMethod("setHandled", new Class[] { - boolean.class }); - // If the target method returns a boolean, use that as a hint - setHandledMethod.invoke(event, new Object[] { Boolean.valueOf(handled) }); - } catch (Exception ex) { - logger.error("OSXAdapter was unable to handle an ApplicationEvent: " + event, ex); - } - } - } - - @SuppressWarnings("unchecked") - protected static Object getApplication() throws Exception { - if (macOSXApplication == null) { - Class applicationClass = Class.forName("com.apple.eawt.Application"); - - if (isJava9OrGreater()) { - macOSXApplication = applicationClass.getMethod("getApplication").invoke(null); - } else { - macOSXApplication = applicationClass.getConstructor((Class[]) null).newInstance((Object[]) null); - } - } - - return macOSXApplication; - } - - @SuppressWarnings("removal") - protected static void setHandler(InvocationHandler adapter, String interfaceName, String applicationSetter) throws Exception { - Class handlerInterface = Class.forName(interfaceName); - Object handlerImpl = Proxy.newProxyInstance(AccessController.doPrivileged(ReflectionHelper.getClassLoaderPA(handlerInterface)), new Class[] { - handlerInterface }, adapter); - Object application = getApplication(); - application.getClass().getMethod(applicationSetter, handlerInterface).invoke(application, handlerImpl); - } - - protected static boolean isJava9OrGreater() { - String version = System.getProperty("java.version"); - - int index = version.indexOf('-'); - if (index > 0) { - version = version.substring(0, index); - } - - index = version.indexOf('.'); - if (index > 0) { - version = version.substring(0, index); - } - - return NumberUtils.toDouble(version) >= 9; - } -} \ No newline at end of file diff --git a/server/mirth-client.jnlp b/server/mirth-client.jnlp index 21ebd24c74..4a5754db37 100644 --- a/server/mirth-client.jnlp +++ b/server/mirth-client.jnlp @@ -26,7 +26,7 @@ - + diff --git a/server/src/test/java/com/mirth/connect/server/servlets/WebStartServletTest.java b/server/src/test/java/com/mirth/connect/server/servlets/WebStartServletTest.java index 25945adcbf..42d40e02d2 100644 --- a/server/src/test/java/com/mirth/connect/server/servlets/WebStartServletTest.java +++ b/server/src/test/java/com/mirth/connect/server/servlets/WebStartServletTest.java @@ -462,7 +462,7 @@ private static String normalizeWhitespace(String input) { + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" - + " \n" + + " \n" + " \n" + " \n" + " \n"