Skip to content

Commit

Permalink
Improved: Move ‘AdminServer’ inside a container
Browse files Browse the repository at this point in the history
(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 aa67827
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 69 deletions.
2 changes: 2 additions & 0 deletions framework/base/ofbiz-component.xml
Expand Up @@ -32,5 +32,7 @@ under the License.
<property name="host" value="0.0.0.0"/> <property name="host" value="0.0.0.0"/>
<property name="port" value="1099"/> <property name="port" value="1099"/>
</container> </container>
<!-- load the administration server -->
<container name="admin-container" loaders="main" class="org.apache.ofbiz.base.container.AdminServerContainer"/>


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


import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
Expand All @@ -26,80 +26,98 @@
import java.net.ServerSocket; import java.net.ServerSocket;
import java.net.Socket; import java.net.Socket;
import java.nio.charset.StandardCharsets; 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.Start.ServerState;
import org.apache.ofbiz.base.start.StartupCommand;
import org.apache.ofbiz.base.util.UtilValidate; import org.apache.ofbiz.base.util.UtilValidate;


/** /**
* The AdminServer provides a way to communicate with a running * The AdminServer provides a way to communicate with a running
* OFBiz instance after it has started and send commands to that instance * OFBiz instance after it has started and send commands to that instance
* such as inquiring on server status or requesting system shutdown * 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 * Commands communicated between AdminClient and AdminServer
*/ */
enum OfbizSocketCommand { public enum OfbizSocketCommand {
SHUTDOWN, STATUS, FAIL SHUTDOWN, STATUS, FAIL
} }


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


AdminServer(ContainerLoader loader, AtomicReference<ServerState> serverState, Config config) throws StartupException { @Override
super("OFBiz-AdminServer"); public void init(List<StartupCommand> ofbizCommands, String name, String configFile) throws ContainerException {
this.name = name;
try { try {
this.serverSocket = new ServerSocket(config.adminPort, 1, config.adminAddress); serverSocket = new ServerSocket(cfg.adminPort, 1, cfg.adminAddress);
} catch (IOException e) { } 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; if (cfg.adminPort > 0) {
this.serverState = serverState; serverThread = new Thread(this::run, "OFBiz-AdminServer");
this.config = config; } else {
serverThread = new Thread("OFBiz-AdminServer"); // Dummy thread
System.out.println("Admin socket not configured; set to port 0");
}
serverThread.setDaemon(false);
} }


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

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

processClientRequest(clientSocket, loader, serverState);

} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
} }
} }
} }


private void processClientRequest(Socket client, ContainerLoader loader, AtomicReference<ServerState> serverState) @Override
throws IOException { 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)); 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 // read client request and prepare response
String clientRequest = reader.readLine(); String clientRequest = reader.readLine();
OfbizSocketCommand clientCommand = determineClientCommand(clientRequest); OfbizSocketCommand clientCommand = determineClientCommand(clientRequest);
String serverResponse = prepareResponseToClient(clientCommand, serverState); String serverResponse = prepareResponseToClient(clientCommand);


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


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


private static String prepareResponseToClient(OfbizSocketCommand control, AtomicReference<ServerState> serverState) {
private static String prepareResponseToClient(OfbizSocketCommand control) {
String response = null; String response = null;
ServerState state = Start.getInstance().getCurrentState();
switch(control) { switch(control) {
case SHUTDOWN: case SHUTDOWN:
if (serverState.get() == ServerState.STOPPING) { if (state == ServerState.STOPPING) {
response = "IN-PROGRESS"; response = "IN-PROGRESS";
} else { } else {
response = "OK"; response = "OK";
} }
break; break;
case STATUS: case STATUS:
response = serverState.get().toString(); response = state.toString();
break; break;
case FAIL: case FAIL:
response = "FAIL"; response = "FAIL";
Expand Down
Expand Up @@ -27,7 +27,7 @@
import java.net.Socket; import java.net.Socket;
import java.nio.charset.StandardCharsets; 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 * The AdminClient communicates with a running OFBiz server instance
Expand Down
Expand Up @@ -24,6 +24,8 @@
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors; import java.util.stream.Collectors;


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

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


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


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


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

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

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

createLogDirectoryIfMissing(config.logDir); createLogDirectoryIfMissing(config.logDir);


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


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


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


private static void loadGlobalOfbizSystemProperties(String globalOfbizPropertiesFileName) throws StartupException { private static void loadGlobalOfbizSystemProperties(String globalOfbizPropertiesFileName) throws StartupException {
Expand All @@ -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) { private static void createLogDirectoryIfMissing(String logDirName) {
File logDir = new File(logDirName); File logDir = new File(logDirName);
if (!logDir.exists()) { if (!logDir.exists()) {
Expand All @@ -167,7 +144,7 @@ private static void createRuntimeShutdownHook(ContainerLoader loader, AtomicRefe
Runtime.getRuntime().addShutdownHook(new Thread() { Runtime.getRuntime().addShutdownHook(new Thread() {
@Override @Override
public void run() { public void run() {
shutdownServer(loader, serverState, this); shutdownServer(loader, serverState);
} }
}); });
} }
Expand Down

0 comments on commit aa67827

Please sign in to comment.