blob: dabf03f00d61fff65dad975c92348c7bc3c41af3 [file] [log] [blame]
/*******************************************************************************
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*******************************************************************************/
package org.ofbiz.base.start;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.net.ConnectException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
/**
* OFBiz startup class.
*
*/
public final class Start {
/*
* This class implements a thread-safe state machine. The design is critical
* for reliable starting and stopping of the server.
*
* The machine's current state and state changes must be encapsulated in this
* class. Client code may query the current state, but it may not change it.
*
* This class uses a singleton pattern to guarantee that only one server instance
* is running in the VM. Client code retrieves the instance by using the getInstance()
* static method.
*
*/
private static final Start instance = new Start();
private static Command checkCommand(Command command, Command wanted) {
if (wanted == Command.HELP || wanted.equals(command)) {
return wanted;
} else if (command == null) {
return wanted;
} else {
System.err.println("Duplicate command detected(was " + command + ", wanted " + wanted);
return Command.HELP_ERROR;
}
}
/**
* Returns the <code>Start</code> instance.
*/
public static Start getInstance() {
return instance;
}
private static void help(PrintStream out) {
// Currently some commands have no dash, see OFBIZ-5872
out.println("");
out.println("Usage: java -jar ofbiz.jar [command] [arguments]");
out.println("both -----> Runs simultaneously the POS (Point of Sales) application and OFBiz standard");
out.println("-help, -? ----> This screen");
out.println("load-data -----> Creates tables/load data, eg: load-data -readers=seed,demo,ext -timeout=7200 -delegator=default -group=org.ofbiz. Or: load-data -file=/tmp/dataload.xml");
out.println("pos -----> Runs the POS (Point of Sales) application");
//out.println("-setup -------> Run external application server setup");
out.println("start -------> Starts the server");
out.println("-status ------> Gives the status of the server");
out.println("-shutdown ----> Shutdowns the server");
out.println("test --------> Runs the JUnit test script");
out.println("[no config] --> Uses default config");
out.println("[no command] -> Starts the server with default config");
}
public static void main(String[] args) throws StartupException {
Command command = null;
List<String> loaderArgs = new ArrayList<String>(args.length);
for (String arg : args) {
if (arg.equals("-help") || arg.equals("-?")) {
command = checkCommand(command, Command.HELP);
} else if (arg.equals("-status")) {
command = checkCommand(command, Command.STATUS);
} else if (arg.equals("-shutdown")) {
command = checkCommand(command, Command.SHUTDOWN);
} else if (arg.startsWith("-")) {
if (!arg.contains("portoffset")) {
command = checkCommand(command, Command.COMMAND);
}
loaderArgs.add(arg.substring(1));
} else {
command = checkCommand(command, Command.COMMAND);
if (command == Command.COMMAND) {
loaderArgs.add(arg);
} else {
command = Command.HELP_ERROR;
}
}
}
if (command == null) {
command = Command.COMMAND;
loaderArgs.add("start");
}
if (command == Command.HELP) {
help(System.out);
return;
} else if (command == Command.HELP_ERROR) {
help(System.err);
System.exit(1);
}
instance.init(args, command == Command.COMMAND);
try {
if (command == Command.STATUS) {
System.out.println("Current Status : " + instance.status());
} else if (command == Command.SHUTDOWN) {
System.out.println("Shutting down server : " + instance.shutdown());
} else {
// general start
instance.start();
}
} catch (Exception e) {
e.printStackTrace();
System.exit(99);
}
}
// ---------------------------------------------- //
private Config config = null;
private final List<String> loaderArgs = new ArrayList<String>();
private final ArrayList<StartupLoader> loaders = new ArrayList<StartupLoader>();
private final AtomicReference<ServerState> serverState = new AtomicReference<ServerState>(ServerState.STARTING);
private Thread adminPortThread = null;
// DO NOT CHANGE THIS!
private Start() {
}
private void createListenerThread() throws StartupException {
if (config.adminPort > 0) {
this.adminPortThread = new AdminPortThread();
this.adminPortThread.start();
} else {
System.out.println("Admin socket not configured; set to port 0");
}
}
private void createLogDirectory() {
File logDir = new File(config.logDir);
if (!logDir.exists()) {
if (logDir.mkdir()) {
System.out.println("Created OFBiz log dir [" + logDir.getAbsolutePath() + "]");
}
}
}
/**
* Returns the server's current state.
*/
public ServerState getCurrentState() {
return serverState.get();
}
void init(String[] args, boolean fullInit) throws StartupException {
String globalSystemPropsFileName = System.getProperty("ofbiz.system.props");
if (globalSystemPropsFileName != null) {
FileInputStream stream = null;
try {
stream = new FileInputStream(globalSystemPropsFileName);
System.getProperties().load(stream);
} catch (IOException e) {
throw (StartupException) new StartupException("Couldn't load global system props").initCause(e);
} finally {
if (stream != null) {
try {
stream.close();
} catch (IOException e) {
throw (StartupException) new StartupException("Couldn't close stream").initCause(e);
}
}
}
}
try {
this.config = new Config(args);
} catch (IOException e) {
throw (StartupException) new StartupException("Couldn't not fetch config instance").initCause(e);
}
// parse the startup arguments
if (args.length > 1) {
this.loaderArgs.addAll(Arrays.asList(args).subList(1, args.length));
// Needed when portoffset is used with these commands
try {
if ("status".equals(args[0])) {
System.out.println("Current Status : " + instance.status());
} else if ("stop".equals(args[0])) {
System.out.println("Shutting down server : " + instance.shutdown());
}
} catch (Exception e) {
e.printStackTrace();
System.exit(99);
}
}
if (!fullInit) {
return;
}
// create the log directory
createLogDirectory();
// create the listener thread
createListenerThread();
// set the shutdown hook
if (config.useShutdownHook) {
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
shutdownServer();
}
});
} else {
System.out.println("Shutdown hook disabled");
}
// initialize the startup loaders
initStartLoaders();
}
private void initStartLoaders() throws StartupException {
Classpath classPath = new Classpath();
Classpath libraryPath = new Classpath(System.getProperty("java.library.path"));
try {
this.config.initClasspath(classPath, libraryPath);
} catch (Exception e) {
throw (StartupException) new StartupException("Couldn't initialized classpath").initCause(e);
}
ClassLoader classloader = classPath.getClassLoader();
Thread.currentThread().setContextClassLoader(classloader);
synchronized (this.loaders) {
// initialize the loaders
for (Map<String, String> loaderMap : config.loaders) {
if (this.serverState.get() == ServerState.STOPPING) {
return;
}
try {
String loaderClassName = loaderMap.get("class");
Class<?> loaderClass = classloader.loadClass(loaderClassName);
StartupLoader loader = (StartupLoader) loaderClass.newInstance();
loader.load(config, loaderArgs.toArray(new String[loaderArgs.size()]));
loaders.add(loader);
} catch (ClassNotFoundException e) {
throw (StartupException) new StartupException(e.getMessage()).initCause(e);
} catch (InstantiationException e) {
throw (StartupException) new StartupException(e.getMessage()).initCause(e);
} catch (IllegalAccessException e) {
throw (StartupException) new StartupException(e.getMessage()).initCause(e);
}
}
this.loaders.trimToSize();
}
return;
}
private String sendSocketCommand(Control control) throws IOException, ConnectException {
String response = "OFBiz is Down";
try {
Socket socket = new Socket(config.adminAddress, config.adminPort);
// send the command
PrintWriter writer = new PrintWriter(socket.getOutputStream(), true);
writer.println(config.adminKey + ":" + control);
writer.flush();
// read the reply
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
response = reader.readLine();
reader.close();
// close the socket
writer.close();
socket.close();
} catch (ConnectException e) {
System.out.println("Could not connect to " + config.adminAddress + ":" + config.adminPort);
}
return response;
}
private String shutdown() throws IOException {
return sendSocketCommand(Control.SHUTDOWN);
}
void shutdownServer() {
ServerState currentState;
do {
currentState = this.serverState.get();
if (currentState == ServerState.STOPPING) {
return;
}
} while (!this.serverState.compareAndSet(currentState, ServerState.STOPPING));
// The current thread was the one that successfully changed the state;
// continue with further processing.
synchronized (this.loaders) {
// Unload in reverse order
for (int i = this.loaders.size(); i > 0; i--) {
StartupLoader loader = this.loaders.get(i - 1);
try {
loader.unload();
} catch (Exception e) {
e.printStackTrace();
}
}
}
if (this.adminPortThread != null && this.adminPortThread.isAlive()) {
this.adminPortThread.interrupt();
}
}
/**
* Returns <code>true</code> if all loaders were started.
*
* @return <code>true</code> if all loaders were started.
*/
boolean startStartLoaders() {
synchronized (this.loaders) {
// start the loaders
for (StartupLoader loader : this.loaders) {
if (this.serverState.get() == ServerState.STOPPING) {
return false;
}
try {
loader.start();
} catch (StartupException e) {
e.printStackTrace();
return false;
}
}
}
return this.serverState.compareAndSet(ServerState.STARTING, ServerState.RUNNING);
}
private String status() throws IOException {
try {
return sendSocketCommand(Control.STATUS);
} catch (ConnectException e) {
return "Not Running";
} catch (IOException e) {
throw e;
}
}
void stopServer() {
shutdownServer();
System.exit(0);
}
void start() throws Exception {
if (!startStartLoaders()) {
if (this.serverState.get() == ServerState.STOPPING) {
return;
} else {
throw new Exception("Error during start.");
}
}
if (config.shutdownAfterLoad) {
stopServer();
}
}
public Config getConfig() {
return this.config;
}
// ----------------------------------------------- //
private class AdminPortThread extends Thread {
private ServerSocket serverSocket = null;
AdminPortThread() throws StartupException {
super("OFBiz-AdminPortThread");
try {
this.serverSocket = new ServerSocket(config.adminPort, 1, config.adminAddress);
} catch (IOException e) {
throw (StartupException) new StartupException("Couldn't create server socket(" + config.adminAddress + ":" + config.adminPort + ")").initCause(e);
}
setDaemon(false);
}
private void processClientRequest(Socket client) throws IOException {
BufferedReader reader = null;
PrintWriter writer = null;
try {
reader = new BufferedReader(new InputStreamReader(client.getInputStream()));
String request = reader.readLine();
writer = new PrintWriter(client.getOutputStream(), true);
Control control;
if (request != null && !request.isEmpty() && request.contains(":")) {
String key = request.substring(0, request.indexOf(':'));
if (key.equals(config.adminKey)) {
control = Control.valueOf(request.substring(request.indexOf(':') + 1));
if (control == null) {
control = Control.FAIL;
}
} else {
control = Control.FAIL;
}
} else {
control = Control.FAIL;
}
control.processRequest(Start.this, writer);
} finally {
if (reader != null) {
reader.close();
}
if (writer != null) {
writer.flush();
writer.close();
}
}
}
@Override
public void run() {
System.out.println("Admin socket configured on - " + config.adminAddress + ":" + config.adminPort);
while (!Thread.interrupted()) {
try {
Socket clientSocket = serverSocket.accept();
System.out.println("Received connection from - " + clientSocket.getInetAddress() + " : " + clientSocket.getPort());
processClientRequest(clientSocket);
clientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
private enum Command {
HELP, HELP_ERROR, STATUS, SHUTDOWN, COMMAND
}
private enum Control {
SHUTDOWN {
@Override
void processRequest(Start start, PrintWriter writer) {
if (start.serverState.get() == ServerState.STOPPING) {
writer.println("IN-PROGRESS");
} else {
writer.println("OK");
writer.flush();
start.stopServer();
}
}
},
STATUS {
@Override
void processRequest(Start start, PrintWriter writer) {
writer.println(start.serverState.get());
}
},
FAIL {
@Override
void processRequest(Start start, PrintWriter writer) {
writer.println("FAIL");
}
};
abstract void processRequest(Start start, PrintWriter writer);
}
public enum ServerState {
STARTING, RUNNING, STOPPING;
@Override
public String toString() {
return name().charAt(0) + name().substring(1).toLowerCase();
}
}
}