SimpleServer.java

/*
Copyright (C) 2025 Steve Flasby

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or any later version.

This program 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 Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

package org.flasby.thymeleaf;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import lombok.extern.log4j.Log4j2;
import org.flasby.thymeleaf.Lifecycle.StartFailedException;

/**
 * listens on a port and waits for connections. Whene a conection arrives it creates a new Instance
 * and runs this in a new thread. The Instance is given the socket connection to the remote client
 * and a Reporter which it can use to report progress and problems.
 *
 * @author steve
 */
@Log4j2
public abstract class SimpleServer {

  private final int mPort;
  final ServerSocket ss;

  /** starts a simple server on any free port. The port can be found using getPort(). */
  protected SimpleServer() throws IOException {
    this(0);
  }

  /**
   * starts a simple server on the specified port.
   *
   * @param port
   */
  protected SimpleServer(int port) throws IOException {
    ss = new ServerSocket(port);
    mPort = ss.getLocalPort();
  }

  public void start() {
    starting();
    new Thread(
            new Runnable() {
              @Override
              public void run() {
                while (true) {
                  Socket s;
                  try {
                    s = ss.accept();
                  } catch (IOException e) {
                    continue;
                  }
                  final Instance i = create(s);
                  if (i == null) {
                    continue;
                  }
                  Thread.ofVirtual()
                      .start(
                          new Runnable() {
                            @Override
                            public void run() {
                              try {
                                i.start();
                              } catch (StartFailedException ex) {
                                LOG.warn(
                                    "Stopping server instance {} because: {} ", i.mInstanceId, ex);
                                i.stop();
                              }
                              stopped(i);
                            }
                          });
                }
              }
            })
        .start();
  }

  protected abstract Instance create(Socket connectionToClient);

  /**
   * called when an Instance is stopped.
   *
   * @param instance
   */
  protected void stopped(Instance instance) {
    LOG.debug("Stopping server instance {}", instance.mInstanceId);
    instance.stop();
  }

  protected void starting() {
    LOG.debug("Starting the server");
  }

  public int getPort() {
    return mPort;
  }
}