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
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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();
}
}
}
Expand All @@ -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";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand All @@ -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
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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.
Expand Down Expand Up @@ -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();
Expand All @@ -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 {
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) {
File logDir = new File(logDirName);
if (!logDir.exists()) {
Expand All @@ -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);
}
});
}
Expand Down

0 comments on commit aa67827

Please sign in to comment.