1 /*
   2  * Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package org.netbeans.mobilecenter;
  27 
  28 import com.sun.javafx.appmanager.FxApplicationInstance;
  29 import com.sun.javafx.appmanager.FxApplicationManager;
  30 import java.io.ByteArrayOutputStream;
  31 import java.io.File;
  32 import java.io.FileOutputStream;
  33 import java.io.IOException;
  34 import java.io.InputStream;
  35 import java.io.ObjectInputStream;
  36 import java.io.OutputStream;
  37 import java.io.PrintStream;
  38 import java.lang.reflect.Method;
  39 import java.net.DatagramPacket;
  40 import java.net.InetAddress;
  41 import java.net.MulticastSocket;
  42 import java.net.ServerSocket;
  43 import java.net.Socket;
  44 import java.net.URL;
  45 import java.net.URLClassLoader;
  46 import java.util.Enumeration;
  47 import java.util.jar.Manifest;
  48 
  49 final class BackgroundService {
  50 
  51     public static final String GROUP_ADDRESS = "225.10.10.0";
  52     public static final int PORT = 44445;
  53     public static final int PORT_FILE = 44446;
  54     public static final int PACKET_LENGTH = 1024;
  55     public static final String MULTICAST_DISCOVERY = "NetBeansMulticastDiscovery";
  56 
  57     private ThreadGroup threadGroup;
  58 
  59     public void loop() {
  60         boolean firstRun = true;
  61         try (MulticastSocket socket = new MulticastSocket(PORT)) {
  62             InetAddress group = InetAddress.getByName(GROUP_ADDRESS);
  63             socket.joinGroup(group);
  64 
  65             while (true) {
  66                 String appJarPath = null;
  67 
  68                 if (!firstRun) {
  69                     DatagramPacket packet;
  70                     byte[] buf = new byte[PACKET_LENGTH];
  71                     packet = new DatagramPacket(buf, buf.length);
  72                     socket.receive(packet);
  73 
  74                     String received = new String(packet.getData());
  75                     if (!received.trim().equals(MULTICAST_DISCOVERY)) {
  76                         break;
  77                     }
  78 
  79                     buf = String.valueOf(PORT_FILE).getBytes();
  80                     packet = new DatagramPacket(buf, buf.length, packet.getSocketAddress());
  81                     socket.send(packet);
  82                     appJarPath = receiveFile();
  83                 } else {
  84                     firstRun = false;
  85                 }
  86 
  87                 final String filePath = appJarPath;
  88                 stopThreadGroup();
  89                 threadGroup = new ThreadGroup("Mobile Center Group");
  90                 synchronized (threadGroup) {
  91                     new Thread(threadGroup, new Runnable() {
  92                         @Override
  93                         public void run() {
  94                             try {
  95                                 invokeMainMethod(filePath);
  96                             } catch (ThreadDeath td) {
  97                                 throw td;
  98                             } catch (Throwable t) {
  99                                 t.printStackTrace();
 100                             }
 101                         }
 102                     }, "Mobile Center Main").start();
 103                 }
 104             }
 105             socket.leaveGroup(group);
 106             System.exit(0);
 107         } catch (IOException ex) {
 108             System.err.println(ex.getMessage());
 109         }
 110     }
 111 
 112     private String receiveFile() {
 113         try (ServerSocket serverSocket = new ServerSocket(PORT_FILE);
 114                 Socket socket = serverSocket.accept();
 115                 ObjectInputStream ois = new ObjectInputStream(socket.getInputStream())) {
 116             Object receivedObject = ois.readObject();
 117             if (receivedObject instanceof byte[]) {
 118                 File targetFile = File.createTempFile("Debug", ".jar");
 119                 byte[] buffer = (byte[]) receivedObject;
 120                 try (OutputStream out = new FileOutputStream(targetFile)) {
 121                     out.write(buffer);
 122                 }
 123                 return targetFile.getAbsolutePath();
 124             }
 125             return null;
 126         } catch (IOException | ClassNotFoundException ex) {
 127             System.out.println(ex.getMessage());
 128             return null;
 129         }
 130     }
 131 
 132     private FxApplicationInstance lastFxApplication;
 133 
 134     public void invokeMainMethod(String jarPath) {
 135         try {
 136             ClassLoader sysClassLoader = ClassLoader.getSystemClassLoader();
 137             URL[] cpURLs = ((URLClassLoader) sysClassLoader).getURLs();
 138             URL[] classLoaderURLs = new URL[cpURLs.length];
 139             System.arraycopy(cpURLs, 0, classLoaderURLs, 0, cpURLs.length);
 140 
 141             String appJarName = System.getProperty("bgReloadJar");
 142             String jarWithManifest = appJarName;
 143             if (jarPath != null) {
 144                 URL appURL = new File(jarPath).toURI().toURL();
 145                 for (int i = 0; i < classLoaderURLs.length; i++) {
 146                     if (classLoaderURLs[i].getPath().endsWith(appJarName)) {
 147                         classLoaderURLs[i] = appURL;
 148                     }
 149                 }
 150                 jarWithManifest = new File(jarPath).getName();
 151             }
 152 
 153             URLClassLoader classLoader = URLClassLoader.newInstance(classLoaderURLs, ClassLoader.getSystemClassLoader().getParent());
 154 
 155             boolean fxEnabled = isFXApplication(classLoader, jarWithManifest);
 156 
 157             if (lastFxApplication != null) {
 158                 lastFxApplication.stop();
 159                 lastFxApplication = null;
 160             }
 161 
 162             if (fxEnabled) {
 163                 final FxApplicationManager appManager =
 164                         FxApplicationManager.getInstance();
 165                 final String appClass =
 166                         findMainClass(classLoader, jarWithManifest);
 167                 lastFxApplication = appManager.start(classLoader, appClass);
 168             } else {
 169                 Thread.currentThread().setContextClassLoader(classLoader);
 170 
 171                 Class clazz = classLoader.loadClass(findMainClass(classLoader, jarWithManifest));
 172                 Method method = clazz.getDeclaredMethod("main", String[].class);
 173                 method.setAccessible(true);
 174                 String[] params = null;
 175                 OutputStream os = new ByteArrayOutputStream();
 176                 System.setOut(new PrintStream(os));
 177                 method.invoke(classLoader, (Object) params);
 178             }
 179         } catch (Exception ex) {
 180             System.err.println(ex.getClass().getName() + ": " + ex.getMessage());
 181         }
 182     }
 183 
 184     private void stopThreadGroup() {
 185         if (threadGroup == null) {
 186             return;
 187         }
 188         synchronized (threadGroup) {
 189             int count = threadGroup.activeCount();
 190             Thread[] threads = new Thread[count];
 191             threadGroup.enumerate(threads, true);
 192             for (Thread t : threads) {
 193                 t.stop();
 194             }
 195         }
 196     }
 197 
 198     private static String findMainClass(final ClassLoader cl, String jarWithManifest) throws IOException {
 199         String mainClass = null;
 200         Enumeration<URL> en = cl.getResources("META-INF/MANIFEST.MF");
 201         while (en.hasMoreElements()) {
 202             URL url = en.nextElement();
 203             Manifest mf;
 204             try (InputStream is = url.openStream()) {
 205                 mf = new Manifest(is);
 206             }
 207             String mc = mf.getMainAttributes().getValue("JavaFX-Application-Class");
 208             if (mc == null || mc.isEmpty()) {
 209                 mc = mf.getMainAttributes().getValue("Main-Class");
 210             }
 211             if (mc != null && url.getPath().contains(jarWithManifest)) {
 212                 mainClass = mc;
 213                 break;
 214             }
 215         }
 216         return mainClass;
 217     }
 218 
 219     private static boolean isFXApplication(final ClassLoader cl, String jarWithManifest) throws IOException {
 220         boolean fxApp = false;
 221         Enumeration<URL> en = cl.getResources("META-INF/MANIFEST.MF");
 222         while (en.hasMoreElements()) {
 223             URL url = en.nextElement();
 224             Manifest mf;
 225             try (InputStream is = url.openStream()) {
 226                 mf = new Manifest(is);
 227             }
 228             String mc = mf.getMainAttributes().getValue("JavaFX-Version");
 229             if (mc != null && url.getPath().contains(jarWithManifest)) {
 230                 fxApp = true;
 231                 break;
 232             }
 233         }
 234         return fxApp;
 235     }
 236 }