/*
* Symphony - A modern community (forum/SNS/blog) platform written in Java.
* Copyright (C) 2012-2017, b3log.org & hacpai.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.b3log.symphony.util;
import org.apache.commons.io.IOUtils;
import org.b3log.latke.Keys;
import org.b3log.latke.Latkes;
import org.b3log.latke.RuntimeMode;
import org.b3log.latke.ioc.LatkeBeanManager;
import org.b3log.latke.ioc.Lifecycle;
import org.b3log.latke.logging.Level;
import org.b3log.latke.logging.Logger;
import org.b3log.latke.repository.jdbc.JdbcRepository;
import org.b3log.latke.service.LangPropsService;
import org.b3log.latke.service.LangPropsServiceImpl;
import org.b3log.latke.util.CollectionUtils;
import org.b3log.symphony.SymphonyServletListener;
import org.b3log.symphony.model.Common;
import org.b3log.symphony.model.Option;
import org.b3log.symphony.service.OptionQueryService;
import org.json.JSONObject;
import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Symphony utilities.
*
* @author <a href="http://88250.b3log.org">Liang Ding</a>
* @version 1.7.1.8, May 4, 2017
* @since 0.1.0
*/
public final class Symphonys {
/**
* Configurations.
*/
public static final ResourceBundle CFG = ResourceBundle.getBundle("symphony");
/**
* HacPai bot User-Agent.
*/
public static final String USER_AGENT_BOT = "Mozilla/5.0 (compatible; HacPai/1.1; +https://hacpai.com)";
/**
* Reserved tags.
*/
public static final String[] RESERVED_TAGS;
/**
* White list - tags.
*/
public static final String[] WHITE_LIST_TAGS;
/**
* Reserved user names.
*/
public static final String[] RESERVED_USER_NAMES;
/**
* Thread pool.
*/
public static final ExecutorService EXECUTOR_SERVICE = Executors.newFixedThreadPool(50);
/**
* Logger.
*/
private static final Logger LOGGER = Logger.getLogger(Symphonys.class);
static {
// Loads reserved tags
final String reservedTags = CFG.getString("reservedTags");
final String[] tags = reservedTags.split(",");
RESERVED_TAGS = new String[tags.length];
for (int i = 0; i < tags.length; i++) {
final String tag = tags[i];
RESERVED_TAGS[i] = tag.trim();
}
// Loads white list tags
final String whiteListTags = CFG.getString("whitelist.tags");
final String[] wlTags = whiteListTags.split(",");
WHITE_LIST_TAGS = new String[wlTags.length];
for (int i = 0; i < wlTags.length; i++) {
final String tag = wlTags[i];
WHITE_LIST_TAGS[i] = tag.trim();
}
// Loads reserved usernames
final String reservedUserNames = CFG.getString("reservedUserNames");
final String[] userNames = reservedUserNames.split(",");
RESERVED_USER_NAMES = new String[userNames.length];
for (int i = 0; i < userNames.length; i++) {
final String userName = userNames[i];
RESERVED_USER_NAMES[i] = userName.trim();
}
}
static {
try {
final SSLContext ctx = SSLContext.getInstance("TLS");
ctx.init(new KeyManager[0], new TrustManager[]{new X509TrustManager() {
@Override
public void checkClientTrusted(final X509Certificate[] arg0, final String arg1) {
}
@Override
public void checkServerTrusted(final X509Certificate[] arg0, final String arg1) {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
}}, new SecureRandom());
SSLContext.setDefault(ctx);
} catch (final Exception e) {
// ignore
}
// Reports status to Rhythm, I hope that everyone will be able to join in the SymHub plan :p
new Timer(true).schedule(new TimerTask() {
@Override
public void run() {
final String symURL = Latkes.getServePath();
if (Networks.isIPv4(symURL)) {
return;
}
HttpURLConnection httpConn = null;
OutputStream outputStream = null;
try {
final LatkeBeanManager beanManager = Lifecycle.getBeanManager();
final OptionQueryService optionQueryService = beanManager.getReference(OptionQueryService.class);
final JSONObject statistic = optionQueryService.getStatistic();
final int articleCount = statistic.optInt(Option.ID_C_STATISTIC_ARTICLE_COUNT);
if (articleCount < 66) {
return;
}
final LangPropsService langPropsService = beanManager.getReference(LangPropsServiceImpl.class);
httpConn = (HttpURLConnection) new URL("https://rhythm.b3log.org/sym").openConnection();
httpConn.setConnectTimeout(10000);
httpConn.setReadTimeout(10000);
httpConn.setDoOutput(true);
httpConn.setRequestMethod("POST");
httpConn.setRequestProperty(Common.USER_AGENT, USER_AGENT_BOT);
httpConn.connect();
outputStream = httpConn.getOutputStream();
final JSONObject sym = new JSONObject();
sym.put("symURL", symURL);
sym.put("symTitle", langPropsService.get("symphonyLabel", Latkes.getLocale()));
IOUtils.write(sym.toString(), outputStream, "UTF-8");
outputStream.flush();
httpConn.getResponseCode();
} catch (final Exception e) {
// ignore
} finally {
IOUtils.closeQuietly(outputStream);
if (null != httpConn) {
try {
httpConn.disconnect();
} catch (final Exception e) {
// ignore
}
}
JdbcRepository.dispose();
}
}
}, 1000 * 60 * 60 * 2, 1000 * 60 * 60 * 2);
}
/**
* Private default constructor.
*/
private Symphonys() {
}
/**
* Gets all symphonies.
*
* @return a list of symphonies
*/
public static List<JSONObject> getSyms() {
HttpURLConnection httpConn = null;
InputStream inputStream = null;
try {
httpConn = (HttpURLConnection) new URL("https://rhythm.b3log.org/syms").openConnection();
httpConn.setConnectTimeout(10000);
httpConn.setReadTimeout(10000);
httpConn.setRequestMethod("GET");
httpConn.setRequestProperty(Common.USER_AGENT, "B3log Symphony/" + SymphonyServletListener.VERSION);
httpConn.connect();
inputStream = httpConn.getInputStream();
final String data = IOUtils.toString(inputStream, "UTF-8");
final JSONObject result = new JSONObject(data);
if (!result.optBoolean(Keys.STATUS_CODE)) {
return Collections.emptyList();
}
return CollectionUtils.jsonArrayToList(result.optJSONArray("syms"));
} catch (final Exception e) {
LOGGER.log(Level.ERROR, "Gets syms from Rhythm failed", e);
return Collections.emptyList();
} finally {
IOUtils.closeQuietly(inputStream);
if (null != httpConn) {
try {
httpConn.disconnect();
} catch (final Exception e) {
// ignore
}
}
}
}
/**
* Does Symphony runs on development environment?
*
* @return {@code true} if it runs on development environment, {@code false} otherwise
*/
public static boolean runsOnDevEnv() {
return RuntimeMode.DEVELOPMENT == Latkes.getRuntimeMode();
}
/**
* Gets a configuration string property with the specified key.
*
* @param key the specified key
* @return string property value corresponding to the specified key, returns {@code null} if not found
*/
public static String get(final String key) {
return CFG.getString(key);
}
/**
* Gets a configuration boolean property with the specified key.
*
* @param key the specified key
* @return boolean property value corresponding to the specified key, returns {@code null} if not found
*/
public static Boolean getBoolean(final String key) {
final String stringValue = get(key);
if (null == stringValue) {
return null;
}
return Boolean.valueOf(stringValue);
}
/**
* Gets a configuration float property with the specified key.
*
* @param key the specified key
* @return float property value corresponding to the specified key, returns {@code null} if not found
*/
public static Float getFloat(final String key) {
final String stringValue = get(key);
if (null == stringValue) {
return null;
}
return Float.valueOf(stringValue);
}
/**
* Gets a configuration integer property with the specified key.
*
* @param key the specified key
* @return integer property value corresponding to the specified key, returns {@code null} if not found
*/
public static Integer getInt(final String key) {
final String stringValue = get(key);
if (null == stringValue) {
return null;
}
return Integer.valueOf(stringValue);
}
/**
* Gets a configuration long property with the specified key.
*
* @param key the specified key
* @return long property value corresponding to the specified key, returns {@code null} if not found
*/
public static Long getLong(final String key) {
final String stringValue = get(key);
if (null == stringValue) {
return null;
}
return Long.valueOf(stringValue);
}
}