Skip to content
Permalink
Browse files

Improved: Move ‘AdminServer’ inside a container

(OFBIZ-11136)

‘AdminServer’ provides a portable way to manage life-cycle of the OFBiz
process remotely by allowing administrator to check its running status
or shutting it down.

Previously the ‘AdminServer’ class was a special thread opening a
socket and launched at startup.  However since this class is about
managing some run-time resources with a life-cycle, it matches perfectly
the container abstraction.

A benefit of making ‘AdminServer’ a container is that the startup
process is now simpler and more uniform.

Administrators can now prevent remote shutdown of OFBiz for security
reasons by removing the container declaration.  Additionally They can
delegate the process management job to the init process (PID 0) of the
hosting system like Systemd [1] by replacing this container with
another one.

[1] https://www.freedesktop.org/software/systemd/man/systemd-notify.html


git-svn-id: https://svn.apache.org/repos/asf/ofbiz/ofbiz-framework/trunk@1863024 13f79535-47bb-0310-9956-ffa450edef68
  • Loading branch information
mthl committed Jul 13, 2019
1 parent 978ad32 commit aa67827feba9c57fc5250580f7c07b709ec5a289
@@ -32,5 +32,7 @@ under the License.
<property name="host" value="0.0.0.0"/>
<property name="port" value="1099"/>
</container>
<!-- load the administration server -->
<container name="admin-container" loaders="main" class="org.apache.ofbiz.base.container.AdminServerContainer"/>

</ofbiz-component>
@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*******************************************************************************/
package org.apache.ofbiz.base.start;
package org.apache.ofbiz.base.container;

import java.io.BufferedReader;
import java.io.IOException;
@@ -26,80 +26,98 @@
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.atomic.AtomicReference;
import java.util.List;

import org.apache.ofbiz.base.container.ContainerLoader;
import org.apache.ofbiz.base.start.Config;
import org.apache.ofbiz.base.start.Start;
import org.apache.ofbiz.base.start.Start.ServerState;
import org.apache.ofbiz.base.start.StartupCommand;
import org.apache.ofbiz.base.util.UtilValidate;

/**
* The AdminServer provides a way to communicate with a running
* OFBiz instance after it has started and send commands to that instance
* such as inquiring on server status or requesting system shutdown
*/
final class AdminServer extends Thread {

public final class AdminServerContainer implements Container {
/**
* Commands communicated between AdminClient and AdminServer
*/
enum OfbizSocketCommand {
public enum OfbizSocketCommand {
SHUTDOWN, STATUS, FAIL
}

private ServerSocket serverSocket = null;
private ContainerLoader loader;
private AtomicReference<ServerState> serverState = null;
private Config config = null;
private String name;
private Thread serverThread;
private ServerSocket serverSocket;
private Config cfg = Start.getInstance().getConfig();

AdminServer(ContainerLoader loader, AtomicReference<ServerState> serverState, Config config) throws StartupException {
super("OFBiz-AdminServer");
@Override
public void init(List<StartupCommand> ofbizCommands, String name, String configFile) throws ContainerException {
this.name = name;
try {
this.serverSocket = new ServerSocket(config.adminPort, 1, config.adminAddress);
serverSocket = new ServerSocket(cfg.adminPort, 1, cfg.adminAddress);
} catch (IOException e) {
throw new StartupException("Couldn't create server socket(" + config.adminAddress + ":" + config.adminPort + ")", e);
String msg = "Couldn't create server socket(" + cfg.adminAddress + ":" + cfg.adminPort + ")";
throw new ContainerException(msg, e);
}
setDaemon(false);
this.loader = loader;
this.serverState = serverState;
this.config = config;

if (cfg.adminPort > 0) {
serverThread = new Thread(this::run, "OFBiz-AdminServer");
} else {
serverThread = new Thread("OFBiz-AdminServer"); // Dummy thread
System.out.println("Admin socket not configured; set to port 0");
}
serverThread.setDaemon(false);
}

@Override
public void run() {
System.out.println("Admin socket configured on - " + config.adminAddress + ":" + config.adminPort);
// Listens for administration commands.
private void run() {
System.out.println("Admin socket configured on - " + cfg.adminAddress + ":" + cfg.adminPort);
while (!Thread.interrupted()) {
try (Socket clientSocket = serverSocket.accept()){

System.out.println("Received connection from - "
+ clientSocket.getInetAddress() + " : "
+ clientSocket.getPort());

processClientRequest(clientSocket, loader, serverState);

try (Socket client = serverSocket.accept()) {
System.out.println("Received connection from - " + client.getInetAddress() + " : " + client.getPort());
processClientRequest(client);
} catch (IOException e) {
e.printStackTrace();
}
}
}

private void processClientRequest(Socket client, ContainerLoader loader, AtomicReference<ServerState> serverState)
throws IOException {
@Override
public boolean start() throws ContainerException {
serverThread.start();
return true;
}

@Override
public void stop() throws ContainerException {
if (serverThread.isAlive()) {
serverThread.interrupt();
}
}

@Override
public String getName() {
return name;
}

private void processClientRequest(Socket client) throws IOException {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(client.getInputStream(), StandardCharsets.UTF_8));
PrintWriter writer = new PrintWriter(new OutputStreamWriter(client.getOutputStream(), StandardCharsets.UTF_8), true)) {
PrintWriter writer = new PrintWriter(new OutputStreamWriter(client.getOutputStream(), StandardCharsets.UTF_8), true)) {

// read client request and prepare response
String clientRequest = reader.readLine();
OfbizSocketCommand clientCommand = determineClientCommand(clientRequest);
String serverResponse = prepareResponseToClient(clientCommand, serverState);
String serverResponse = prepareResponseToClient(clientCommand);

// send response back to client
writer.println(serverResponse);

// if the client request is shutdown, execute shutdown sequence
if(clientCommand.equals(OfbizSocketCommand.SHUTDOWN)) {
writer.flush();
StartupControlPanel.shutdownServer(loader, serverState, this);
System.exit(0);
Start.getInstance().stop();
}
}
}
@@ -119,22 +137,24 @@ private OfbizSocketCommand determineClientCommand(String request) {
private boolean isValidRequest(String request) {
return UtilValidate.isNotEmpty(request)
&& request.contains(":")
&& request.substring(0, request.indexOf(':')).equals(config.adminKey)
&& request.substring(0, request.indexOf(':')).equals(cfg.adminKey)
&& !request.substring(request.indexOf(':') + 1).isEmpty();
}

private static String prepareResponseToClient(OfbizSocketCommand control, AtomicReference<ServerState> serverState) {

private static String prepareResponseToClient(OfbizSocketCommand control) {
String response = null;
ServerState state = Start.getInstance().getCurrentState();
switch(control) {
case SHUTDOWN:
if (serverState.get() == ServerState.STOPPING) {
if (state == ServerState.STOPPING) {
response = "IN-PROGRESS";
} else {
response = "OK";
}
break;
case STATUS:
response = serverState.get().toString();
response = state.toString();
break;
case FAIL:
response = "FAIL";
@@ -27,7 +27,7 @@
import java.net.Socket;
import java.nio.charset.StandardCharsets;

import org.apache.ofbiz.base.start.AdminServer.OfbizSocketCommand;
import org.apache.ofbiz.base.container.AdminServerContainer.OfbizSocketCommand;

/**
* The AdminClient communicates with a running OFBiz server instance
@@ -24,6 +24,8 @@
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

import org.apache.ofbiz.base.container.ContainerLoader;

/**
* OFBiz startup class.
*
@@ -44,6 +46,7 @@
public final class Start {

private Config config = null;
private ContainerLoader loader = new ContainerLoader();
private final AtomicReference<ServerState> serverState = new AtomicReference<>(ServerState.STARTING);

// Singleton, do not change
@@ -84,7 +87,7 @@ public static void main(String[] args) {
break;
case START:
try {
StartupControlPanel.start(instance.config, instance.serverState, ofbizCommands);
StartupControlPanel.start(instance.config, instance.serverState, ofbizCommands, instance.loader);
} catch (StartupException e) {
StartupControlPanel.fullyTerminateSystem(e);
}
@@ -113,6 +116,11 @@ public ServerState getCurrentState() {
return serverState.get();
}

public void stop() {
StartupControlPanel.shutdownServer(loader, serverState);
System.exit(0);
}

/**
* This enum contains the possible OFBiz server states.
*/
@@ -58,13 +58,8 @@ static Config init(List<StartupCommand> ofbizCommands) {
/**
* Execute the startup sequence for OFBiz
*/
static void start(Config config,
AtomicReference<ServerState> serverState,
List<StartupCommand> ofbizCommands) throws StartupException {

ContainerLoader loader = new ContainerLoader();
Thread adminServer = createAdminServer(config, serverState, loader);

static void start(Config config, AtomicReference<ServerState> serverState, List<StartupCommand> ofbizCommands,
ContainerLoader loader) throws StartupException {
createLogDirectoryIfMissing(config.logDir);

if (config.useShutdownHook) {
@@ -76,7 +71,7 @@ static void start(Config config,
loadContainers(config, loader, ofbizCommands, serverState);

if (config.shutdownAfterLoad) {
shutdownServer(loader, serverState, adminServer);
shutdownServer(loader, serverState);
System.exit(0);
} else {
// Print startup message.
@@ -110,7 +105,7 @@ static void fullyTerminateSystem(StartupException e) {
System.exit(1);
}

static void shutdownServer(ContainerLoader loader, AtomicReference<ServerState> serverState, Thread adminServer) {
static void shutdownServer(ContainerLoader loader, AtomicReference<ServerState> serverState) {
ServerState currentState;
do {
currentState = serverState.get();
@@ -123,9 +118,6 @@ static void shutdownServer(ContainerLoader loader, AtomicReference<ServerState>
} catch (Exception e) {
e.printStackTrace();
}
if (adminServer != null && adminServer.isAlive()) {
adminServer.interrupt();
}
}

private static void loadGlobalOfbizSystemProperties(String globalOfbizPropertiesFileName) throws StartupException {
@@ -139,21 +131,6 @@ private static void loadGlobalOfbizSystemProperties(String globalOfbizProperties
}
}

private static Thread createAdminServer(
Config config,
AtomicReference<ServerState> serverState,
ContainerLoader loader) throws StartupException {

Thread adminServer = null;
if (config.adminPort > 0) {
adminServer = new AdminServer(loader, serverState, config);
adminServer.start();
} else {
System.out.println("Admin socket not configured; set to port 0");
}
return adminServer;
}

private static void createLogDirectoryIfMissing(String logDirName) {
File logDir = new File(logDirName);
if (!logDir.exists()) {
@@ -167,7 +144,7 @@ private static void createRuntimeShutdownHook(ContainerLoader loader, AtomicRefe
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
shutdownServer(loader, serverState, this);
shutdownServer(loader, serverState);
}
});
}

0 comments on commit aa67827

Please sign in to comment.
You can’t perform that action at this time.