/*
* 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.apache.geode.distributed;
import static org.apache.geode.distributed.ConfigurationProperties.*;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.ServiceLoader;
import java.util.TreeMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import joptsimple.OptionException;
import joptsimple.OptionParser;
import joptsimple.OptionSet;
import org.apache.geode.SystemFailure;
import org.apache.geode.cache.Cache;
import org.apache.geode.cache.partition.PartitionRegionHelper;
import org.apache.geode.cache.server.CacheServer;
import org.apache.geode.distributed.internal.DefaultServerLauncherCacheProvider;
import org.apache.geode.distributed.internal.DistributionConfig;
import org.apache.geode.distributed.internal.InternalDistributedSystem;
import org.apache.geode.internal.GemFireVersion;
import org.apache.geode.internal.cache.AbstractCacheServer;
import org.apache.geode.internal.cache.CacheConfig;
import org.apache.geode.internal.cache.CacheServerLauncher;
import org.apache.geode.internal.cache.GemFireCacheImpl;
import org.apache.geode.internal.cache.PartitionedRegion;
import org.apache.geode.internal.cache.tier.sockets.CacheServerHelper;
import org.apache.geode.internal.i18n.LocalizedStrings;
import org.apache.geode.internal.lang.ObjectUtils;
import org.apache.geode.internal.lang.StringUtils;
import org.apache.geode.internal.lang.SystemUtils;
import org.apache.geode.internal.net.SocketCreator;
import org.apache.geode.internal.process.ClusterConfigurationNotAvailableException;
import org.apache.geode.internal.process.ConnectionFailedException;
import org.apache.geode.internal.process.ControlNotificationHandler;
import org.apache.geode.internal.process.ControllableProcess;
import org.apache.geode.internal.process.FileAlreadyExistsException;
import org.apache.geode.internal.process.MBeanInvocationFailedException;
import org.apache.geode.internal.process.PidUnavailableException;
import org.apache.geode.internal.process.ProcessController;
import org.apache.geode.internal.process.ProcessControllerFactory;
import org.apache.geode.internal.process.ProcessControllerParameters;
import org.apache.geode.internal.process.ProcessLauncherContext;
import org.apache.geode.internal.process.ProcessType;
import org.apache.geode.internal.process.StartupStatusListener;
import org.apache.geode.internal.process.UnableToControlProcessException;
import org.apache.geode.internal.util.IOUtils;
import org.apache.geode.lang.AttachAPINotFoundException;
import org.apache.geode.management.internal.cli.i18n.CliStrings;
import org.apache.geode.management.internal.cli.json.GfJsonArray;
import org.apache.geode.management.internal.cli.json.GfJsonException;
import org.apache.geode.management.internal.cli.json.GfJsonObject;
import org.apache.geode.pdx.PdxSerializer;
import org.apache.geode.security.AuthenticationRequiredException;
import org.apache.geode.security.GemFireSecurityException;
/**
* The ServerLauncher class is a launcher class with main method to start a GemFire Server (implying
* a GemFire Cache Server process).
*
* @see org.apache.geode.distributed.AbstractLauncher
* @see org.apache.geode.distributed.LocatorLauncher
* @since GemFire 7.0
*/
@SuppressWarnings({"unused"})
public class ServerLauncher extends AbstractLauncher<String> {
/**
* @deprecated This is specific to the internal implementation and may go away in a future
* release.
*/
protected static final Integer DEFAULT_SERVER_PORT = getDefaultServerPort();
private static final Map<String, String> helpMap = new HashMap<>();
static {
helpMap.put("launcher",
LocalizedStrings.ServerLauncher_SERVER_LAUNCHER_HELP.toLocalizedString());
helpMap.put(Command.START.getName(), LocalizedStrings.ServerLauncher_START_SERVER_HELP
.toLocalizedString(String.valueOf(getDefaultServerPort())));
helpMap.put(Command.STATUS.getName(),
LocalizedStrings.ServerLauncher_STATUS_SERVER_HELP.toLocalizedString());
helpMap.put(Command.STOP.getName(),
LocalizedStrings.ServerLauncher_STOP_SERVER_HELP.toLocalizedString());
helpMap.put(Command.VERSION.getName(),
LocalizedStrings.ServerLauncher_VERSION_SERVER_HELP.toLocalizedString());
helpMap.put("assign-buckets",
LocalizedStrings.ServerLauncher_SERVER_ASSIGN_BUCKETS_HELP.toLocalizedString());
helpMap.put("debug", LocalizedStrings.ServerLauncher_SERVER_DEBUG_HELP.toLocalizedString());
helpMap.put("dir", LocalizedStrings.ServerLauncher_SERVER_DIR_HELP.toLocalizedString());
helpMap.put("disable-default-server",
LocalizedStrings.ServerLauncher_SERVER_DISABLE_DEFAULT_SERVER_HELP.toLocalizedString());
helpMap.put("force", LocalizedStrings.ServerLauncher_SERVER_FORCE_HELP.toLocalizedString());
helpMap.put("help",
LocalizedStrings.SystemAdmin_CAUSES_GEMFIRE_TO_PRINT_OUT_INFORMATION_INSTEAD_OF_PERFORMING_THE_COMMAND_THIS_OPTION_IS_SUPPORTED_BY_ALL_COMMANDS
.toLocalizedString());
helpMap.put("member", LocalizedStrings.ServerLauncher_SERVER_MEMBER_HELP.toLocalizedString());
helpMap.put("pid", LocalizedStrings.ServerLauncher_SERVER_PID_HELP.toLocalizedString());
helpMap.put("rebalance",
LocalizedStrings.ServerLauncher_SERVER_REBALANCE_HELP.toLocalizedString());
helpMap.put("redirect-output",
LocalizedStrings.ServerLauncher_SERVER_REDIRECT_OUTPUT_HELP.toLocalizedString());
helpMap.put(SERVER_BIND_ADDRESS,
LocalizedStrings.ServerLauncher_SERVER_BIND_ADDRESS_HELP.toLocalizedString());
helpMap.put("hostname-for-clients",
LocalizedStrings.ServerLauncher_SERVER_HOSTNAME_FOR_CLIENT_HELP.toLocalizedString());
helpMap.put("server-port", LocalizedStrings.ServerLauncher_SERVER_PORT_HELP
.toLocalizedString(String.valueOf(getDefaultServerPort())));
}
private static final Map<Command, String> usageMap = new TreeMap<>();
static {
usageMap.put(Command.START,
"start <member-name> [--assign-buckets] [--disable-default-server] [--rebalance] [--server-bind-address=<IP-address>] [--server-port=<port>] [--force] [--debug] [--help]");
usageMap.put(Command.STATUS,
"status [--member=<member-ID/Name>] [--pid=<process-ID>] [--dir=<Server-working-directory>] [--debug] [--help]");
usageMap.put(Command.STOP,
"stop [--member=<member-ID/Name>] [--pid=<process-ID>] [--dir=<Server-working-directory>] [--debug] [--help]");
usageMap.put(Command.VERSION, "version");
}
/**
* @deprecated This is specific to the internal implementation and may go away in a future
* release.
*/
public static final String DEFAULT_SERVER_PID_FILE = "vf.gf.server.pid";
private static final String DEFAULT_SERVER_LOG_EXT = ".log";
private static final String DEFAULT_SERVER_LOG_NAME = "gemfire";
private static final String SERVER_SERVICE_NAME = "Server";
private static final AtomicReference<ServerLauncher> INSTANCE = new AtomicReference<>();
private static final ServerLauncherCacheProvider DEFAULT_CACHE_PROVIDER =
new DefaultServerLauncherCacheProvider();
private volatile transient boolean debug;
private final transient ControlNotificationHandler controlHandler;
private final AtomicBoolean starting = new AtomicBoolean(false);
private final boolean assignBuckets;
private final boolean disableDefaultServer;
private final boolean force;
private final boolean help;
private final boolean rebalance;
private final boolean redirectOutput;
private volatile transient Cache cache;
private final transient CacheConfig cacheConfig;
private final Command command;
private final InetAddress serverBindAddress;
private final Integer pid;
private final Integer serverPort;
private final Properties distributedSystemProperties;
private final String memberName;
private final String springXmlLocation;
private final String workingDirectory;
// NOTE in addition to debug, the other shared, mutable state
private volatile transient String statusMessage;
private final Float criticalHeapPercentage;
private final Float evictionHeapPercentage;
private final Float criticalOffHeapPercentage;
private final Float evictionOffHeapPercentage;
private final String hostNameForClients;
private final Integer maxConnections;
private final Integer maxMessageCount;
private final Integer messageTimeToLive;
private final Integer socketBufferSize;
private final Integer maxThreads;
private volatile transient ControllableProcess process;
private final transient ServerControllerParameters controllerParameters;
/**
* Launches a GemFire Server from the command-line configured with the given arguments.
*
* @param args the command-line arguments used to configure the GemFire Server at runtime.
*/
public static void main(final String... args) {
try {
new Builder(args).build().run();
} catch (AttachAPINotFoundException e) {
System.err.println(e.getMessage());
}
}
private static Integer getDefaultServerPort() {
return Integer.getInteger(AbstractCacheServer.TEST_OVERRIDE_DEFAULT_PORT_PROPERTY,
CacheServer.DEFAULT_PORT);
}
/**
* Gets the instance of the ServerLauncher used to launch the GemFire Cache Server, or null if
* this VM does not have an instance of ServerLauncher indicating no GemFire Cache Server is
* running.
*
* @return the instance of ServerLauncher used to launcher a GemFire Cache Server in this VM.
*/
public static ServerLauncher getInstance() {
return INSTANCE.get();
}
/**
* Gets the ServerState for this process or null if this process was not launched using this VM's
* ServerLauncher reference .
*
* @return the ServerState for this process or null.
*/
public static ServerState getServerState() {
return (getInstance() != null ? getInstance().status() : null);
}
/**
* Private constructor used to properly construct an immutable instance of the ServerLauncher
* using a Builder. The Builder is used to configure a ServerLauncher instance. The Builder can
* process user input from the command-line or be used programmatically to properly construct an
* instance of the ServerLauncher using the API.
*
* @param builder an instance of ServerLauncher.Builder for configuring and constructing an
* instance of the ServerLauncher.
* @see org.apache.geode.distributed.ServerLauncher.Builder
*/
private ServerLauncher(final Builder builder) {
this.cache = builder.getCache(); // testing
this.cacheConfig = builder.getCacheConfig();
this.command = builder.getCommand();
this.assignBuckets = Boolean.TRUE.equals(builder.getAssignBuckets());
setDebug(Boolean.TRUE.equals(builder.getDebug()));
this.disableDefaultServer = Boolean.TRUE.equals(builder.getDisableDefaultServer());
CacheServerLauncher.disableDefaultServer.set(this.disableDefaultServer);
this.distributedSystemProperties = builder.getDistributedSystemProperties();
this.force = Boolean.TRUE.equals(builder.getForce());
this.help = Boolean.TRUE.equals(builder.getHelp());
this.hostNameForClients = builder.getHostNameForClients();
this.memberName = builder.getMemberName();
// TODO:KIRK: set ThreadLocal for LogService with getLogFile or getLogFileName
this.pid = builder.getPid();
this.rebalance = Boolean.TRUE.equals(builder.getRebalance());
this.redirectOutput = Boolean.TRUE.equals(builder.getRedirectOutput());
this.serverBindAddress = builder.getServerBindAddress();
if (builder.isServerBindAddressSetByUser() && this.serverBindAddress != null) {
CacheServerLauncher.serverBindAddress.set(this.serverBindAddress.getHostAddress());
}
this.serverPort = builder.getServerPort();
if (builder.isServerPortSetByUser() && this.serverPort != null) {
CacheServerLauncher.serverPort.set(this.serverPort);
}
this.springXmlLocation = builder.getSpringXmlLocation();
this.workingDirectory = builder.getWorkingDirectory();
this.criticalHeapPercentage = builder.getCriticalHeapPercentage();
this.evictionHeapPercentage = builder.getEvictionHeapPercentage();
this.criticalOffHeapPercentage = builder.getCriticalOffHeapPercentage();
this.evictionOffHeapPercentage = builder.getEvictionOffHeapPercentage();
this.maxConnections = builder.getMaxConnections();
this.maxMessageCount = builder.getMaxMessageCount();
this.maxThreads = builder.getMaxThreads();
this.messageTimeToLive = builder.getMessageTimeToLive();
this.socketBufferSize = builder.getSocketBufferSize();
this.controllerParameters = new ServerControllerParameters();
this.controlHandler = new ControlNotificationHandler() {
@Override
public void handleStop() {
if (isStoppable()) {
stopInProcess();
}
}
@Override
public ServiceState<?> handleStatus() {
return statusInProcess();
}
};
}
/**
* Gets a reference to the Cache that was created when the GemFire Server was started.
*
* @return a reference to the Cache created by the GemFire Server start operation.
* @see org.apache.geode.cache.Cache
*/
final Cache getCache() {
if (this.cache != null) {
boolean isReconnecting = this.cache.isReconnecting();
if (isReconnecting) {
Cache newCache = this.cache.getReconnectedCache();
if (newCache != null) {
this.cache = newCache;
}
}
}
return this.cache;
}
/**
* Gets the CacheConfig object used to configure additional GemFire Cache components and features
* (e.g. PDX).
*
* @return a CacheConfig object with additional GemFire Cache configuration meta-data used on
* startup to configure the Cache.
*/
public final CacheConfig getCacheConfig() {
final CacheConfig copy = new CacheConfig();
copy.setDeclarativeConfig(this.cacheConfig);
return copy;
}
/**
* Gets an identifier that uniquely identifies and represents the Server associated with this
* launcher.
*
* @return a String value identifier to uniquely identify the Server and it's launcher.
* @see #getServerBindAddressAsString()
* @see #getServerPortAsString()
*/
public final String getId() {
final StringBuilder buffer = new StringBuilder(ServerState.getServerBindAddressAsString(this));
final String serverPort = ServerState.getServerPortAsString(this);
if (!StringUtils.isBlank(serverPort)) {
buffer.append("[").append(serverPort).append("]");
}
return buffer.toString();
}
/**
* Get the Server launcher command used to invoke the Server.
*
* @return the Server launcher command used to invoke the Server.
* @see org.apache.geode.distributed.ServerLauncher.Command
*/
public Command getCommand() {
return this.command;
}
/**
* Determines whether buckets should be assigned to partitioned regions in the cache upon Server
* start.
*
* @return a boolean indicating if buckets should be assigned upon Server start.
*/
public boolean isAssignBuckets() {
return this.assignBuckets;
}
/**
* Determines whether a default cache server will be added when the GemFire Server comes online.
*
* @return a boolean value indicating whether to add a default cache server.
*/
public boolean isDisableDefaultServer() {
return this.disableDefaultServer;
}
/**
* Determines whether the PID file is allowed to be overwritten when the Server is started and a
* PID file already exists in the Server's specified working directory.
*
* @return boolean indicating if force has been enabled.
*/
public boolean isForcing() {
return this.force;
}
/**
* Determines whether this launcher will be used to display help information. If so, then none of
* the standard Server launcher commands will be used to affect the state of the Server. A
* launcher is said to be 'helping' if the user entered the "--help" option (switch) on the
* command-line.
*
* @return a boolean value indicating if this launcher is used for displaying help information.
* @see org.apache.geode.distributed.ServerLauncher.Command
*/
public boolean isHelping() {
return this.help;
}
/**
* Determines whether a rebalance operation on the cache will occur upon starting the GemFire
* server using this launcher.
*
* @return a boolean indicating if the cache will be rebalance when the GemFire server starts.
*/
public boolean isRebalancing() {
return this.rebalance;
}
/**
* Determines whether this launcher will redirect output to system logs when starting a new
* Locator process.
*
* @return a boolean value indicating if this launcher will redirect output to system logs when
* starting a new Locator process
*/
public boolean isRedirectingOutput() {
return this.redirectOutput;
}
/**
* Gets the name of the log file used to log information about this Server.
*
* @return a String value indicating the name of this Server's log file.
*/
public String getLogFileName() {
return StringUtils.defaultIfBlank(getMemberName(), DEFAULT_SERVER_LOG_NAME)
.concat(DEFAULT_SERVER_LOG_EXT);
}
/**
* Gets the name of this member (this Server) in the GemFire distributed system as determined by
* the 'name' GemFire property.
*
* @return a String indicating the name of the member (this Server) in the GemFire distributed
* system.
* @see AbstractLauncher#getMemberName()
*/
public String getMemberName() {
return StringUtils.defaultIfBlank(this.memberName, super.getMemberName());
}
/**
* Gets the user-specified process ID (PID) of the running Server that ServerLauncher uses to
* issue status and stop commands to the Server.
*
* @return an Integer value indicating the process ID (PID) of the running Server.
*/
@Override
public Integer getPid() {
return this.pid;
}
/**
* Gets the GemFire Distributed System (cluster) Properties.
*
* @return a Properties object containing the configuration settings for the GemFire Distributed
* System (cluster).
* @see java.util.Properties
*/
public Properties getProperties() {
return (Properties) this.distributedSystemProperties.clone();
}
/**
* Gets the IP address to which the Server is bound listening for and accepting cache client
* connections. This property should not be confused with 'bindAddress' ServerLauncher property,
* which is the port for binding the Server's ServerSocket used in distribution and messaging
* between the peers of the GemFire distributed system.
*
* @return an InetAddress indicating the IP address that the Server is bound to listening for and
* accepting cache client connections in a client/server topology.
*/
public InetAddress getServerBindAddress() {
return this.serverBindAddress;
}
/**
* Gets the host, as either hostname or IP address, on which the Server was bound and running. An
* attempt is made to get the canonical hostname for IP address to which the Server was bound for
* accepting client requests. If the server bind address is null or localhost is unknown, then a
* default String value of "localhost/127.0.0.1" is returned.
*
* Note, this information is purely information and should not be used to re-construct state or
* for other purposes.
*
* @return the hostname or IP address of the host running the Server, based on the bind-address,
* or 'localhost/127.0.0.1' if the bind address is null and localhost is unknown.
* @see java.net.InetAddress
* @see #getServerBindAddress()
*/
public String getServerBindAddressAsString() {
try {
if (getServerBindAddress() != null) {
return getServerBindAddress().getCanonicalHostName();
}
final InetAddress localhost = SocketCreator.getLocalHost();
return localhost.getCanonicalHostName();
} catch (UnknownHostException ignore) {
// TODO determine a better value for the host on which the Server is running to return here...
// NOTE returning localhost/127.0.0.1 implies the serverBindAddress was null and no IP address
// for localhost
// could be found
return "localhost/127.0.0.1";
}
}
/**
* Gets the port on which the Server is listening for cache client connections. This property
* should not be confused with the 'port' ServerLauncher property, which is used by the Server to
* set the 'tcp-port' distribution config property and is used by the ServerSocket for peer
* distribution and messaging.
*
* @return an Integer value indicating the port the Server is listening on for cache client
* connections in the client/server topology.
*/
public Integer getServerPort() {
return this.serverPort;
}
/**
* Gets the server port on which the Server is listening for client requests represented as a
* String value.
*
* @return a String representing the server port on which the Server is listening for client
* requests.
* @see #getServerPort()
*/
public String getServerPortAsString() {
return ObjectUtils.defaultIfNull(getServerPort(), getDefaultServerPort()).toString();
}
/**
* Gets the name for a GemFire Server.
*
* @return a String indicating the name for a GemFire Server.
*/
public String getServiceName() {
return SERVER_SERVICE_NAME;
}
/**
* Gets the location of the Spring XML configuration meta-data file used to bootstrap, configure
* and initialize the GemFire Server on start.
* <p>
*
* @return a String indicating the location of the Spring XML configuration file.
* @see org.apache.geode.distributed.ServerLauncher.Builder#getSpringXmlLocation()
*/
public String getSpringXmlLocation() {
return this.springXmlLocation;
}
/**
* Determines whether this GemFire Server was configured and initialized with Spring configuration
* meta-data.
* <p>
*
* @return a boolean value indicating whether this GemFire Server was configured with Spring
* configuration meta-data.
*/
public boolean isSpringXmlLocationSpecified() {
return !StringUtils.isBlank(this.springXmlLocation);
}
/**
* Gets the working directory pathname in which the Server will be run.
*
* @return a String value indicating the pathname of the Server's working directory.
*/
@Override
public String getWorkingDirectory() {
return this.workingDirectory;
}
public Float getCriticalHeapPercentage() {
return this.criticalHeapPercentage;
}
public Float getEvictionHeapPercentage() {
return this.evictionHeapPercentage;
}
public Float getCriticalOffHeapPercentage() {
return this.criticalOffHeapPercentage;
}
public Float getEvictionOffHeapPercentage() {
return this.evictionOffHeapPercentage;
}
public String getHostNameForClients() {
return this.hostNameForClients;
}
public Integer getMaxConnections() {
return this.maxConnections;
}
public Integer getMaxMessageCount() {
return this.maxMessageCount;
}
public Integer getMessageTimeToLive() {
return this.messageTimeToLive;
}
public Integer getMaxThreads() {
return this.maxThreads;
}
public Integer getSocketBufferSize() {
return this.socketBufferSize;
}
/**
* Displays help for the specified Server launcher command to standard err. If the Server launcher
* command is unspecified, then usage information is displayed instead.
*
* @param command the Server launcher command in which to display help information.
* @see #usage()
*/
public void help(final Command command) {
if (Command.isUnspecified(command)) {
usage();
} else {
info(StringUtils.wrap(helpMap.get(command.getName()), 80, ""));
info("\n\nusage: \n\n");
info(StringUtils.wrap("> java ... " + getClass().getName() + " " + usageMap.get(command), 80,
"\t\t"));
info("\n\noptions: \n\n");
for (final String option : command.getOptions()) {
info(StringUtils.wrap("--" + option + ": " + helpMap.get(option) + "\n", 80, "\t"));
}
info("\n\n");
}
}
/**
* Displays usage information on the proper invocation of the ServerLauncher from the command-line
* to standard err.
*
* @see #help(org.apache.geode.distributed.ServerLauncher.Command)
*/
public void usage() {
info(StringUtils.wrap(helpMap.get("launcher"), 80, "\t"));
info("\n\nSTART\n\n");
help(Command.START);
info("STATUS\n\n");
help(Command.STATUS);
info("STOP\n\n");
help(Command.STOP);
}
/**
* A Runnable method used to invoke the GemFire server (cache server) with the specified command.
* From run, a user can invoke 'start', 'status', 'stop' and 'version'. Note, that 'version' is
* also a command-line option, but can be treated as a "command" as well.
*
* @see java.lang.Runnable
*/
@Override
public void run() {
if (!isHelping()) {
switch (getCommand()) {
case START:
info(start());
waitOnServer();
break;
case STATUS:
info(status());
break;
case STOP:
info(stop());
break;
case VERSION:
info(version());
break;
default:
usage();
}
} else {
help(getCommand());
}
}
/**
* Gets a File reference with the path to the PID file for the Server.
*
* @return a File reference to the path of the Server's PID file.
*/
protected File getServerPidFile() {
return new File(getWorkingDirectory(), ProcessType.SERVER.getPidFileName());
}
/**
* Determines whether a GemFire Cache Server can be started with this instance of ServerLauncher.
*
* @return a boolean indicating whether a GemFire Cache Server can be started with this instance
* of ServerLauncher, which is true if the ServerLauncher has not already started a Server
* or a Server is not already running.
* @see #start()
*/
private boolean isStartable() {
return (!isRunning() && this.starting.compareAndSet(false, true));
}
/**
* Invokes the 'start' command and operation to startup a GemFire server (a cache server). Note,
* this method will cause the JVM to block upon server start, providing the calling Thread is a
* non-daemon Thread.
*
* @see #run()
*/
public ServerState start() {
if (isStartable()) {
INSTANCE.compareAndSet(null, this);
try {
process = new ControllableProcess(this.controlHandler, new File(getWorkingDirectory()),
ProcessType.SERVER, isForcing());
if (!isDisableDefaultServer()) {
assertPortAvailable(getServerBindAddress(), getServerPort());
}
SystemFailure.setExitOK(true);
ProcessLauncherContext.set(isRedirectingOutput(), getOverriddenDefaults(),
new StartupStatusListener() {
@Override
public void setStatus(final String statusMessage) {
debug("Callback setStatus(String) called with message (%1$s)...", statusMessage);
ServerLauncher.this.statusMessage = statusMessage;
}
});
try {
final Properties gemfireProperties = getDistributedSystemProperties(getProperties());
this.cache = createCache(gemfireProperties);
// Set the resource manager options
if (this.criticalHeapPercentage != null) {
this.cache.getResourceManager().setCriticalHeapPercentage(getCriticalHeapPercentage());
}
if (this.evictionHeapPercentage != null) {
this.cache.getResourceManager().setEvictionHeapPercentage(getEvictionHeapPercentage());
}
if (this.criticalOffHeapPercentage != null) {
this.cache.getResourceManager()
.setCriticalOffHeapPercentage(getCriticalOffHeapPercentage());
}
if (this.evictionOffHeapPercentage != null) {
this.cache.getResourceManager()
.setEvictionOffHeapPercentage(getEvictionOffHeapPercentage());
}
this.cache.setIsServer(true);
startCacheServer(this.cache);
assignBuckets(this.cache);
rebalance(this.cache);
} finally {
ProcessLauncherContext.remove();
}
debug("Running Server on (%1$s) in (%2$s) as (%2$s)...", getId(), getWorkingDirectory(),
getMember());
this.running.set(true);
return new ServerState(this, Status.ONLINE);
} catch (AuthenticationRequiredException e) {
failOnStart(e);
throw new AuthenticationRequiredException(
"user/password required. Please start your server with --user and --password. "
+ e.getMessage());
} catch (GemFireSecurityException e) {
failOnStart(e);
throw new GemFireSecurityException(e.getMessage());
} catch (IOException e) {
failOnStart(e);
throw new RuntimeException(LocalizedStrings.Launcher_Command_START_IO_ERROR_MESSAGE
.toLocalizedString(getServiceName(), getWorkingDirectory(), getId(), e.getMessage()),
e);
} catch (FileAlreadyExistsException e) {
failOnStart(e);
throw new RuntimeException(
LocalizedStrings.Launcher_Command_START_PID_FILE_ALREADY_EXISTS_ERROR_MESSAGE
.toLocalizedString(getServiceName(), getWorkingDirectory(), getId()),
e);
} catch (PidUnavailableException e) {
failOnStart(e);
throw new RuntimeException(
LocalizedStrings.Launcher_Command_START_PID_UNAVAILABLE_ERROR_MESSAGE.toLocalizedString(
getServiceName(), getId(), getWorkingDirectory(), e.getMessage()),
e);
} catch (ClusterConfigurationNotAvailableException e) {
failOnStart(e);
throw e;
} catch (RuntimeException e) {
failOnStart(e);
throw e;
} catch (Exception e) {
failOnStart(e);
throw new RuntimeException(e);
} catch (Error e) {
failOnStart(e);
throw e;
} finally {
this.starting.set(false);
}
} else {
throw new IllegalStateException(
LocalizedStrings.Launcher_Command_START_SERVICE_ALREADY_RUNNING_ERROR_MESSAGE
.toLocalizedString(getServiceName(), getWorkingDirectory(), getId()));
}
}
private Cache createCache(Properties gemfireProperties) {
ServiceLoader<ServerLauncherCacheProvider> loader =
ServiceLoader.load(ServerLauncherCacheProvider.class);
for (ServerLauncherCacheProvider provider : loader) {
Cache cache = provider.createCache(gemfireProperties, this);
if (cache != null) {
return cache;
}
}
return DEFAULT_CACHE_PROVIDER.createCache(gemfireProperties, this);
}
/**
* A helper method to ensure the same sequence of actions are taken when the Server fails to start
* caused by some exception.
*
* @param cause the Throwable thrown during the startup operation on the Server.
*/
private void failOnStart(final Throwable cause) {
if (this.cache != null) {
this.cache.close();
this.cache = null;
}
if (this.process != null) {
this.process.stop();
this.process = null;
}
INSTANCE.compareAndSet(this, null);
this.running.set(false);
}
/**
* Determines whether the specified Cache has any CacheServers.
*
* @param cache the Cache to check for existing CacheServers.
* @return a boolean value indicating if any CacheServers were added to the Cache.
*/
protected boolean isServing(final Cache cache) {
return !cache.getCacheServers().isEmpty();
}
/**
* Determines whether to continue waiting and keep the GemFire non-Server data member running.
*
* @param cache the Cache associated with this GemFire (non-Server) data member.
* @return a boolean value indicating whether the GemFire data member should continue running, as
* determined by the running flag and a connection to the distributed system (GemFire
* cluster).
*/
final boolean isWaiting(final Cache cache) {
// return (isRunning() && !getCache().isClosed());
return (isRunning() && (cache.getDistributedSystem().isConnected() || cache.isReconnecting()));
}
/**
* Causes the calling Thread to block until the GemFire Cache Server/Data Member stops.
*/
public void waitOnServer() {
assert getCache() != null : "The Cache Server must first be started with a call to start!";
if (!isServing(getCache())) {
Throwable cause = null;
try {
while (isWaiting(getCache())) {
try {
synchronized (this) {
wait(500l);
}
} catch (InterruptedException ignore) {
}
}
} catch (RuntimeException e) {
cause = e;
throw e;
} finally {
failOnStart(cause);
}
}
}
/**
* Determines whether a default server (a cache server) should be created on startup as determined
* by the absence of specifying the --disable-default-server command-line option (switch). In
* addition, a default cache server is started only if no cache servers have been added to the
* Cache by way of cache.xml.
*
* @param cache the reference to the Cache to check for any existing cache servers.
* @return a boolean indicating whether a default server should be added to the Cache.
* @see #isDisableDefaultServer()
*/
protected boolean isDefaultServerEnabled(final Cache cache) {
return (cache.getCacheServers().isEmpty() && !isDisableDefaultServer());
}
/**
* If the default server (cache server) has not been disabled and no prior cache servers were
* added to the cache, then this method will add a cache server to the Cache and start the server
* Thread on the specified bind address and port.
*
* @param cache the Cache to which the server will be added.
* @throws IOException if the Cache server fails to start due to IO error.
*/
final void startCacheServer(final Cache cache) throws IOException {
if (isDefaultServerEnabled(cache)) {
final String serverBindAddress =
(getServerBindAddress() == null ? null : getServerBindAddress().getHostAddress());
final Integer serverPort = getServerPort();
CacheServerLauncher.serverBindAddress.set(serverBindAddress);
CacheServerLauncher.serverPort.set(serverPort);
final CacheServer cacheServer = cache.addCacheServer();
cacheServer.setBindAddress(serverBindAddress);
cacheServer.setPort(serverPort);
if (getMaxThreads() != null) {
cacheServer.setMaxThreads(getMaxThreads());
}
if (getMaxConnections() != null) {
cacheServer.setMaxConnections(getMaxConnections());
}
if (getMaxMessageCount() != null) {
cacheServer.setMaximumMessageCount(getMaxMessageCount());
}
if (getMessageTimeToLive() != null) {
cacheServer.setMessageTimeToLive(getMessageTimeToLive());
}
if (getSocketBufferSize() != null) {
cacheServer.setSocketBufferSize(getSocketBufferSize());
}
if (getHostNameForClients() != null) {
cacheServer.setHostnameForClients(getHostNameForClients());
}
CacheServerHelper.setIsDefaultServer(cacheServer);
cacheServer.start();
}
}
/**
* Causes a rebalance operation to occur on the given Cache.
*
* @param cache the reference to the Cache to rebalance.
* @see org.apache.geode.cache.control.ResourceManager#createRebalanceFactory()
*/
private void rebalance(final Cache cache) {
if (isRebalancing()) {
cache.getResourceManager().createRebalanceFactory().start();
}
}
/**
* Determines whether the user indicated that buckets should be assigned on cache server start
* using the --assign-buckets command-line option (switch) at the command-line as well as whether
* the option is technically allowed. The option is only allowed if the instance of the Cache is
* the internal GemFireCacheImpl at present.
*
* @param cache the Cache reference to check for instance type.
* @return a boolean indicating if bucket assignment is both enabled and allowed.
* @see #isAssignBuckets()
*/
protected boolean isAssignBucketsAllowed(final Cache cache) {
return (isAssignBuckets() && (cache instanceof GemFireCacheImpl));
}
/**
* Assigns buckets to individual Partitioned Regions of the Cache.
*
* @param cache the Cache who's Partitioned Regions are accessed to assign buckets to.
* @see PartitionRegionHelper#assignBucketsToPartitions(org.apache.geode.cache.Region)
*/
final void assignBuckets(final Cache cache) {
if (isAssignBucketsAllowed(cache)) {
for (PartitionedRegion region : ((GemFireCacheImpl) cache).getPartitionedRegions()) {
PartitionRegionHelper.assignBucketsToPartitions(region);
}
}
}
/**
* Determines whether the Server is the process of starting or is already running.
*
* @return a boolean indicating if the Server is starting or is already running.
*/
protected boolean isStartingOrRunning() {
return (this.starting.get() || isRunning());
}
/**
* Invokes the 'status' command and operation to check the status of a GemFire server (a cache
* server).
*/
public ServerState status() {
final ServerLauncher launcher = getInstance();
// if this instance is running then return local status
if (isStartingOrRunning()) {
debug(
"Getting status from the ServerLauncher instance that actually launched the GemFire Cache Server.%n");
return new ServerState(this, (isRunning() ? Status.ONLINE : Status.STARTING));
} else if (isPidInProcess() && launcher != null) {
return launcher.statusInProcess();
} else if (getPid() != null) {
debug("Getting Server status using process ID (%1$s)%n", getPid());
return statusWithPid();
}
// attempt to get status using workingDirectory
else if (getWorkingDirectory() != null) {
debug("Getting Server status using working directory (%1$s)%n", getWorkingDirectory());
return statusWithWorkingDirectory();
}
debug(
"This ServerLauncher was not the instance used to launch the GemFire Cache Server, and neither PID "
.concat("nor working directory were specified; the Server's state is unknown.%n"));
return new ServerState(this, Status.NOT_RESPONDING);
}
private ServerState statusInProcess() {
if (isStartingOrRunning()) {
debug(
"Getting status from the ServerLauncher instance that actually launched the GemFire Cache Server.%n");
return new ServerState(this, (isRunning() ? Status.ONLINE : Status.STARTING));
} else {
return new ServerState(this, Status.NOT_RESPONDING);
}
}
private ServerState statusWithPid() {
try {
final ProcessController controller = new ProcessControllerFactory()
.createProcessController(this.controllerParameters, getPid());
controller.checkPidSupport();
final String statusJson = controller.status();
return ServerState.fromJson(statusJson);
}
// catch (NoClassDefFoundError error) {
// if (isAttachAPINotFound(error)) {
// throw new
// AttachAPINotFoundException(LocalizedStrings.Launcher_ATTACH_API_NOT_FOUND_ERROR_MESSAGE
// .toLocalizedString(), error);
// }
//
// throw error;
// }
catch (ConnectionFailedException e) {
// failed to attach to server JVM
return createNoResponseState(e, "Failed to connect to server with process id " + getPid());
} catch (IOException e) {
// failed to open or read file or dir
return createNoResponseState(e,
"Failed to communicate with server with process id " + getPid());
}
// catch (MalformedObjectNameException e) { // impossible
// // JMX object name is bad
// return createNoResponseState(e, "Failed to communicate with server with process id " +
// getPid());
// }
catch (MBeanInvocationFailedException e) {
// MBean either doesn't exist or method or attribute don't exist
return createNoResponseState(e,
"Failed to communicate with server with process id " + getPid());
}
// catch (PidUnavailableException e) {
// // couldn't determine pid from within server JVM
// return createNoResponseState(e, "Failed to communicate with server with process id " +
// getPid());
// }
catch (UnableToControlProcessException e) {
// TODO comment me
return createNoResponseState(e,
"Failed to communicate with server with process id " + getPid());
} catch (InterruptedException e) {
// TODO comment me
return createNoResponseState(e,
"Failed to communicate with server with process id " + getPid());
} catch (TimeoutException e) {
// TODO comment me
return createNoResponseState(e,
"Failed to communicate with server with process id " + getPid());
}
}
private ServerState statusWithWorkingDirectory() {
int parsedPid = 0;
try {
final ProcessController controller = new ProcessControllerFactory().createProcessController(
this.controllerParameters, new File(getWorkingDirectory()),
ProcessType.SERVER.getPidFileName(), READ_PID_FILE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
parsedPid = controller.getProcessId();
// note: in-process request will go infinite loop unless we do the following
if (parsedPid == identifyPid()) {
final ServerLauncher runningLauncher = getInstance();
if (runningLauncher != null) {
return runningLauncher.statusInProcess();
}
}
final String statusJson = controller.status();
return ServerState.fromJson(statusJson);
} catch (ConnectionFailedException e) {
// failed to attach to server JVM
return createNoResponseState(e, "Failed to connect to server with process id " + parsedPid);
} catch (FileNotFoundException e) {
// could not find pid file
return createNoResponseState(e, "Failed to find process file "
+ ProcessType.SERVER.getPidFileName() + " in " + getWorkingDirectory());
} catch (IOException e) {
// failed to open or read file or dir
return createNoResponseState(e,
"Failed to communicate with server with process id " + parsedPid);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return createNoResponseState(e,
"Interrupted while trying to communicate with server with process id " + parsedPid);
} catch (MBeanInvocationFailedException e) {
// MBean either doesn't exist or method or attribute don't exist
return createNoResponseState(e,
"Failed to communicate with server with process id " + parsedPid);
} catch (PidUnavailableException e) {
// couldn't determine pid from within server JVM
return createNoResponseState(e, "Failed to find usable process id within file "
+ ProcessType.SERVER.getPidFileName() + " in " + getWorkingDirectory());
} catch (UnableToControlProcessException e) {
return createNoResponseState(e,
"Failed to communicate with server with process id " + parsedPid);
} catch (TimeoutException e) {
return createNoResponseState(e,
"Failed to communicate with server with process id " + parsedPid);
}
}
/**
* Determines whether the Server can be stopped in-process, such as when a Server is embedded in
* an application and the ServerLauncher API is being used.
*
* @return a boolean indicating whether the Server can be stopped in-process (the application's
* process with an embedded Server).
*/
private boolean isStoppable() {
return (isRunning() && getCache() != null);
}
/**
* Invokes the 'stop' command and operation to stop a GemFire server (a cache server).
*/
public ServerState stop() {
final ServerLauncher launcher = getInstance();
// if this instance is running then stop it
if (isStoppable()) {
return stopInProcess();
}
// if in-process but difference instance of ServerLauncher
else if (isPidInProcess() && launcher != null) {
return launcher.stopInProcess();
}
// attempt to stop using pid if provided
else if (getPid() != null) {
return stopWithPid();
}
// attempt to stop using workingDirectory
else if (getWorkingDirectory() != null) {
return stopWithWorkingDirectory();
}
// TODO give user detailed error message?
return new ServerState(this, Status.NOT_RESPONDING);
}
private ServerState stopInProcess() {
if (isStoppable()) {
if (this.cache.isReconnecting()) {
this.cache.getDistributedSystem().stopReconnecting();
}
this.cache.close();
this.cache = null;
if (this.process != null) {
this.process.stop();
this.process = null;
}
INSTANCE.compareAndSet(this, null); // note: other thread may return Status.NOT_RESPONDING now
this.running.set(false);
return new ServerState(this, Status.STOPPED);
} else {
return new ServerState(this, Status.NOT_RESPONDING);
}
}
private ServerState stopWithPid() {
try {
final ProcessController controller = new ProcessControllerFactory()
.createProcessController(this.controllerParameters, getPid());
controller.checkPidSupport();
controller.stop();
return new ServerState(this, Status.STOPPED);
}
// catch (NoClassDefFoundError error) {
// if (isAttachAPINotFound(error)) {
// throw new
// AttachAPINotFoundException(LocalizedStrings.Launcher_ATTACH_API_NOT_FOUND_ERROR_MESSAGE
// .toLocalizedString(), error);
// }
//
// throw error;
// }
catch (ConnectionFailedException e) {
// failed to attach to server JVM
return createNoResponseState(e, "Failed to connect to server with process id " + getPid());
} catch (IOException e) {
// failed to open or read file or dir
return createNoResponseState(e,
"Failed to communicate with server with process id " + getPid());
}
// catch (MalformedObjectNameException e) { // impossible
// // JMX object name is bad
// return createNoResponseState(e, "Failed to communicate with server with process id " +
// getPid());
// }
catch (MBeanInvocationFailedException e) {
// MBean either doesn't exist or method or attribute don't exist
return createNoResponseState(e,
"Failed to communicate with server with process id " + getPid());
}
// catch (PidUnavailableException e) {
// // couldn't determine pid from within server JVM
// return createNoResponseState(e, "Failed to communicate with server with process id " +
// getPid());
// }
catch (UnableToControlProcessException e) {
// TODO comment me
return createNoResponseState(e,
"Failed to communicate with server with process id " + getPid());
}
}
private ServerState stopWithWorkingDirectory() {
int parsedPid = 0;
try {
final ProcessController controller = new ProcessControllerFactory().createProcessController(
this.controllerParameters, new File(getWorkingDirectory()),
ProcessType.SERVER.getPidFileName(), READ_PID_FILE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
parsedPid = controller.getProcessId();
// NOTE in-process request will go infinite loop unless we do the following
if (parsedPid == identifyPid()) {
final ServerLauncher runningLauncher = getInstance();
if (runningLauncher != null) {
return runningLauncher.stopInProcess();
}
}
controller.stop();
return new ServerState(this, Status.STOPPED);
} catch (ConnectionFailedException e) {
// failed to attach to server JVM
return createNoResponseState(e, "Failed to connect to server with process id " + parsedPid);
} catch (FileNotFoundException e) {
// could not find pid file
return createNoResponseState(e, "Failed to find process file "
+ ProcessType.SERVER.getPidFileName() + " in " + getWorkingDirectory());
} catch (IOException e) {
// failed to open or read file or dir
return createNoResponseState(e,
"Failed to communicate with server with process id " + parsedPid);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return createNoResponseState(e,
"Interrupted while trying to communicate with server with process id " + parsedPid);
} catch (MBeanInvocationFailedException e) {
// MBean either doesn't exist or method or attribute don't exist
return createNoResponseState(e,
"Failed to communicate with server with process id " + parsedPid);
} catch (PidUnavailableException e) {
// couldn't determine pid from within server JVM
return createNoResponseState(e, "Failed to find usable process id within file "
+ ProcessType.SERVER.getPidFileName() + " in " + getWorkingDirectory());
} catch (TimeoutException e) {
return createNoResponseState(e, "Timed out trying to find usable process id within file "
+ ProcessType.SERVER.getPidFileName() + " in " + getWorkingDirectory());
} catch (UnableToControlProcessException e) {
return createNoResponseState(e,
"Failed to communicate with server with process id " + parsedPid);
}
}
// For testing purposes only!
void setIsRunningForTest() {
this.running.set(true);
}
private ServerState createNoResponseState(final Exception cause, final String errorMessage) {
debug(cause);
return new ServerState(this, Status.NOT_RESPONDING, errorMessage);
}
private Properties getOverriddenDefaults() {
final Properties overriddenDefaults = new Properties();
overriddenDefaults.put(ProcessLauncherContext.OVERRIDDEN_DEFAULTS_PREFIX.concat(LOG_FILE),
getLogFileName());
for (String key : System.getProperties().stringPropertyNames()) {
if (key.startsWith(ProcessLauncherContext.OVERRIDDEN_DEFAULTS_PREFIX)) {
overriddenDefaults.put(key, System.getProperty(key));
}
}
return overriddenDefaults;
}
private class ServerControllerParameters implements ProcessControllerParameters {
@Override
public File getPidFile() {
return getServerPidFile();
}
@Override
public File getWorkingDirectory() {
return new File(ServerLauncher.this.getWorkingDirectory());
}
@Override
public int getProcessId() {
return getPid();
}
@Override
public ProcessType getProcessType() {
return ProcessType.SERVER;
}
@Override
public ObjectName getNamePattern() {
try {
return ObjectName.getInstance("GemFire:type=Member,*");
} catch (MalformedObjectNameException e) {
return null;
} catch (NullPointerException e) {
return null;
}
}
@Override
public String getPidAttribute() {
return "ProcessId";
}
@Override
public String getStopMethod() {
return "shutDownMember";
}
@Override
public String getStatusMethod() {
return "status";
}
@Override
public String[] getAttributes() {
return new String[] {"Server"};
}
@Override
public Object[] getValues() {
return new Object[] {Boolean.TRUE};
}
}
/**
* The Builder class, modeled after the Builder creational design pattern, is used to construct a
* properly configured and initialized instance of the ServerLauncher to control and run GemFire
* servers (in particular, cache servers).
*/
public static class Builder {
protected static final Command DEFAULT_COMMAND = Command.UNSPECIFIED;
private boolean serverBindAddressSetByUser;
private boolean serverPortSetByUser;
private Boolean assignBuckets;
private Boolean debug;
private Boolean disableDefaultServer;
private Boolean force;
private Boolean help;
private Boolean rebalance;
private Boolean redirectOutput;
private Cache cache;
private final CacheConfig cacheConfig = new CacheConfig();
private Command command;
private InetAddress serverBindAddress;
private Integer pid;
private Integer serverPort;
private final Properties distributedSystemProperties = new Properties();
private String memberName;
private String springXmlLocation;
private String workingDirectory;
private Float criticalHeapPercentage;
private Float evictionHeapPercentage;
private Float criticalOffHeapPercentage;
private Float evictionOffHeapPercentage;
private String hostNameForClients;
private Integer loadPollInterval;
private Integer maxConnections;
private Integer maxMessageCount;
private Integer messageTimeToLive;
private Integer socketBufferSize;
private Integer maxThreads;
/**
* Default constructor used to create an instance of the Builder class for programmatical
* access.
*/
public Builder() {}
/**
* Constructor used to create and configure an instance of the Builder class with the specified
* arguments, passed in from the command-line when launching an instance of this class from the
* command-line using the Java launcher.
*
* @param args the array of arguments used to configure the Builder.
* @see #parseArguments(String...)
*/
public Builder(final String... args) {
parseArguments(args != null ? args : new String[0]);
}
/**
* Gets an instance of the JOptSimple OptionParser to parse the command-line arguments for
* Server.
*
* @return an instance of the JOptSimple OptionParser configured with the command-line options
* used by the Server.
*/
private OptionParser getParser() {
OptionParser parser = new OptionParser(true);
parser.accepts("assign-buckets");
parser.accepts("debug");
parser.accepts("dir").withRequiredArg().ofType(String.class);
parser.accepts("disable-default-server");
parser.accepts("force");
parser.accepts("help");
parser.accepts("member").withRequiredArg().ofType(String.class);
parser.accepts("pid").withRequiredArg().ofType(Integer.class);
parser.accepts("rebalance");
parser.accepts("redirect-output");
parser.accepts(SERVER_BIND_ADDRESS).withRequiredArg().ofType(String.class);
parser.accepts("server-port").withRequiredArg().ofType(Integer.class);
parser.accepts("spring-xml-location").withRequiredArg().ofType(String.class);
parser.accepts("version");
parser.accepts(CliStrings.START_SERVER__CRITICAL__HEAP__PERCENTAGE).withRequiredArg()
.ofType(Float.class);
parser.accepts(CliStrings.START_SERVER__EVICTION__HEAP__PERCENTAGE).withRequiredArg()
.ofType(Float.class);
parser.accepts(CliStrings.START_SERVER__CRITICAL_OFF_HEAP_PERCENTAGE).withRequiredArg()
.ofType(Float.class);
parser.accepts(CliStrings.START_SERVER__EVICTION_OFF_HEAP_PERCENTAGE).withRequiredArg()
.ofType(Float.class);
parser.accepts(CliStrings.START_SERVER__MAX__CONNECTIONS).withRequiredArg()
.ofType(Integer.class);
parser.accepts(CliStrings.START_SERVER__MAX__MESSAGE__COUNT).withRequiredArg()
.ofType(Integer.class);
parser.accepts(CliStrings.START_SERVER__MAX__THREADS).withRequiredArg().ofType(Integer.class);
parser.accepts(CliStrings.START_SERVER__MESSAGE__TIME__TO__LIVE).withRequiredArg()
.ofType(Integer.class);
parser.accepts(CliStrings.START_SERVER__SOCKET__BUFFER__SIZE).withRequiredArg()
.ofType(Integer.class);
parser.accepts(CliStrings.START_SERVER__HOSTNAME__FOR__CLIENTS).withRequiredArg()
.ofType(String.class);
return parser;
}
/**
* Parses the list of arguments to configure this Builder with the intent of constructing a
* Server launcher to invoke a Cache Server. This method is called to parse the arguments
* specified by the user on the command-line.
*
* @param args the array of arguments used to configure this Builder and create an instance of
* ServerLauncher.
*/
protected void parseArguments(final String... args) {
try {
OptionSet options = getParser().parse(args);
parseCommand(args);
parseMemberName(args); // TODO:KIRK: need to get the name to LogService for log file name
setAssignBuckets(options.has("assign-buckets"));
setDebug(options.has("debug"));
setDisableDefaultServer(options.has("disable-default-server"));
setForce(options.has("force"));
setHelp(options.has("help"));
setRebalance(options.has("rebalance"));
setRedirectOutput(options.has("redirect-output"));
if (options.hasArgument(CliStrings.START_SERVER__CRITICAL__HEAP__PERCENTAGE)) {
setCriticalHeapPercentage(Float.parseFloat(ObjectUtils
.toString(options.valueOf(CliStrings.START_SERVER__CRITICAL__HEAP__PERCENTAGE))));
}
if (options.hasArgument(CliStrings.START_SERVER__EVICTION__HEAP__PERCENTAGE)) {
setEvictionHeapPercentage(Float.parseFloat(ObjectUtils
.toString(options.valueOf(CliStrings.START_SERVER__EVICTION__HEAP__PERCENTAGE))));
}
if (options.hasArgument(CliStrings.START_SERVER__CRITICAL_OFF_HEAP_PERCENTAGE)) {
setCriticalOffHeapPercentage(Float.parseFloat(ObjectUtils
.toString(options.valueOf(CliStrings.START_SERVER__CRITICAL_OFF_HEAP_PERCENTAGE))));
}
if (options.hasArgument(CliStrings.START_SERVER__EVICTION_OFF_HEAP_PERCENTAGE)) {
setEvictionOffHeapPercentage(Float.parseFloat(ObjectUtils
.toString(options.valueOf(CliStrings.START_SERVER__EVICTION_OFF_HEAP_PERCENTAGE))));
}
if (options.hasArgument(CliStrings.START_SERVER__MAX__CONNECTIONS)) {
setMaxConnections(Integer.parseInt(
ObjectUtils.toString(options.valueOf(CliStrings.START_SERVER__MAX__CONNECTIONS))));
}
if (options.hasArgument(CliStrings.START_SERVER__MAX__MESSAGE__COUNT)) {
setMaxConnections(Integer.parseInt(
ObjectUtils.toString(options.valueOf(CliStrings.START_SERVER__MAX__MESSAGE__COUNT))));
}
if (options.hasArgument(CliStrings.START_SERVER__MESSAGE__TIME__TO__LIVE)) {
setMaxConnections(Integer.parseInt(ObjectUtils
.toString(options.valueOf(CliStrings.START_SERVER__MESSAGE__TIME__TO__LIVE))));
}
if (options.hasArgument(CliStrings.START_SERVER__SOCKET__BUFFER__SIZE)) {
setMaxConnections(Integer.parseInt(ObjectUtils
.toString(options.valueOf(CliStrings.START_SERVER__SOCKET__BUFFER__SIZE))));
}
if (options.hasArgument(CliStrings.START_SERVER__MAX__THREADS)) {
setMaxThreads(Integer.parseInt(
ObjectUtils.toString(options.valueOf(CliStrings.START_SERVER__MAX__THREADS))));
}
if (!isHelping()) {
if (options.has("dir")) {
setWorkingDirectory(ObjectUtils.toString(options.valueOf("dir")));
}
if (options.has("pid")) {
setPid((Integer) options.valueOf("pid"));
}
if (options.has(SERVER_BIND_ADDRESS)) {
setServerBindAddress(ObjectUtils.toString(options.valueOf(SERVER_BIND_ADDRESS)));
}
if (options.has("server-port")) {
setServerPort((Integer) options.valueOf("server-port"));
}
if (options.has("spring-xml-location")) {
setSpringXmlLocation(ObjectUtils.toString(options.valueOf("spring-xml-location")));
}
if (options.has("version")) {
setCommand(Command.VERSION);
}
}
// TODO why are these option not inside the 'if (!isHelping())' conditional block!?
if (options.hasArgument(CliStrings.START_SERVER__CRITICAL__HEAP__PERCENTAGE)) {
setCriticalHeapPercentage(Float.parseFloat(ObjectUtils
.toString(options.valueOf(CliStrings.START_SERVER__CRITICAL__HEAP__PERCENTAGE))));
}
if (options.hasArgument(CliStrings.START_SERVER__EVICTION__HEAP__PERCENTAGE)) {
setEvictionHeapPercentage(Float.parseFloat(ObjectUtils
.toString(options.valueOf(CliStrings.START_SERVER__EVICTION__HEAP__PERCENTAGE))));
}
if (options.hasArgument(CliStrings.START_SERVER__MAX__CONNECTIONS)) {
setMaxConnections(Integer.parseInt(
ObjectUtils.toString(options.valueOf(CliStrings.START_SERVER__MAX__CONNECTIONS))));
}
if (options.hasArgument(CliStrings.START_SERVER__MAX__MESSAGE__COUNT)) {
setMaxMessageCount(Integer.parseInt(
ObjectUtils.toString(options.valueOf(CliStrings.START_SERVER__MAX__MESSAGE__COUNT))));
}
if (options.hasArgument(CliStrings.START_SERVER__MAX__THREADS)) {
setMaxThreads(Integer.parseInt(
ObjectUtils.toString(options.valueOf(CliStrings.START_SERVER__MAX__THREADS))));
}
if (options.hasArgument(CliStrings.START_SERVER__MESSAGE__TIME__TO__LIVE)) {
setMessageTimeToLive(Integer.parseInt(ObjectUtils
.toString(options.valueOf(CliStrings.START_SERVER__MESSAGE__TIME__TO__LIVE))));
}
if (options.hasArgument(CliStrings.START_SERVER__SOCKET__BUFFER__SIZE)) {
setSocketBufferSize(Integer.parseInt(ObjectUtils
.toString(options.valueOf(CliStrings.START_SERVER__SOCKET__BUFFER__SIZE))));
}
if (options.hasArgument(CliStrings.START_SERVER__HOSTNAME__FOR__CLIENTS)) {
setHostNameForClients(ObjectUtils
.toString(options.valueOf(CliStrings.START_SERVER__HOSTNAME__FOR__CLIENTS)));
}
} catch (OptionException e) {
throw new IllegalArgumentException(
LocalizedStrings.Launcher_Builder_PARSE_COMMAND_LINE_ARGUMENT_ERROR_MESSAGE
.toLocalizedString("Server", e.getMessage()),
e);
} catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
}
}
/**
* Iterates the list of arguments in search of the target Server launcher command.
*
* @param args an array of arguments from which to search for the Server launcher command.
* @see org.apache.geode.distributed.ServerLauncher.Command#valueOfName(String)
* @see #parseArguments(String...)
*/
protected void parseCommand(final String... args) {
if (args != null) {
for (String arg : args) {
Command command = Command.valueOfName(arg);
if (command != null) {
setCommand(command);
break;
}
}
}
}
/**
* Iterates the list of arguments in search of the Server's GemFire member name. If the argument
* does not start with '-' or is not the name of a Server launcher command, then the value is
* presumed to be the member name for the Server in GemFire.
*
* @param args the array of arguments from which to search for the Server's member name in
* GemFire.
* @see org.apache.geode.distributed.ServerLauncher.Command#isCommand(String)
* @see #parseArguments(String...)
*/
protected void parseMemberName(final String... args) {
if (args != null) {
for (String arg : args) {
if (!(arg.startsWith(OPTION_PREFIX) || Command.isCommand(arg))) {
setMemberName(arg);
break;
}
}
}
}
/**
* Gets the CacheConfig object used to configure PDX on the GemFire Cache by the Builder.
*
* @return the CacheConfig object used to configure PDX on the GemFire Cache by the Builder.
*/
CacheConfig getCacheConfig() {
return this.cacheConfig;
}
/**
* Gets the Server launcher command used during the invocation of the ServerLauncher.
*
* @return the Server launcher command used to invoke (run) the ServerLauncher class.
* @see #setCommand(org.apache.geode.distributed.ServerLauncher.Command)
* @see org.apache.geode.distributed.ServerLauncher.Command
*/
public Command getCommand() {
return ObjectUtils.defaultIfNull(this.command, DEFAULT_COMMAND);
}
/**
* Sets the Sever launcher command used during the invocation of the ServerLauncher
*
* @param command the targeted Server launcher command used during the invocation (run) of
* ServerLauncher.
* @return this Builder instance.
* @see #getCommand()
* @see org.apache.geode.distributed.ServerLauncher.Command
*/
public Builder setCommand(final Command command) {
this.command = command;
return this;
}
/**
* Determines whether buckets should be assigned to partitioned regions in the cache upon Server
* start.
*
* @return a boolean indicating if buckets should be assigned upon Server start.
* @see #setAssignBuckets(Boolean)
*/
public Boolean getAssignBuckets() {
return this.assignBuckets;
}
/**
* Sets whether buckets should be assigned to partitioned regions in the cache upon Server
* start.
*
* @param assignBuckets a boolean indicating if buckets should be assigned upon Server start.
* @return this Builder instance.
* @see #getAssignBuckets()
*/
public Builder setAssignBuckets(final Boolean assignBuckets) {
this.assignBuckets = assignBuckets;
return this;
}
// For testing purposes only!
Cache getCache() {
return this.cache;
}
// For testing purposes only!
Builder setCache(final Cache cache) {
this.cache = cache;
return this;
}
/**
* Determines whether the new instance of the ServerLauncher will be set to debug mode.
*
* @return a boolean value indicating whether debug mode is enabled or disabled.
* @see #setDebug(Boolean)
*/
public Boolean getDebug() {
return this.debug;
}
/**
* Sets whether the new instance of the ServerLauncher will be set to debug mode.
*
* @param debug a boolean value indicating whether debug mode is to be enabled or disabled.
* @return this Builder instance.
* @see #getDebug()
*/
public Builder setDebug(final Boolean debug) {
this.debug = debug;
return this;
}
/**
* Determines whether a default cache server will be added when the GemFire Server comes online.
*
* @return a boolean value indicating whether to add a default cache server.
* @see #setDisableDefaultServer(Boolean)
*/
public Boolean getDisableDefaultServer() {
return this.disableDefaultServer;
}
/**
* Sets a boolean value indicating whether to add a default cache when the GemFire Server comes
* online.
*
* @param disableDefaultServer a boolean value indicating whether to add a default cache server.
* @return this Builder instance.
* @see #getDisableDefaultServer()
*/
public Builder setDisableDefaultServer(final Boolean disableDefaultServer) {
this.disableDefaultServer = disableDefaultServer;
return this;
}
/**
* Gets the GemFire Distributed System (cluster) Properties configuration.
*
* @return a Properties object containing configuration settings for the GemFire Distributed
* System (cluster).
* @see java.util.Properties
*/
public Properties getDistributedSystemProperties() {
return this.distributedSystemProperties;
}
/**
* Gets the boolean value used by the Server to determine if it should overwrite the PID file if
* it already exists.
*
* @return the boolean value specifying whether or not to overwrite the PID file if it already
* exists.
* @see org.apache.geode.internal.process.LocalProcessLauncher
* @see #setForce(Boolean)
*/
public Boolean getForce() {
return ObjectUtils.defaultIfNull(this.force, DEFAULT_FORCE);
}
/**
* Sets the boolean value used by the Server to determine if it should overwrite the PID file if
* it already exists.
*
* @param force a boolean value indicating whether to overwrite the PID file when it already
* exists.
* @return this Builder instance.
* @see org.apache.geode.internal.process.LocalProcessLauncher
* @see #getForce()
*/
public Builder setForce(final Boolean force) {
this.force = force;
return this;
}
/**
* Determines whether the new instance of the ServerLauncher will be used to output help
* information for either a specific command, or for using ServerLauncher in general.
*
* @return a boolean value indicating whether help will be output during the invocation of the
* ServerLauncher.
* @see #setHelp(Boolean)
*/
public Boolean getHelp() {
return this.help;
}
/**
* Determines whether help has been enabled.
*
* @return a boolean indicating if help was enabled.
*/
protected final boolean isHelping() {
return Boolean.TRUE.equals(getHelp());
}
/**
* Sets whether the new instance of ServerLauncher will be used to output help information for
* either a specific command, or for using ServerLauncher in general.
*
* @param help a boolean indicating whether help information is to be displayed during
* invocation of ServerLauncher.
* @return this Builder instance.
* @see #getHelp()
*/
public Builder setHelp(final Boolean help) {
this.help = help;
return this;
}
/**
* Determines whether a rebalance operation on the cache will occur upon starting the GemFire
* server.
*
* @return a boolean indicating if the cache will be rebalance when the GemFire server starts.
* @see #setRebalance(Boolean)
*/
public Boolean getRebalance() {
return this.rebalance;
}
/**
* Set a boolean value indicating whether a rebalance operation on the cache should occur upon
* starting the GemFire server.
*
* @param rebalance a boolean indicating if the cache will be rebalanced when the GemFire server
* starts.
* @return this Builder instance.
* @see #getRebalance()
*/
public Builder setRebalance(final Boolean rebalance) {
this.rebalance = rebalance;
return this;
}
/**
* Gets the member name of this Server in GemFire.
*
* @return a String indicating the member name of this Server in GemFire.
* @see #setMemberName(String)
*/
public String getMemberName() {
return this.memberName;
}
/**
* Sets the member name of the Server in GemFire.
*
* @param memberName a String indicating the member name of this Server in GemFire.
* @return this Builder instance.
* @throws IllegalArgumentException if the member name is invalid.
* @see #getMemberName()
*/
public Builder setMemberName(final String memberName) {
if (StringUtils.isEmpty(StringUtils.trim(memberName))) {
throw new IllegalArgumentException(
LocalizedStrings.Launcher_Builder_MEMBER_NAME_ERROR_MESSAGE
.toLocalizedString("Server"));
}
this.memberName = memberName;
return this;
}
/**
* Gets the process ID (PID) of the running Server indicated by the user as an argument to the
* ServerLauncher. This PID is used by the Server launcher to determine the Server's status, or
* invoke shutdown on the Server.
*
* @return a user specified Integer value indicating the process ID of the running Server.
* @see #setPid(Integer)
*/
public Integer getPid() {
return this.pid;
}
/**
* Sets the process ID (PID) of the running Server indicated by the user as an argument to the
* ServerLauncher. This PID will be used by the Server launcher to determine the Server's
* status, or invoke shutdown on the Server.
*
* @param pid a user specified Integer value indicating the process ID of the running Server.
* @return this Builder instance.
* @throws IllegalArgumentException if the process ID (PID) is not valid (greater than zero if
* not null).
* @see #getPid()
*/
public Builder setPid(final Integer pid) {
if (pid != null && pid < 0) {
throw new IllegalArgumentException(
LocalizedStrings.Launcher_Builder_PID_ERROR_MESSAGE.toLocalizedString());
}
this.pid = pid;
return this;
}
/**
* Determines whether the new instance of LocatorLauncher will redirect output to system logs
* when starting a Locator.
*
* @return a boolean value indicating if output will be redirected to system logs when starting
* a Locator
*
* @see #setRedirectOutput(Boolean)
*/
public Boolean getRedirectOutput() {
return this.redirectOutput;
}
/**
* Determines whether redirecting of output has been enabled.
*
* @return a boolean indicating if redirecting of output was enabled.
*/
private boolean isRedirectingOutput() {
return Boolean.TRUE.equals(getRedirectOutput());
}
/**
* Sets whether the new instance of LocatorLauncher will redirect output to system logs when
* starting a Locator.
*
* @param redirectOutput a boolean value indicating if output will be redirected to system logs
* when starting a Locator.
* @return this Builder instance.
* @see #getRedirectOutput()
*/
public Builder setRedirectOutput(final Boolean redirectOutput) {
this.redirectOutput = redirectOutput;
return this;
}
/**
* Gets the IP address to which the Server will be bound listening for and accepting cache
* client connections in a client/server topology.
*
* @return an InetAddress indicating the IP address that the Server is bound to listening for
* and accepting cache client connections in a client/server topology.
* @see #setServerBindAddress(String)
*/
public InetAddress getServerBindAddress() {
return this.serverBindAddress;
}
boolean isServerBindAddressSetByUser() {
return this.serverBindAddressSetByUser;
}
/**
* Sets the IP address to which the Server will be bound listening for and accepting cache
* client connections in a client/server topology.
*
* @param serverBindAddress a String specifying the IP address or hostname that the Server will
* be bound to listen for and accept cache client connections in a client/server
* topology.
* @return this Builder instance.
* @throws IllegalArgumentException wrapping the UnknownHostException if the IP address or
* hostname for the server bind address is unknown.
* @see #getServerBindAddress()
*/
public Builder setServerBindAddress(final String serverBindAddress) {
if (StringUtils.isBlank(serverBindAddress)) {
this.serverBindAddress = null;
return this;
}
// NOTE only set the 'bind address' if the user specified a value
else {
try {
InetAddress bindAddress = InetAddress.getByName(serverBindAddress);
if (SocketCreator.isLocalHost(bindAddress)) {
this.serverBindAddress = bindAddress;
this.serverBindAddressSetByUser = true;
return this;
} else {
throw new IllegalArgumentException(
serverBindAddress + " is not an address for this machine.");
}
} catch (UnknownHostException e) {
throw new IllegalArgumentException(
LocalizedStrings.Launcher_Builder_UNKNOWN_HOST_ERROR_MESSAGE
.toLocalizedString("Server"),
e);
}
}
}
/**
* Gets the port on which the Server will listen for and accept cache client connections in a
* client/server topology.
*
* @return an Integer value specifying the port the Server will listen on and accept cache
* client connections in a client/server topology.
* @see #setServerPort(Integer)
*/
public Integer getServerPort() {
return ObjectUtils.defaultIfNull(this.serverPort, getDefaultServerPort());
}
boolean isServerPortSetByUser() {
return this.serverPortSetByUser;
}
/**
* Sets the port on which the Server will listen for and accept cache client connections in a
* client/server topology.
*
* @param serverPort an Integer value specifying the port the Server will listen on and accept
* cache client connections in a client/server topology.
* @return this Builder instance.
* @throws IllegalArgumentException if the port number is not valid.
* @see #getServerPort()
*/
public Builder setServerPort(final Integer serverPort) {
if (serverPort != null && (serverPort < 0 || serverPort > 65535)) {
throw new IllegalArgumentException(
LocalizedStrings.Launcher_Builder_INVALID_PORT_ERROR_MESSAGE
.toLocalizedString("Server"));
}
this.serverPort = serverPort;
this.serverPortSetByUser = true;
return this;
}
/**
* Gets the location of the Spring XML configuration meta-data file used to bootstrap, configure
* and initialize the GemFire Server on start.
* <p>
*
* @return a String indicating the location of the Spring XML configuration file.
* @see #setSpringXmlLocation(String)
*/
public String getSpringXmlLocation() {
return this.springXmlLocation;
}
/**
* Sets the location of the Spring XML configuration meta-data file used to bootstrap, configure
* and initialize the GemFire Server on start.
* <p>
*
* @param springXmlLocation a String indicating the location of the Spring XML configuration
* file.
* @return this Builder instance.
* @see #getSpringXmlLocation()
*/
public Builder setSpringXmlLocation(final String springXmlLocation) {
this.springXmlLocation = springXmlLocation;
return this;
}
/**
* Gets the working directory pathname in which the Server will be ran. If the directory is
* unspecified, then working directory defaults to the current directory.
*
* @return a String indicating the working directory pathname.
* @see #setWorkingDirectory(String)
*/
public String getWorkingDirectory() {
return IOUtils.tryGetCanonicalPathElseGetAbsolutePath(
new File(StringUtils.defaultIfBlank(this.workingDirectory, DEFAULT_WORKING_DIRECTORY)));
}
/**
* Sets the working directory in which the Server will be ran. This also the directory in which
* all Server files (such as log and license files) will be written. If the directory is
* unspecified, then the working directory defaults to the current directory.
*
* @param workingDirectory a String indicating the pathname of the directory in which the Server
* will be ran.
* @return this Builder instance.
* @throws IllegalArgumentException wrapping a FileNotFoundException if the working directory
* pathname cannot be found.
* @see #getWorkingDirectory()
* @see java.io.FileNotFoundException
*/
public Builder setWorkingDirectory(final String workingDirectory) {
if (!(new File(StringUtils.defaultIfBlank(workingDirectory, DEFAULT_WORKING_DIRECTORY))
.isDirectory())) {
throw new IllegalArgumentException(
LocalizedStrings.Launcher_Builder_WORKING_DIRECTORY_NOT_FOUND_ERROR_MESSAGE
.toLocalizedString("Server"),
new FileNotFoundException(workingDirectory));
}
this.workingDirectory = workingDirectory;
return this;
}
public Float getCriticalHeapPercentage() {
return this.criticalHeapPercentage;
}
public Builder setCriticalHeapPercentage(final Float criticalHeapPercentage) {
if (criticalHeapPercentage != null) {
if (criticalHeapPercentage < 0 || criticalHeapPercentage > 100.0f) {
throw new IllegalArgumentException(
String.format("Critical heap percentage (%1$s) must be between 0 and 100!",
criticalHeapPercentage));
}
}
this.criticalHeapPercentage = criticalHeapPercentage;
return this;
}
public Float getCriticalOffHeapPercentage() {
return this.criticalOffHeapPercentage;
}
public Builder setCriticalOffHeapPercentage(final Float criticalOffHeapPercentage) {
if (criticalOffHeapPercentage != null) {
if (criticalOffHeapPercentage < 0 || criticalOffHeapPercentage > 100.0f) {
throw new IllegalArgumentException(
String.format("Critical off-heap percentage (%1$s) must be between 0 and 100!",
criticalOffHeapPercentage));
}
}
this.criticalOffHeapPercentage = criticalOffHeapPercentage;
return this;
}
public Float getEvictionHeapPercentage() {
return this.evictionHeapPercentage;
}
public Builder setEvictionHeapPercentage(final Float evictionHeapPercentage) {
if (evictionHeapPercentage != null) {
if (evictionHeapPercentage < 0 || evictionHeapPercentage > 100.0f) {
throw new IllegalArgumentException(
String.format("Eviction heap percentage (%1$s) must be between 0 and 100!",
evictionHeapPercentage));
}
}
this.evictionHeapPercentage = evictionHeapPercentage;
return this;
}
public Float getEvictionOffHeapPercentage() {
return this.evictionOffHeapPercentage;
}
public Builder setEvictionOffHeapPercentage(final Float evictionOffHeapPercentage) {
if (evictionOffHeapPercentage != null) {
if (evictionOffHeapPercentage < 0 || evictionOffHeapPercentage > 100.0f) {
throw new IllegalArgumentException(
String.format("Eviction off-heap percentage (%1$s) must be between 0 and 100",
evictionOffHeapPercentage));
}
}
this.evictionOffHeapPercentage = evictionOffHeapPercentage;
return this;
}
public String getHostNameForClients() {
return this.hostNameForClients;
}
public Builder setHostNameForClients(String hostNameForClients) {
this.hostNameForClients = hostNameForClients;
return this;
}
public Integer getMaxConnections() {
return this.maxConnections;
}
public Builder setMaxConnections(Integer maxConnections) {
if (maxConnections != null && maxConnections < 1) {
throw new IllegalArgumentException(
String.format("Max Connections (%1$s) must be greater than 0!", maxConnections));
}
this.maxConnections = maxConnections;
return this;
}
public Integer getMaxMessageCount() {
return this.maxMessageCount;
}
public Builder setMaxMessageCount(Integer maxMessageCount) {
if (maxMessageCount != null && maxMessageCount < 1) {
throw new IllegalArgumentException(
String.format("Max Message Count (%1$s) must be greater than 0!", maxMessageCount));
}
this.maxMessageCount = maxMessageCount;
return this;
}
public Integer getMaxThreads() {
return this.maxThreads;
}
public Builder setMaxThreads(Integer maxThreads) {
if (maxThreads != null && maxThreads < 1) {
throw new IllegalArgumentException(
String.format("Max Threads (%1$s) must be greater than 0!", maxThreads));
}
this.maxThreads = maxThreads;
return this;
}
public Integer getMessageTimeToLive() {
return this.messageTimeToLive;
}
public Builder setMessageTimeToLive(Integer messageTimeToLive) {
if (messageTimeToLive != null && messageTimeToLive < 1) {
throw new IllegalArgumentException(String
.format("Message Time To Live (%1$s) must be greater than 0!", messageTimeToLive));
}
this.messageTimeToLive = messageTimeToLive;
return this;
}
public Integer getSocketBufferSize() {
return this.socketBufferSize;
}
public Builder setSocketBufferSize(Integer socketBufferSize) {
if (socketBufferSize != null && socketBufferSize < 1) {
throw new IllegalArgumentException(String.format(
"The Server's Socket Buffer Size (%1$s) must be greater than 0!", socketBufferSize));
}
this.socketBufferSize = socketBufferSize;
return this;
}
/**
* Sets a GemFire Distributed System Property.
*
* @param propertyName a String indicating the name of the GemFire Distributed System property
* as described in {@link ConfigurationProperties}
* @param propertyValue a String value for the GemFire Distributed System property.
* @return this Builder instance.
*/
public Builder set(final String propertyName, final String propertyValue) {
this.distributedSystemProperties.setProperty(propertyName, propertyValue);
return this;
}
/**
* Sets whether the PDX type meta-data should be persisted to disk.
*
* @param persistent a boolean indicating whether PDX type meta-data should be persisted to
* disk.
* @return this Builder instance.
*/
public Builder setPdxPersistent(final boolean persistent) {
this.cacheConfig.setPdxPersistent(persistent);
return this;
}
/**
* Sets the GemFire Disk Store to be used to persist PDX type meta-data.
*
* @param pdxDiskStore a String indicating the name of the GemFire Disk Store to use to store
* PDX type meta-data
* @return this Builder instance.
*/
public Builder setPdxDiskStore(final String pdxDiskStore) {
this.cacheConfig.setPdxDiskStore(pdxDiskStore);
return this;
}
/**
* Sets whether fields in the PDX instance should be ignored when unread.
*
* @param ignore a boolean indicating whether unread fields in the PDX instance should be
* ignored.
* @return this Builder instance.
*/
public Builder setPdxIgnoreUnreadFields(final boolean ignore) {
this.cacheConfig.setPdxIgnoreUnreadFields(ignore);
return this;
}
/**
* Sets whether PDX instances should be returned as is when Region.get(key:String):Object is
* called.
*
* @param readSerialized a boolean indicating whether the PDX instance should be returned from a
* call to Region.get(key:String):Object
* @return this Builder instance.
*/
public Builder setPdxReadSerialized(final boolean readSerialized) {
this.cacheConfig.setPdxReadSerialized(readSerialized);
return this;
}
/**
* Set the PdxSerializer to use to serialize POJOs to the GemFire Cache Region or when sent
* between peers, client/server, or during persistence to disk.
*
* @param pdxSerializer the PdxSerializer that is used to serialize application domain objects
* into PDX.
* @return this Builder instance.
*/
public Builder setPdxSerializer(final PdxSerializer pdxSerializer) {
this.cacheConfig.setPdxSerializer(pdxSerializer);
return this;
}
/**
* Validates the configuration settings and properties of this Builder, ensuring that all
* invariants have been met. Currently, the only invariant constraining the Builder is that the
* user must specify the member name for the Server in the GemFire distributed system as a
* command-line argument, or by setting the memberName property programmatically using the
* corresponding setter method.
*
* @throws IllegalStateException if the Builder is not properly configured.
*/
protected void validate() {
if (!isHelping()) {
validateOnStart();
validateOnStatus();
validateOnStop();
}
}
/**
* Validates the arguments passed to the Builder when the 'start' command has been issued.
*
* @see org.apache.geode.distributed.ServerLauncher.Command#START
*/
protected void validateOnStart() {
if (Command.START.equals(getCommand())) {
if (StringUtils.isBlank(getMemberName())
&& !isSet(System.getProperties(), DistributionConfig.GEMFIRE_PREFIX + NAME)
&& !isSet(getDistributedSystemProperties(), NAME)
&& !isSet(loadGemFireProperties(DistributedSystem.getPropertyFileURL()), NAME)) {
throw new IllegalStateException(
LocalizedStrings.Launcher_Builder_MEMBER_NAME_VALIDATION_ERROR_MESSAGE
.toLocalizedString("Server"));
}
if (!SystemUtils.CURRENT_DIRECTORY.equals(getWorkingDirectory())) {
throw new IllegalStateException(
LocalizedStrings.Launcher_Builder_WORKING_DIRECTORY_OPTION_NOT_VALID_ERROR_MESSAGE
.toLocalizedString("Server"));
}
}
}
/**
* Validates the arguments passed to the Builder when the 'status' command has been issued.
*
* @see org.apache.geode.distributed.ServerLauncher.Command#STATUS
*/
protected void validateOnStatus() {
if (Command.STATUS.equals(getCommand())) {
// do nothing
}
}
/**
* Validates the arguments passed to the Builder when the 'stop' command has been issued.
*
* @see org.apache.geode.distributed.ServerLauncher.Command#STOP
*/
protected void validateOnStop() {
if (Command.STOP.equals(getCommand())) {
// do nothing
}
}
/**
* Validates the Builder configuration settings and then constructs an instance of the
* ServerLauncher class to invoke operations on a GemFire Server.
*
* @return a newly constructed instance of the ServerLauncher configured with this Builder.
* @see #validate()
* @see org.apache.geode.distributed.ServerLauncher
*/
public ServerLauncher build() {
validate();
return new ServerLauncher(this);
}
}
/**
* An enumerated type representing valid commands to the Server launcher.
*/
public static enum Command {
START("start", "assign-buckets", "disable-default-server", "rebalance", SERVER_BIND_ADDRESS,
"server-port", "force", "debug",
"help"), STATUS("status", "member", "pid", "dir", "debug", "help"), STOP("stop", "member",
"pid", "dir", "debug", "help"), UNSPECIFIED("unspecified"), VERSION("version");
private final List<String> options;
private final String name;
Command(final String name, final String... options) {
assert !StringUtils.isBlank(name) : "The name of the command must be specified!";
this.name = name;
this.options = (options != null ? Collections.unmodifiableList(Arrays.asList(options))
: Collections.<String>emptyList());
}
/**
* Determines whether the specified name refers to a valid Server launcher command, as defined
* by this enumerated type.
*
* @param name a String value indicating the potential name of a Server launcher command.
* @return a boolean indicating whether the specified name for a Server launcher command is
* valid.
*/
public static boolean isCommand(final String name) {
return (valueOfName(name) != null);
}
/**
* Determines whether the given Server launcher command has been properly specified. The command
* is deemed unspecified if the reference is null or the Command is UNSPECIFIED.
*
* @param command the Server launcher command.
* @return a boolean value indicating whether the Server launcher command is unspecified.
* @see Command#UNSPECIFIED
*/
public static boolean isUnspecified(final Command command) {
return (command == null || command.isUnspecified());
}
/**
* Looks up a Server launcher command by name. The equality comparison on name is
* case-insensitive.
*
* @param name a String value indicating the name of the Server launcher command.
* @return an enumerated type representing the command name or null if the no such command with
* the specified name exists.
*/
public static Command valueOfName(final String name) {
for (final Command command : values()) {
if (command.getName().equalsIgnoreCase(name)) {
return command;
}
}
return null;
}
/**
* Gets the name of the Server launcher command.
*
* @return a String value indicating the name of the Server launcher command.
*/
public String getName() {
return this.name;
}
/**
* Gets a set of valid options that can be used with the Locator launcher command when used from
* the command-line.
*
* @return a Set of Strings indicating the names of the options available to the Server launcher
* command.
*/
public List<String> getOptions() {
return this.options;
}
/**
* Determines whether this Locator launcher command has the specified command-line option.
*
* @param option a String indicating the name of the command-line option to this command.
* @return a boolean value indicating whether this command has the specified named command-line
* option.
*/
public boolean hasOption(final String option) {
return getOptions().contains(StringUtils.toLowerCase(option));
}
/**
* Convenience method for determining whether this is the UNSPECIFIED Server launcher command.
*
* @return a boolean indicating if this command is UNSPECIFIED.
* @see #UNSPECIFIED
*/
public boolean isUnspecified() {
return (this == UNSPECIFIED);
}
/**
* Gets the String representation of this Server launcher command.
*
* @return a String value representing this Server launcher command.
*/
@Override
public String toString() {
return getName();
}
}
/**
* The ServerState is an immutable type representing the state of the specified Locator at any
* given moment in time. The state of the Locator is assessed at the exact moment an instance of
* this class is constructed.
*
* @see org.apache.geode.distributed.AbstractLauncher.ServiceState
*/
public static final class ServerState extends ServiceState<String> {
/**
* Unmarshals a ServerState instance from the JSON String.
*
* @return a ServerState value unmarshalled from the JSON String.
*/
public static ServerState fromJson(final String json) {
try {
final GfJsonObject gfJsonObject = new GfJsonObject(json);
final Status status = Status.valueOfDescription(gfJsonObject.getString(JSON_STATUS));
final List<String> jvmArguments =
Arrays.asList(GfJsonArray.toStringArray(gfJsonObject.getJSONArray(JSON_JVMARGUMENTS)));
return new ServerState(status, gfJsonObject.getString(JSON_STATUSMESSAGE),
gfJsonObject.getLong(JSON_TIMESTAMP), gfJsonObject.getString(JSON_LOCATION),
gfJsonObject.getInt(JSON_PID), gfJsonObject.getLong(JSON_UPTIME),
gfJsonObject.getString(JSON_WORKINGDIRECTORY), jvmArguments,
gfJsonObject.getString(JSON_CLASSPATH), gfJsonObject.getString(JSON_GEMFIREVERSION),
gfJsonObject.getString(JSON_JAVAVERSION), gfJsonObject.getString(JSON_LOGFILE),
gfJsonObject.getString(JSON_HOST), gfJsonObject.getString(JSON_PORT),
gfJsonObject.getString(JSON_MEMBERNAME));
} catch (GfJsonException e) {
// TODO: or should we return OFFLINE?
throw new IllegalArgumentException("Unable to create ServerStatus from JSON: " + json, e);
}
}
public ServerState(final ServerLauncher launcher, final Status status) {
this(status, launcher.statusMessage, System.currentTimeMillis(), launcher.getId(),
identifyPid(), ManagementFactory.getRuntimeMXBean().getUptime(),
launcher.getWorkingDirectory(), ManagementFactory.getRuntimeMXBean().getInputArguments(),
System.getProperty("java.class.path"), GemFireVersion.getGemFireVersion(),
System.getProperty("java.version"), getServerLogFileCanonicalPath(launcher),
getServerBindAddressAsString(launcher), getServerPortAsString(launcher),
launcher.getMemberName());
}
public ServerState(final ServerLauncher launcher, final Status status,
final String errorMessage) {
this(status, // status
errorMessage, // statusMessage
System.currentTimeMillis(), // timestamp
null, // serverLocation
null, // pid
0L, // uptime
launcher.getWorkingDirectory(), // workingDirectory
Collections.<String>emptyList(), // jvmArguments
null, // classpath
GemFireVersion.getGemFireVersion(), // gemfireVersion
null, // javaVersion
null, // logFile
null, // host
null, // port
null);// memberName
}
protected ServerState(final Status status, final String statusMessage, final long timestamp,
final String serverLocation, final Integer pid, final Long uptime,
final String workingDirectory, final List<String> jvmArguments, final String classpath,
final String gemfireVersion, final String javaVersion, final String logFile,
final String host, final String port, final String memberName) {
super(status, statusMessage, timestamp, serverLocation, pid, uptime, workingDirectory,
jvmArguments, classpath, gemfireVersion, javaVersion, logFile, host, port, memberName);
}
private static String getServerLogFileCanonicalPath(final ServerLauncher launcher) {
final InternalDistributedSystem system = InternalDistributedSystem.getAnyInstance();
if (system != null) {
final File logFile = system.getConfig().getLogFile();
if (logFile != null && logFile.isFile()) {
final String logFileCanonicalPath =
IOUtils.tryGetCanonicalPathElseGetAbsolutePath(logFile);
if (!StringUtils.isBlank(logFileCanonicalPath)) {
return logFileCanonicalPath;
}
}
}
return launcher.getLogFileCanonicalPath();
}
@SuppressWarnings("unchecked")
private static String getServerBindAddressAsString(final ServerLauncher launcher) {
final GemFireCacheImpl gemfireCache = GemFireCacheImpl.getInstance();
if (gemfireCache != null) {
final List<CacheServer> csList = gemfireCache.getCacheServers();
if (csList != null && !csList.isEmpty()) {
final CacheServer cs = csList.get(0);
final String serverBindAddressAsString = cs.getBindAddress();
if (!StringUtils.isBlank(serverBindAddressAsString)) {
return serverBindAddressAsString;
}
}
}
return launcher.getServerBindAddressAsString();
}
@SuppressWarnings("unchecked")
private static String getServerPortAsString(final ServerLauncher launcher) {
final GemFireCacheImpl gemfireCache = GemFireCacheImpl.getInstance();
if (gemfireCache != null) {
final List<CacheServer> csList = gemfireCache.getCacheServers();
if (csList != null && !csList.isEmpty()) {
final CacheServer cs = csList.get(0);
final String portAsString = String.valueOf(cs.getPort());
if (!StringUtils.isBlank(portAsString)) {
return portAsString;
}
}
}
return (launcher.isDisableDefaultServer() ? StringUtils.EMPTY_STRING
: launcher.getServerPortAsString());
}
@Override
protected String getServiceName() {
return SERVER_SERVICE_NAME;
}
}
}