org.b3log.latke.Latkes Maven / Gradle / Ivy
/*
* Copyright (c) 2009-present, b3log.org
*
* 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 org.b3log.latke;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.b3log.latke.cache.redis.RedisCache;
import org.b3log.latke.http.renderer.StaticFileRenderer;
import org.b3log.latke.ioc.BeanManager;
import org.b3log.latke.ioc.Discoverer;
import org.b3log.latke.repository.jdbc.util.Connections;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.URL;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Latke framework configuration utility facade.
*
* @author Liang Ding
* @version 2.11.0.18, Feb 3, 2020
* @see #init()
* @see #shutdown()
* @see #getServePath()
* @see #getStaticServePath()
*/
public final class Latkes {
/**
* Logger.
*/
private static final Logger LOGGER = LogManager.getLogger(Latkes.class);
/**
* Executor service.
*/
public static final ExecutorService EXECUTOR_SERVICE = Executors.newCachedThreadPool();
/**
* Version.
*/
public static final String VERSION = "3.2.3";
/**
* Application startup time millisecond.
*/
public static long startupTimeMillis = System.currentTimeMillis();
/**
* Local properties (local.properties).
*/
private static Properties localProps;
/**
* Latke configurations (latke.properties).
*/
private static Properties latkeProps;
/**
* Which mode Latke runs in?
*/
private static RuntimeMode runtimeMode;
/**
* Static resource version.
*/
private static String staticResourceVersion;
/**
* Context path.
*/
private static String contextPath;
/**
* Static path.
*/
private static String staticPath;
/**
* IoC scan path.
*/
private static String scanPath;
/**
* Init flag.
*/
private static boolean inited;
/**
* Indicates whether HTTP session is enabled.
*/
private static boolean enabledSession = true;
/**
* Scheme of the current request.
*/
private static final ThreadLocal SCHEME = new ThreadLocal<>();
/**
* Host of the current request.
*/
private static final ThreadLocal HOST = new ThreadLocal<>();
/**
* Port of the current request.
*/
private static final ThreadLocal PORT = new ThreadLocal<>();
/**
* Sets the current scheme with the specified scheme.
*
* @param scheme the specified scheme
*/
public static void setScheme(final String scheme) {
SCHEME.set(scheme);
}
/**
* Sets the current host with the specified host.
*
* @param host the specified host
*/
public static void setHost(final String host) {
HOST.set(host);
}
/**
* Sets the current port with the specified port.
*
* @param port the specified port
*/
public static void setPort(final String port) {
PORT.set(port);
}
/**
* Clears scheme, host and port.
*/
public static void clearSchemeHostPort() {
SCHEME.set(null);
HOST.set(null);
PORT.set(null);
}
/**
* Whether enables HTTP session. Default is enabled.
*
* @param enabled {@code true} if enable it
*/
public static void setEnabledSession(final boolean enabled) {
enabledSession = enabled;
}
/**
* Is enabled HTTP session.
*
* @return {@true} if enabled it, returns {@code false} otherwise
*/
public static boolean isEnabledSession() {
return enabledSession;
}
/**
* Checks if process is running via docker.
*
* @return {@code true} it is, returns {@code false} otherwise
*/
public static boolean isDocker() {
return 1 == currentPID();
}
/**
* Gets the current process's id.
*
* @return the current process's id
*/
public static long currentPID() {
final String processName = java.lang.management.ManagementFactory.getRuntimeMXBean().getName();
return Long.parseLong(processName.split("@")[0]);
}
/**
* Gets operating system name.
*
* @return os name
*/
public static String getOperatingSystemName() {
return System.getProperty("os.name");
}
private static boolean inJar;
static {
try {
final URL resource = StaticFileRenderer.class.getResource("/");
inJar = null == resource || "jar".equals(resource.toURI().getScheme());
} catch (final Exception e) {
LOGGER.log(Level.ERROR, "Checks filesystem failed, exit", e);
System.exit(-1);
}
}
/**
* Checks whether process is running via jar.
*
* @return {@code true} it is, returns {@code false} otherwise
*/
public static boolean isInJar() {
return inJar;
}
/**
* Sets local.props with the specified props. This method is useful when you want to override behaviours of the default properties.
*
* @param props the specified props
*/
public static void setLocalProps(final Properties props) {
Latkes.localProps = props;
}
/**
* Sets local.props with the specified key and value.
*
* @param key the specified key
* @param value the specified value
*/
public static void setLocalProperty(final String key, final String value) {
if (null == key) {
LOGGER.log(Level.WARN, "local.props can not set null key");
return;
}
if (null == value) {
LOGGER.log(Level.WARN, "local.props can not set null value");
return;
}
localProps.setProperty(key, value);
}
/**
* Sets latke.props with the specified props. This method is useful when you want to override behaviours of the default properties.
*
* @param props the specified props
*/
public static void setLakteProps(final Properties props) {
Latkes.latkeProps = props;
}
/**
* Sets latke.props with the specified key and value.
*
* @param key the specified key
* @param value the specified value
*/
public static void setLatkeProperty(final String key, final String value) {
if (null == key) {
LOGGER.log(Level.WARN, "latke.props can not set null key");
return;
}
if (null == value) {
LOGGER.log(Level.WARN, "latke.props can not set null value");
return;
}
latkeProps.setProperty(key, value);
}
/**
* Loads the local.props.
*/
private static void loadLocalProps() {
if (null == localProps) {
localProps = new Properties();
}
try {
InputStream resourceAsStream;
final String localPropsEnv = System.getenv("LATKE_LOCAL_PROPS");
if (StringUtils.isNotBlank(localPropsEnv)) {
LOGGER.debug("Loading local.properties from env var [$LATKE_LOCAL_PROPS=" + localPropsEnv + "]");
resourceAsStream = new FileInputStream(localPropsEnv);
} else {
LOGGER.debug("Loading local.properties from classpath [/local.properties]");
resourceAsStream = Latkes.class.getResourceAsStream("/local.properties");
}
if (null != resourceAsStream) {
localProps.load(resourceAsStream);
LOGGER.debug("Loaded local.properties");
}
} catch (final Exception e) {
LOGGER.log(Level.DEBUG, "Loads local.properties failed, ignored");
}
}
/**
* Loads the latke.props.
*/
private static void loadLatkeProps() {
if (null == latkeProps) {
latkeProps = new Properties();
}
try {
InputStream resourceAsStream;
final String latkePropsEnv = System.getenv("LATKE_PROPS");
if (StringUtils.isNotBlank(latkePropsEnv)) {
LOGGER.debug("Loading latke.properties from env var [$LATKE_PROPS=" + latkePropsEnv + "]");
resourceAsStream = new FileInputStream(latkePropsEnv);
} else {
LOGGER.debug("Loading latke.properties from classpath [/latke.properties]");
resourceAsStream = Latkes.class.getResourceAsStream("/latke.properties");
}
if (null != resourceAsStream) {
latkeProps.load(resourceAsStream);
LOGGER.debug("Loaded latke.properties");
}
} catch (final Exception e) {
LOGGER.log(Level.ERROR, "Loads latke.properties failed", e);
throw new RuntimeException("Loads latke.properties failed");
}
}
/**
* Gets static resource (JS, CSS files) version.
*
* Returns the value of "staticResourceVersion" property in local.properties. Returns the
* {@link #startupTimeMillis application startup millisecond} if not found the "staticResourceVersion" property in
* local.properties.
*
*
* @return static resource version
*/
public static String getStaticResourceVersion() {
if (null == staticResourceVersion) {
staticResourceVersion = getLatkeProperty("staticResourceVersion");
if (null == staticResourceVersion) {
staticResourceVersion = String.valueOf(startupTimeMillis);
}
}
return staticResourceVersion;
}
/**
* Sets static resource version with the specified static resource version.
*
* @param staticResourceVersion the specified static resource version
*/
public static void setStaticResourceVersion(final String staticResourceVersion) {
Latkes.staticResourceVersion = staticResourceVersion;
}
/**
* Gets server scheme.
*
* @return server scheme
*/
public static String getServerScheme() {
final String scheme = SCHEME.get();
if (null != scheme) {
return scheme;
}
String ret = getLatkeProperty("serverScheme");
if (null == ret) {
ret = "http";
}
return ret;
}
/**
* Sets server scheme.
*
* @param serverScheme the specified server scheme
*/
public static void setServerScheme(final String serverScheme) {
setLatkeProperty("serverScheme", serverScheme);
}
/**
* Gets server host.
*
* @return server host
*/
public static String getServerHost() {
final String host = HOST.get();
if (null != host) {
return host;
}
String ret = getLatkeProperty("serverHost");
if (null == ret) {
ret = "localhost";
}
return ret;
}
/**
* Sets server host.
*
* @param serverHost the specified server host
*/
public static void setServerHost(final String serverHost) {
setLatkeProperty("serverHost", serverHost);
}
/**
* Gets server port.
*
* @return server port
*/
public static String getServerPort() {
final String port = PORT.get();
if (null != port) {
return port;
}
String ret = getLatkeProperty("serverPort");
if (null == ret) {
ret = "8080";
}
return ret;
}
/**
* Sets server port.
*
* @param serverPort the specified server port
*/
public static void setServerPort(final String serverPort) {
setLatkeProperty("serverPort", serverPort);
}
/**
* Gets public IP.
*
* @return public IP
*/
public static String getPublicIP() {
if (StringUtils.isBlank(PUBLIC_IP)) {
initPublicIP();
}
return PUBLIC_IP;
}
private static String PUBLIC_IP;
/**
* Init public IP.
*/
public synchronized static void initPublicIP() {
if (StringUtils.isNotBlank(PUBLIC_IP)) {
return;
}
try {
final URL url = new URL("http://checkip.amazonaws.com");
final HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setConnectTimeout(3000);
urlConnection.setReadTimeout(3000);
try (final BufferedReader in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()))) {
PUBLIC_IP = in.readLine();
}
urlConnection.disconnect();
return;
} catch (final Exception e) {
try {
PUBLIC_IP = InetAddress.getLocalHost().getHostAddress();
return;
} catch (final Exception e2) {
}
}
PUBLIC_IP = "127.0.0.1";
}
/**
* Gets server.
*
* @return server, ${serverScheme}://${serverHost}:${serverPort}
*/
public static String getServer() {
final StringBuilder serverBuilder = new StringBuilder(getServerScheme()).append("://").append(getServerHost());
final String port = getServerPort();
if (StringUtils.isNotBlank(port) && !"80".equals(port) && !"443".equals(port)) {
serverBuilder.append(':').append(port);
}
return serverBuilder.toString();
}
/**
* Gets serve path.
*
* @return serve path, ${server}${contextPath}
*/
public static String getServePath() {
return getServer() + getContextPath();
}
/**
* Gets static server scheme.
*
* @return static server scheme
*/
public static String getStaticServerScheme() {
String ret = getLatkeProperty("staticServerScheme");
if (null == ret) {
return getServerScheme();
}
return ret;
}
/**
* Sets static server scheme.
*
* @param staticServerScheme the specified static server scheme
*/
public static void setStaticServerScheme(final String staticServerScheme) {
setLatkeProperty("staticServerScheme", staticServerScheme);
}
/**
* Gets static server host.
*
* @return static server host
*/
public static String getStaticServerHost() {
String ret = getLatkeProperty("staticServerHost");
if (null == ret) {
return getServerHost();
}
return ret;
}
/**
* Sets static server host.
*
* @param staticServerHost the specified static server host
*/
public static void setStaticServerHost(final String staticServerHost) {
setLatkeProperty("staticServerHost", staticServerHost);
}
/**
* Gets static server port.
*
* @return static server port
*/
public static String getStaticServerPort() {
String ret = getLatkeProperty("staticServerPort");
if (null == ret) {
return getServerPort();
}
return ret;
}
/**
* Sets static server port.
*
* @param staticServerPort the specified static server port
*/
public static void setStaticServerPort(final String staticServerPort) {
setLatkeProperty("staticServerPort", staticServerPort);
}
/**
* Gets static server.
*
* @return static server, ${staticServerScheme}://${staticServerHost}:${staticServerPort}
*/
public static String getStaticServer() {
final StringBuilder staticServerBuilder = new StringBuilder(getStaticServerScheme()).append("://").append(getStaticServerHost());
final String port = getStaticServerPort();
if (StringUtils.isNotBlank(port) && !"80".equals(port) && !"443".equals(port)) {
staticServerBuilder.append(':').append(port);
}
return staticServerBuilder.toString();
}
/**
* Gets static serve path.
*
* @return static serve path, ${staticServer}${staticPath}
*/
public static String getStaticServePath() {
return getStaticServer() + getStaticPath();
}
/**
* Gets context path.
*
* @return context path
*/
public static String getContextPath() {
if (null != contextPath) {
return contextPath;
}
final String contextPathConf = getLatkeProperty("contextPath");
if (null != contextPathConf) {
contextPath = contextPathConf;
return contextPath;
}
contextPath = "";
return contextPath;
}
/**
* Sets context path with the specified context path.
*
* @param contextPath the specified context path
*/
public static void setContextPath(final String contextPath) {
Latkes.contextPath = contextPath;
}
/**
* Gets static path.
*
* @return static path
*/
public static String getStaticPath() {
if (null == staticPath) {
staticPath = getLatkeProperty("staticPath");
if (null == staticPath) {
staticPath = getContextPath();
}
}
return staticPath;
}
/**
* Sets static path with the specified static path.
*
* @param staticPath the specified static path
*/
public static void setStaticPath(final String staticPath) {
Latkes.staticPath = staticPath;
}
/**
* Gets IoC scan path.
*
* @return scan path
*/
public static String getScanPath() {
if (null == scanPath) {
scanPath = getLatkeProperty("scanPath");
if (StringUtils.isBlank(scanPath)) {
scanPath = "org.b3log";
LOGGER.log(Level.INFO, "IoC scan path is empty, uses \"org.b3log\" as default scan path");
}
}
return scanPath;
}
/**
* Sets IoC scan path with the specified scan path.
*
* @param scanPath the specified scan path
*/
public static void setScanPath(final String scanPath) {
Latkes.scanPath = scanPath;
}
/**
* Initializes Latke framework.
*/
public static synchronized void init() {
if (inited) {
return;
}
inited = true;
LOGGER.log(Level.TRACE, "Initializing Latke");
loadLatkeProps();
loadLocalProps();
if (null == runtimeMode) {
final String runtimeModeValue = getLatkeProperty("runtimeMode");
if (null != runtimeModeValue) {
runtimeMode = RuntimeMode.valueOf(runtimeModeValue);
} else {
LOGGER.log(Level.TRACE, "Can't parse runtime mode in latke.properties, default to [PRODUCTION]");
runtimeMode = RuntimeMode.PRODUCTION;
}
}
if (Latkes.RuntimeMode.DEVELOPMENT == getRuntimeMode()) {
LOGGER.warn("!!!!Runtime mode is [" + Latkes.RuntimeMode.DEVELOPMENT + "], please make sure configured it with ["
+ Latkes.RuntimeMode.PRODUCTION + "] in latke.properties if deployed on production environment!!!!");
} else {
LOGGER.log(Level.DEBUG, "Runtime mode is [{}]", getRuntimeMode());
}
final RuntimeDatabase runtimeDatabase = getRuntimeDatabase();
LOGGER.log(Level.DEBUG, "Runtime database is [{}]", runtimeDatabase);
final RuntimeCache runtimeCache = getRuntimeCache();
LOGGER.log(Level.INFO, "Runtime cache is [{}]", runtimeCache);
Locale.setDefault(Locale.SIMPLIFIED_CHINESE);
final Collection> beanClasses = Discoverer.discover(Latkes.getScanPath());
BeanManager.start(beanClasses);
LOGGER.log(Level.INFO, "Initialized Latke");
}
/**
* Gets the runtime mode.
*
* @return runtime mode
*/
public static RuntimeMode getRuntimeMode() {
if (null == runtimeMode) {
throw new RuntimeException("Runtime mode has not been initialized!");
}
return runtimeMode;
}
/**
* Sets the runtime mode with the specified mode.
*
* @param runtimeMode the specified mode
*/
public static void setRuntimeMode(final RuntimeMode runtimeMode) {
Latkes.runtimeMode = runtimeMode;
}
/**
* Gets the runtime cache.
*
* @return runtime cache
*/
public static RuntimeCache getRuntimeCache() {
final String runtimeCache = getLocalProperty("runtimeCache");
if (null == runtimeCache) {
LOGGER.debug("Not found [runtimeCache] in local.properties, uses [LOCAL_LRU] as default");
return RuntimeCache.LOCAL_LRU;
}
return RuntimeCache.valueOf(runtimeCache);
}
/**
* Gets the runtime database.
*
* @return runtime database
*/
public static RuntimeDatabase getRuntimeDatabase() {
final String runtimeDatabase = getLocalProperty("runtimeDatabase");
if (null == runtimeDatabase) {
throw new RuntimeException("Please configures runtime database in local.properties!");
}
final RuntimeDatabase ret = RuntimeDatabase.valueOf(runtimeDatabase);
if (null == ret) {
throw new RuntimeException("Please configures a valid runtime database in local.properties!");
}
return ret;
}
/**
* Gets the locale.
*
* @return the locale
*/
public static Locale getLocale() {
return Locale.getDefault();
}
/**
* Sets the locale with the specified locale.
*
* @param locale the specified locale
*/
public static void setLocale(final Locale locale) {
Locale.setDefault(locale);
}
/**
* Gets a property specified by the given key from file "local.properties".
*
* @param key the given key
* @return the value, returns {@code null} if not found
*/
public static String getLocalProperty(final String key) {
String ret = localProps.getProperty(key);
if (StringUtils.isBlank(ret)) {
return ret;
}
ret = replaceEnvVars(ret);
return ret;
}
/**
* Gets a property specified by the given key from file "latke.properties".
*
* @param key the given key
* @return the value, returns {@code null} if not found
*/
public static String getLatkeProperty(final String key) {
String ret = latkeProps.getProperty(key);
if (StringUtils.isBlank(ret)) {
return ret;
}
ret = replaceEnvVars(ret);
return ret;
}
/**
* Shutdowns Latke.
*/
public static void shutdown() {
try {
EXECUTOR_SERVICE.shutdown();
if (RuntimeCache.REDIS == getRuntimeCache()) {
RedisCache.shutdown();
}
Connections.shutdownConnectionPool();
} catch (final Exception e) {
LOGGER.log(Level.ERROR, "Shutdowns Latke failed", e);
}
BeanManager.close();
// Manually unregister JDBC driver, which prevents Tomcat from complaining about memory leaks
final Enumeration drivers = DriverManager.getDrivers();
while (drivers.hasMoreElements()) {
final Driver driver = drivers.nextElement();
try {
DriverManager.deregisterDriver(driver);
LOGGER.log(Level.TRACE, "Unregistered JDBC driver [" + driver + "]");
} catch (final SQLException e) {
LOGGER.log(Level.ERROR, "Unregister JDBC driver [" + driver + "] failed", e);
}
}
}
/**
* Gets the skin name for the specified skin directory name. The skin name was configured in skin.properties
* file({@code name} as the key) under skin directory specified by the given skin directory name.
*
* @param skinDirName the given skin directory name
* @return skin name, returns {@code null} if not found or error occurs
*/
public static String getSkinName(final String skinDirName) {
try {
final Properties ret = new Properties();
final File file = getFile("/skins/" + skinDirName + "/skin.properties");
ret.load(new FileInputStream(file));
return ret.getProperty("name");
} catch (final Exception e) {
LOGGER.log(Level.ERROR, "Read skin [" + skinDirName + "]'s configuration failed: " + e.getMessage());
return null;
}
}
/**
* Gets a file with the specified path.
*
* @param path the specified path
* @return file
*/
public static File getFile(final String path) {
try {
final URL resource = Latkes.class.getResource(path);
if (null == resource) {
return null;
}
File ret = FileUtils.toFile(resource);
if (null == ret) {
final File tempdir = FileUtils.getTempDirectory();
ret = new File(tempdir.getPath() + path);
FileUtils.copyURLToFile(resource, ret);
ret.deleteOnExit();
}
return ret;
} catch (final Exception e) {
LOGGER.log(Level.ERROR, "Reads file [path=" + path + "] failed", e);
return null;
}
}
/**
* Lists file names under the specified path
*
* @param path the specified path
* @return file names
*/
public static List listFiles(final String path) {
final List ret = new ArrayList<>();
try (final InputStream in = Latkes.class.getResourceAsStream(path);
final BufferedReader br = new BufferedReader(new InputStreamReader(in))) {
String resource;
while ((resource = br.readLine()) != null) {
ret.add(path + "/" + resource);
}
} catch (final Exception e) {
LOGGER.log(Level.ERROR, "Read file names [path=" + path + "] failed", e);
}
return ret;
}
/**
* Latke runtime JDBC database specified in the configuration file local.properties.
*
* @author Love Yao
* @author Liang Ding
* @version 1.0.0.6, Jul 5, 2016
* @see Latkes#getRuntimeDatabase()
*/
public enum RuntimeDatabase {
/**
* None.
*/
NONE,
/**
* Oracle.
*/
ORACLE,
/**
* MySQL.
*/
MYSQL,
/**
* H2.
*/
H2,
/**
* MSSQL.
*/
MSSQL,
}
/**
* Latke runtime cache specified in the configuration file local.properties.
*
* @author Liang Ding
* @version 1.0.0.0, Jul 5, 2017
* @see Latkes#getRuntimeCache()
*/
public enum RuntimeCache {
/**
* None.
*/
NONE,
/**
* Local LRU memory cache (Caffeine).
*/
LOCAL_LRU,
/**
* Redis.
*/
REDIS,
}
/**
* Latke runtime mode.
*
* @author Liang Ding
* @version 1.0.0.0, Jun 24, 2011
* @see Latkes#getRuntimeMode()
*/
public enum RuntimeMode {
/**
* Indicates Latke runs in development.
*/
DEVELOPMENT,
/**
* Indicates Latke runs in production.
*/
PRODUCTION,
}
/**
* Replaces ${xxx} with corresponding env variable for the specified val.
*
* @param val the specified val
* @return replaced val
*/
private static String replaceEnvVars(final String val) {
String ret = val;
final String[] envVars = StringUtils.substringsBetween(ret, "${", "}");
if (null != envVars) {
for (int i = 0; i < envVars.length; i++) {
final String envKey = envVars[i];
String envVal = System.getenv(envKey);
if (StringUtils.isBlank(envVal)) {
envVal = "";
}
ret = StringUtils.replace(ret, "${" + envKey + "}", envVal);
}
}
return ret;
}
/**
* Private constructor.
*/
private Latkes() {
}
}