/*
* Copyright (c) 2013-2017 Cinchapi Inc.
*
* Licensed 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 com.cinchapi.concourse.server;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.LinkedBlockingQueue;
import javax.annotation.Nullable;
import com.cinchapi.common.reflect.Reflection;
import com.cinchapi.concourse.Constants;
import com.cinchapi.concourse.annotate.NonPreference;
import com.cinchapi.concourse.config.ConcourseServerPreferences;
import com.cinchapi.concourse.server.io.FileSystem;
import com.cinchapi.concourse.server.plugin.data.WriteEvent;
import com.cinchapi.concourse.util.ByteBuffers;
import com.cinchapi.concourse.util.Networking;
import com.google.common.base.MoreObjects;
import com.google.common.base.Throwables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import ch.qos.logback.classic.Level;
/**
* Contains configuration and state that must be accessible to various parts of
* the Server.
*
* @author Jeff Nelson
*/
public final class GlobalState extends Constants {
// ========================================================================
// =========================== SYSTEM METADATA ============================
/**
* A flag to indicate if the program is running from Eclipse. This flag has
* a value of {@code true} if the JVM is launched with the
* {@code -Declipse=true} flag.
*/
private static final boolean RUNNING_FROM_ECLIPSE = System
.getProperty("eclipse") != null
&& System.getProperty("eclipse").equals("true") ? true : false;
// ========================================================================
// ============================ PREFERENCES ================================
/*
* INSTRUCTIONS FOR ADDING CONFIGURATION PREFERENCES
* 1. Create the appropriately named static variable and assigned it a
* default value.
* 2. Find the PREF READING BLOCK and attempt to read the value from the
* prefs file, while supplying the variable you made in Step 1 as the
* defaultValue.
* 3. Add a placeholder for the new preference to the stock concourse.prefs
* file in conf/concourse.prefs.
*/
/**
* The absolute path to the directory where the Database record and index
* files are stored. For optimal performance, the Database should be
* placed on a separate disk partition (ideally a separate physical device)
* from the buffer_directory.
*/
public static String DATABASE_DIRECTORY = System.getProperty("user.home")
+ File.separator + "concourse" + File.separator + "db";
/**
* The absolute path to the directory where the Buffer data is stored.
* For optimal write performance, the Buffer should be placed on a
* separate disk partition (ideally a separate physical device) from
* the database_directory.
*/
public static String BUFFER_DIRECTORY = System.getProperty("user.home")
+ File.separator + "concourse" + File.separator + "buffer";
/**
* This {@link UUID} is used to identify the Concourse instance across host
* and port changes. This id will be registered locally in the system in
* data and buffer directory.
*/
@NonPreference
@Nullable
public static UUID SYSTEM_ID = null;
/**
* The size for each page in the Buffer. During reads, Buffer pages
* are individually locked, so it is desirable to have several smaller
* pages as opposed to few larger ones. Nevertheless, be sure to balance
* the desire to maximize lock granularity with the risks of having too
* many open buffer files simultaneously.
*/
public static int BUFFER_PAGE_SIZE = 8192;
/**
* The listener port (1-65535) for client connections. Choose a port between
* 49152 and 65535 to minimize the possibility of conflicts with other
* services on this host.
*/
public static int CLIENT_PORT = 1717;
/**
* The port on which the ShutdownRunner listens. Choose a port between
* 49152 and 65535 to minimize the possibility of conflicts with other
* services on this host.
*/
public static int SHUTDOWN_PORT = 3434;
/**
* The listener port (1-65535) for JMX connections. Choose a port between
* 49152 and 65535 to minimize the possibility of conflicts with other
* services on this host.
*/
public static int JMX_PORT = 9010;
/**
* The listener port (1-65535) for the management server. Choose a port
* between
* 49152 and 65535 to minimize the possibility of conflicts with other
* services on this host.
*/
public static int MANAGEMENT_PORT = 9011;
/**
* The amount of memory that is allocated to the Concourse Server JVM.
* Concourse requires a minimum heap size of 256MB to start, but much
* more is recommended to ensure that read and write operations avoid
* expensive disk seeks where possible. Concourse generally sets both
* the initial and maximum heap sizes to the specified value, so there
* must be enough system memory available for Concourse Server to start.
*/
public static long HEAP_SIZE = 1024 * 1024 * 1024; // NOTE: This is handled
// by Tanuki before the
// server starts
/**
* The listener port (1-65535) for HTTP/S connections. Choose a
* port between 49152 and 65535 to minimize the possibility of conflicts
* with other services on this host. A value of 0 indicates that the
* Concourse HTTP Server is disabled.
*/
public static int HTTP_PORT = 0;
/**
* Determine if the HTTP Server should enable and process preferences
* related to Cross-Origin Resource Sharing requests.
*/
public static boolean HTTP_ENABLE_CORS = false;
/**
* A comma separated list of default URIs that are permitted to access HTTP
* endpoints. By default (if enabled), the value of this preference is set
* to the wildcard character '*' which means that any origin is allowed
* access. Changing this value to a discrete list will set the default
* origins that are permitted, but individual endpoints may override this
* value.
*/
public static String HTTP_CORS_DEFAULT_ALLOW_ORIGIN = "*";
/**
* A comma separated list of default headers that are sent in response to a
* CORS preflight request to indicate which HTTP headers can be used when
* making the actual request. By default (if enabled), the value of this
* preference is set to the wildcard character '*' which means that any
* headers specified in the preflight request are allowed. Changing this
* value to a discrete list will set the default headers that are permitted,
* but individual endpoints may override this value.
*/
public static String HTTP_CORS_DEFAULT_ALLOW_HEADERS = "*";
/**
* A comma separated list of default methods that are sent in response to a
* CORS preflight request to indicate which HTTP methods can be used when
* making the actual request. By default (if enabled), the value of this
* preference is set to the wildcard character '*' which means that any
* method specified in the preflight request is allowed. Changing this value
* to a discrete list will set the default methods that are permitted, but
* individual endpoints may override this value.
*/
public static String HTTP_CORS_DEFAULT_ALLOW_METHODS = "*";
/**
* The default environment that is automatically loaded when the server
* starts and is used whenever a client does not specify an environment for
* its connection.
*/
public static String DEFAULT_ENVIRONMENT = "default";
/**
* <p>
* The amount of runtime information logged by the system. The options below
* are listed from least to most verbose. In addition to the indicated types
* of information, each level also logs the information for each less
* verbose level (i.e. ERROR only prints error messages, but INFO prints
* info, warn and error messages).
* </p>
* <p>
* <ul>
* <li><strong>ERROR</strong>: critical information when the system reaches
* a potentially fatal state and may not operate normally.</li>
* <li><strong>WARN</strong>: useful information when the system reaches a
* less than ideal state but can continue to operate normally.</li>
* <li><strong>INFO</strong>: status information about the system that can
* be used for sanity checking.</li>
* <li><strong>DEBUG</strong>: detailed information about the system that
* can be used to diagnose bugs.</li>
* </ul>
* </p>
* <p>
* Logging is important, but may cause performance degradation. Only use the
* DEBUG level for staging environments or instances when detailed
* information to diagnose a bug. Otherwise use the WARN or INFO levels.
* </p>
*/
public static Level LOG_LEVEL = Level.INFO;
/**
* The class representation of {@link RemoteInvocationThread}.
*/
@NonPreference
public static final Class<?> INVOCATION_THREAD_CLASS = Reflection
.getClassCasted(
"com.cinchapi.concourse.server.plugin.RemoteInvocationThread");
/**
* Whether log messages should also be printed to the console.
*/
public static boolean ENABLE_CONSOLE_LOGGING = RUNNING_FROM_ECLIPSE ? true
: false;
static {
ConcourseServerPreferences config;
try {
String devPrefs = "conf" + File.separator + "concourse.prefs.dev";
String defaultPrefs = "conf" + File.separator + "concourse.prefs";
if(FileSystem.hasFile(devPrefs)) {
config = ConcourseServerPreferences.open(devPrefs);
PREFS_FILE_PATH = FileSystem.expandPath(devPrefs);
}
else {
config = ConcourseServerPreferences.open(defaultPrefs);
PREFS_FILE_PATH = FileSystem.expandPath(defaultPrefs);
}
}
catch (Exception e) {
config = null;
}
if(config != null) {
// =================== PREF READING BLOCK ====================
DATABASE_DIRECTORY = config.getString("database_directory",
DATABASE_DIRECTORY);
BUFFER_DIRECTORY = config.getString("buffer_directory",
BUFFER_DIRECTORY);
BUFFER_PAGE_SIZE = (int) config.getSize("buffer_page_size",
BUFFER_PAGE_SIZE);
CLIENT_PORT = config.getInt("client_port", CLIENT_PORT);
SHUTDOWN_PORT = config.getInt("shutdown_port",
Networking.getCompanionPort(CLIENT_PORT, 2));
JMX_PORT = config.getInt("jmx_port", JMX_PORT);
HEAP_SIZE = config.getSize("heap_size", HEAP_SIZE);
HTTP_PORT = config.getInt("http_port", HTTP_PORT);
HTTP_ENABLE_CORS = config.getBoolean("http_enable_cors",
HTTP_ENABLE_CORS);
HTTP_CORS_DEFAULT_ALLOW_ORIGIN = config.getString(
"http_cors_default_allow_origin",
HTTP_CORS_DEFAULT_ALLOW_ORIGIN);
HTTP_CORS_DEFAULT_ALLOW_HEADERS = config.getString(
"http_cors_default_allow_headers",
HTTP_CORS_DEFAULT_ALLOW_HEADERS);
HTTP_CORS_DEFAULT_ALLOW_METHODS = config.getString(
"http_cors_default_allow_methods",
HTTP_CORS_DEFAULT_ALLOW_METHODS);
LOG_LEVEL = Level.valueOf(
config.getString("log_level", LOG_LEVEL.toString()));
ENABLE_CONSOLE_LOGGING = config.getBoolean("enable_console_logging",
ENABLE_CONSOLE_LOGGING);
if(!ENABLE_CONSOLE_LOGGING) {
ENABLE_CONSOLE_LOGGING = Boolean
.parseBoolean(System.getProperty(
"com.cinchapi.concourse.server.logging.console",
"false"));
}
DEFAULT_ENVIRONMENT = config.getString("default_environment",
DEFAULT_ENVIRONMENT);
MANAGEMENT_PORT = config.getInt("management_port",
Networking.getCompanionPort(CLIENT_PORT, 4));
SYSTEM_ID = getSystemId();
// =================== PREF READING BLOCK ====================
}
}
/**
* The list of words that are omitted from search indexes to increase speed
* and improve space efficiency.
*/
@NonPreference
public static final Set<String> STOPWORDS = Sets.newHashSet();
static {
try {
BufferedReader reader = new BufferedReader(
new FileReader("conf" + File.separator + "stopwords.txt"));
String line = null;
while ((line = reader.readLine()) != null) {
STOPWORDS.add(line);
}
reader.close();
}
catch (IOException e) {
throw Throwables.propagate(e);
}
}
/**
* The file which contains the credentials used by the
* {@link com.cinchapi.concourse.security.AccessManager}.
* This file is typically located in the root of the server installation.
*/
@NonPreference
public static String ACCESS_FILE = ".access";
/**
* A global {@link BlockingQueue} that is populated with {@link WriteEvent
* write events} within each environment's {@link Buffer}.
*/
@NonPreference
public static LinkedBlockingQueue<WriteEvent> BINARY_QUEUE = new LinkedBlockingQueue<WriteEvent>();
/**
* The absolute path to the root of the directory where Concourse Server is
* installed. This value is set by the start script. When running from
* Eclipse, this value is set to the launch directory.
*/
@NonPreference
public static String CONCOURSE_HOME = MoreObjects.firstNonNull(
System.getProperty("com.cinchapi.concourse.server.home"),
System.getProperty("user.dir"));
/**
* The name of the cookie where the HTTP auth token is stored.
*/
@NonPreference
public static String HTTP_AUTH_TOKEN_COOKIE = "concoursedb_auth_token";
/**
* The name of the header where the HTTP auth token may be stored.
*/
@NonPreference
public static String HTTP_AUTH_TOKEN_HEADER = "X-Auth-Token";
/**
* The name of the attribute where the {@link AccessToken} component of an
* AuthToken is temporarily stored for each HTTP Request. This information
* is used for validation after URL rewriting occurs.
*/
@NonPreference
public static final String HTTP_ACCESS_TOKEN_ATTRIBUTE = "com.cinchapi.concourse.server.http.AccessTokenAttribute";
/**
* The name of the attribute where the environment component of an
* AuthToken is temporarily stored for each HTTP Request. This information
* is used for validation after URL rewriting occurs.
*/
@NonPreference
public static final String HTTP_ENVIRONMENT_ATTRIBUTE = "com.cinchapi.concourse.server.http.EnvironmentAttribute";
/**
* The name of the attribute where the fingerprint component of an AuthToken
* is temporarily stored for each HTTP Request. This information is to
* prevent session hijacking and session fixation.
*/
@NonPreference
public static final String HTTP_FINGERPRINT_ATTRIBUTE = "com.cinchapi.concourse.server.http.FingerprintAttribute";
/**
* The name of the attribute that is used to signal that an HTTP request
* requires authentication.
*/
@NonPreference
public static final String HTTP_REQUIRE_AUTH_ATTRIBUTE = "com.cinchapi.concourse.server.http.RequireAuthAttribute";
/**
* The name of the cookie where the HTTP transaction token is stored.
*/
@NonPreference
public static final String HTTP_TRANSACTION_TOKEN_COOKIE = "concoursedb_transaction_token";
/**
* The name of the attribute where the transaction token is temporarily
* stored for each HTTP request.
*/
@NonPreference
public static final String HTTP_TRANSACTION_TOKEN_ATTRIBUTE = "com.cinchapi.concourse.server.http.TransactionTokenAttribute";
/**
* The path to the underlying file from which the preferences are extracted.
* This value is set in the static initialization block.
*/
@NonPreference
@Nullable
private static String PREFS_FILE_PATH;
// ========================================================================
/**
* Return the path to the underlying file from which the preferences are
* extracted.
*
* @return the absolute path to the prefs file
*/
@Nullable
public static String getPrefsFilePath() {
return PREFS_FILE_PATH;
}
/**
* Return the canonical system id based on the storage directories that are
* configured this this instance.
* <p>
* If the system id does not exist, create a new one and store it. If
* different system ids are stored, return {@code null} to indicate that the
* system is in an inconsistent state.
* </p>
*
* @return a {@link UUID} that represents the system id
*/
private static UUID getSystemId() {
String relativeFileName = ".id";
Path bufferId = Paths.get(BUFFER_DIRECTORY, relativeFileName);
Path databaseId = Paths.get(DATABASE_DIRECTORY, relativeFileName);
List<String> files = Lists.newArrayList(bufferId.toString(),
databaseId.toString());
boolean hasBufferId = false;
boolean hasDatabaseId = false;
if((hasBufferId = FileSystem.hasFile(bufferId.toString()))
&& (hasDatabaseId = FileSystem
.hasFile(databaseId.toString()))) {
UUID uuid = null;
for (String file : files) {
ByteBuffer bytes = FileSystem.readBytes(file);
long mostSignificantBits = bytes.getLong();
long leastSignificantBits = bytes.getLong();
UUID stored = new UUID(mostSignificantBits,
leastSignificantBits);
if(uuid == null || stored.equals(uuid)) {
uuid = stored;
continue;
}
else {
uuid = null;
break;
}
}
return uuid;
}
else if(!hasBufferId && !hasDatabaseId) {
UUID uuid = UUID.randomUUID();
ByteBuffer bytes = ByteBuffer.allocate(16);
bytes.putLong(uuid.getMostSignificantBits());
bytes.putLong(uuid.getLeastSignificantBits());
bytes.flip();
for (String file : files) {
FileSystem.writeBytes(ByteBuffers.asReadOnlyBuffer(bytes),
file);
}
return uuid;
}
return null;
}
}