content: /*
   * Copyright (c) 2011, 2016, Oracle and/or its affiliates. All rights reserved.
   * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   *
   * This code is free software; you can redistribute it and/or modify it
   * under the terms of the GNU General Public License version 2 only, as
   * published by the Free Software Foundation.  Oracle designates this
   * particular file as subject to the "Classpath" exception as provided
   * by Oracle in the LICENSE file that accompanied this code.
   *
   * This code is distributed in the hope that it will be useful, but WITHOUT
   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
   * version 2 for more details (a copy is included in the LICENSE file that
   * accompanied this code).
   *
   * You should have received a copy of the GNU General Public License version
   * 2 along with this work; if not, write to the Free Software Foundation,
   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
   *
   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
   * or visit www.oracle.com if you need additional information or have any
   * questions.
   */
  
  package openjdk.tools.sjavac.server;
  
  import java.io.FileNotFoundException;
  import java.io.FileWriter;
  import java.io.IOException;
  import java.io.PrintStream;
  import java.io.PrintWriter;
  import java.net.InetAddress;
  import java.net.InetSocketAddress;
  import java.net.ServerSocket;
  import java.net.Socket;
  import java.net.SocketException;
  import java.util.HashMap;
  import java.util.Map;
  import java.util.Random;
  import java.util.concurrent.atomic.AtomicBoolean;
  
  import openjdk.tools.javac.main.Main;
  import openjdk.tools.javac.main.Main.Result;
  import openjdk.tools.sjavac.Log;
  import openjdk.tools.sjavac.Util;
  import openjdk.tools.sjavac.client.PortFileInaccessibleException;
  import openjdk.tools.sjavac.comp.PooledSjavac;
  import openjdk.tools.sjavac.comp.SjavacImpl;
  
  /**
   * The JavacServer class contains methods both to setup a server that responds to requests and methods to connect to this server.
   *
   *  <p><b>This is NOT part of any supported API.
   *  If you write code that depends on this, you do so at your own risk.
   *  This code and its internal interfaces are subject to change or
   *  deletion without notice.</b>
   */
  public class SjavacServer implements Terminable {
  
      // Prefix of line containing return code.
      public final static String LINE_TYPE_RC = "RC";
  
      final private String portfilename;
      final private int poolsize;
      final private int keepalive;
  
      // The secret cookie shared between server and client through the port file.
      // Used to prevent clients from believing that they are communicating with
      // an old server when a new server has started and reused the same port as
      // an old server.
      private final long myCookie;
  
      // Accumulated build time, not counting idle time, used for logging purposes
      private long totalBuildTime;
  
      // The sjavac implementation to delegate requests to
      Sjavac sjavac;
  
      private ServerSocket serverSocket;
  
      private PortFile portFile;
      private PortFileMonitor portFileMonitor;
  
      // Set to false break accept loop
      final AtomicBoolean keepAcceptingRequests = new AtomicBoolean();
  
      // For the client, all port files fetched, one per started javac server.
      // Though usually only one javac server is started by a client.
      private static Map<String, PortFile> allPortFiles;
  
      public SjavacServer(String settings) throws FileNotFoundException {
          this(Util.extractStringOption("portfile", settings),
               Util.extractIntOption("poolsize", settings, Runtime.getRuntime().availableProcessors()),
               Util.extractIntOption("keepalive", settings, 120));
      }
  
      public SjavacServer(String portfilename,
                          int poolsize,
                          int keepalive)
                                  throws FileNotFoundException {
          this.portfilename = portfilename;
          this.poolsize = poolsize;
          this.keepalive = keepalive;
          this.myCookie = new Random().nextLong();
      }
  
  
      /**
       * Acquire the port file. Synchronized since several threads inside an smart javac wrapper client acquires the same port file at the same time.
       */
      public static synchronized PortFile getPortFile(String filename) {
          if (allPortFiles == null) {
              allPortFiles = new HashMap<>();
          }
          PortFile pf = allPortFiles.get(filename);
  
          // Port file known. Does it still exist?
          if (pf != null) {
              try {
                  if (!pf.exists())
                      pf = null;
              } catch (IOException ioex) {
                  ioex.printStackTrace();
              }
          }
  
          if (pf == null) {
              pf = new PortFile(filename);
              allPortFiles.put(filename, pf);
          }
          return pf;
      }
  
      /**
       * Get the cookie used for this server.
       */
      long getCookie() {
          return myCookie;
      }
  
      /**
       * Get the port used for this server.
       */
      int getPort() {
          return serverSocket.getLocalPort();
      }
  
      /**
       * Sum up the total build time for this javac server.
       */
      public void addBuildTime(long inc) {
          totalBuildTime += inc;
      }
  
      /**
       * Start a server using a settings string. Typically: "--startserver:portfile=/tmp/myserver,poolsize=3" and the string "portfile=/tmp/myserver,poolsize=3"
       * is sent as the settings parameter. Returns 0 on success, -1 on failure.
       */
      public int startServer() throws IOException, InterruptedException {
          long serverStart = System.currentTimeMillis();
  
          // The port file is locked and the server port and cookie is written into it.
          portFile = getPortFile(portfilename);
  
          synchronized (portFile) {
              portFile.lock();
              portFile.getValues();
              if (portFile.containsPortInfo()) {
                  Log.debug("Javac server not started because portfile exists!");
                  portFile.unlock();
                  return Result.ERROR.exitCode;
              }
  
              //           .-----------.   .--------.   .------.
              // socket -->| IdleReset |-->| Pooled |-->| Impl |--> javac
              //           '-----------'   '--------'   '------'
              sjavac = new SjavacImpl();
              sjavac = new PooledSjavac(sjavac, poolsize);
              sjavac = new IdleResetSjavac(sjavac,
                                           this,
                                           keepalive * 1000);
  
              serverSocket = new ServerSocket();
              InetAddress localhost = InetAddress.getByName(null);
              serverSocket.bind(new InetSocketAddress(localhost, 0));
  
              // At this point the server accepts connections, so it is  now safe
              // to publish the port / cookie information
              portFile.setValues(getPort(), getCookie());
              portFile.unlock();
          }
  
          portFileMonitor = new PortFileMonitor(portFile, this);
          portFileMonitor.start();
  
          Log.debug("Sjavac server started. Accepting connections...");
          Log.debug("    port: " + getPort());
          Log.debug("    time: " + new java.util.Date());
          Log.debug("    poolsize: " + poolsize);
  
  
          keepAcceptingRequests.set(true);
          do {
              try {
                  Socket socket = serverSocket.accept();
                  new RequestHandler(socket, sjavac).start();
              } catch (SocketException se) {
                  // Caused by serverSocket.close() and indicates shutdown
              }
          } while (keepAcceptingRequests.get());
  
          Log.debug("Shutting down.");
  
          // No more connections accepted. If any client managed to connect after
          // the accept() was interrupted but before the server socket is closed
          // here, any attempt to read or write to the socket will result in an
          // IOException on the client side.
  
          long realTime = System.currentTimeMillis() - serverStart;
          Log.debug("Total wall clock time " + realTime + "ms build time " + totalBuildTime + "ms");
  
          // Shut down
          sjavac.shutdown();
  
          return Result.OK.exitCode;
      }
  
      @Override
      public void shutdown(String quitMsg) {
          if (!keepAcceptingRequests.compareAndSet(true, false)) {
              // Already stopped, no need to shut down again
              return;
          }
  
          Log.debug("Quitting: " + quitMsg);
  
          portFileMonitor.shutdown(); // No longer any need to monitor port file
  
          // Unpublish port before shutting down socket to minimize the number of
          // failed connection attempts
          try {
              portFile.delete();
          } catch (IOException | InterruptedException e) {
              Log.error(e);
          }
          try {
              serverSocket.close();
          } catch (IOException e) {
              Log.error(e);
          }
      }
  }
  