package ameba.feature.datasource;
import com.alibaba.druid.stat.DruidStatService;
import com.alibaba.druid.util.Utils;
import com.google.common.collect.Maps;
import org.apache.commons.lang3.StringUtils;
import org.glassfish.jersey.server.model.ModelProcessor;
import org.glassfish.jersey.server.model.Resource;
import org.glassfish.jersey.server.model.ResourceModel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Singleton;
import javax.management.MBeanServerConnection;
import javax.management.ObjectName;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
import javax.ws.rs.*;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.core.*;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.net.URI;
import java.util.Map;
import java.util.UUID;
/**
* <p>StatViewFeature class.</p>
*
* @author ICode
* @since 13-8-14 下午7:49
*
*/
public class StatViewFeature implements Feature {
/**
* Constant <code>PARAM_NAME_RESET_ENABLE="datasource.resetEnable"</code>
*/
public static final String PARAM_NAME_RESET_ENABLE = "datasource.resetEnable";
/**
* Constant <code>PARAM_NAME_USERNAME="datasource.loginUsername"</code>
*/
public static final String PARAM_NAME_USERNAME = "datasource.loginUsername";
/**
* Constant <code>PARAM_NAME_PASSWORD="datasource.loginPassword"</code>
*/
public static final String PARAM_NAME_PASSWORD = "datasource.loginPassword";
/**
* Constant <code>SESSION_USER_KEY="SVST"</code>
*/
public static final String SESSION_USER_KEY = "SVST";//stat view session token
/**
* Constant <code>PARAM_NAME_JMX_URL="datasource.jmxUrl"</code>
*/
public static final String PARAM_NAME_JMX_URL = "datasource.jmxUrl";
/**
* Constant <code>PARAM_NAME_JMX_USERNAME="datasource.jmxUsername"</code>
*/
public static final String PARAM_NAME_JMX_USERNAME = "datasource.jmxUsername";
/**
* Constant <code>PARAM_NAME_JMX_PASSWORD="datasource.jmxPassword"</code>
*/
public static final String PARAM_NAME_JMX_PASSWORD = "datasource.jmxPassword";
private static final Logger logger = LoggerFactory.getLogger(StatViewFeature.class);
private final static String RESOURCE_PATH = "support/http/resources";
private static String username = null;
private static String password = "";
private static String authorizeToken = null;
/**
* 配置的jmx的连接地址
*/
private static String jmxUrl = null;
/**
* 配置的jmx的用户名
*/
private static String jmxUsername = null;
/**
* 配置的jmx的密码
*/
private static String jmxPassword = null;
private static MBeanServerConnection conn = null;
private static DruidStatService statService = DruidStatService.getInstance();
private static String dsPath = "/__ds";
private static void init(Configuration configuration) {
initAuthEnv(configuration);
try {
String param = (String) configuration.getProperty(PARAM_NAME_RESET_ENABLE);
if (param != null && param.trim().length() != 0) {
param = param.trim();
boolean resetEnable = Boolean.parseBoolean(param);
statService.setResetEnable(resetEnable);
}
} catch (Exception e) {
String msg = "initParameter config error, resetEnable : " + configuration.getProperty(PARAM_NAME_RESET_ENABLE);
logger.error(msg, e);
}
// 获取jmx的连接配置信息
String param = readInitParam(configuration, PARAM_NAME_JMX_URL);
if (param != null) {
jmxUrl = param;
jmxUsername = readInitParam(configuration, PARAM_NAME_JMX_USERNAME);
jmxPassword = readInitParam(configuration, PARAM_NAME_JMX_PASSWORD);
try {
initJmxConn();
} catch (IOException e) {
logger.error("init jmx connection error", e);
}
}
}
/**
* 初始化jmx连接
*
* @throws IOException IOException
*/
private static void initJmxConn() throws IOException {
if (jmxUrl != null) {
JMXServiceURL url = new JMXServiceURL(jmxUrl);
Map<String, String[]> env = null;
if (jmxUsername != null) {
env = Maps.newHashMap();
String[] credentials = new String[]{jmxUsername, jmxPassword};
env.put(JMXConnector.CREDENTIALS, credentials);
}
JMXConnector jmxc = JMXConnectorFactory.connect(url, env);
conn = jmxc.getMBeanServerConnection();
}
}
private static void initAuthEnv(Configuration configuration) {
String paramUserName = (String) configuration.getProperty(PARAM_NAME_USERNAME);
if (StringUtils.isNotBlank(paramUserName)) {
username = paramUserName;
}
String paramPassword = (String) configuration.getProperty(PARAM_NAME_PASSWORD);
if (StringUtils.isNotBlank(paramPassword)) {
password = paramPassword;
}
}
/**
* 读取配置参数.
*
* @param key 配置参数名
* @return 配置参数值,如果不存在当前配置参数,或者为配置参数长度为0,将返回null
*/
private static String readInitParam(Configuration configuration, String key) {
String value = null;
try {
String param = (String) configuration.getProperty(key);
if (param != null) {
param = param.trim();
if (param.length() > 0) {
value = param;
}
}
} catch (Exception e) {
String msg = "initParameter config [" + key + "] error";
logger.warn(msg, e);
}
return value;
}
/**
* 根据指定的url来获取jmx服务返回的内容.
*
* @param connetion jmx连接
* @param url url内容
* @return the jmx返回的内容
* @throws Exception the exception
*/
private static String getJmxResult(MBeanServerConnection connetion, String url) throws Exception {
ObjectName name = new ObjectName(DruidStatService.MBEAN_NAME);
return (String) conn.invoke(name, "service", new String[]{url},
new String[]{String.class.getName()});
}
/**
* 程序首先判断是否存在jmx连接地址,如果不存在,则直接调用本地的duird服务; 如果存在,则调用远程jmx服务。在进行jmx通信,首先判断一下jmx连接是否已经建立成功,如果已经
* 建立成功,则直接进行通信,如果之前没有成功建立,则会尝试重新建立一遍。.
*
* @param url 要连接的服务地址
* @return 调用服务后返回的json字符串
*/
private static String genServiceResponse(String url) {
String resp = null;
if (jmxUrl == null) {
resp = statService.service(url);
} else {
if (conn == null) {// 连接在初始化时创建失败
try {// 尝试重新连接
initJmxConn();
} catch (IOException e) {
logger.error("init jmx connection error", e);
resp = DruidStatService.returnJSONResult(DruidStatService.RESULT_CODE_ERROR,
"init jmx connection error" + e.getMessage());
}
if (conn != null) {// 连接成功
try {
resp = getJmxResult(conn, url);
} catch (Exception e) {
logger.error("get jmx data error", e);
resp = DruidStatService.returnJSONResult(DruidStatService.RESULT_CODE_ERROR, "get data error:"
+ e.getMessage());
}
}
} else {// 连接成功
try {
resp = getJmxResult(conn, url);
} catch (Exception e) {
logger.error("get jmx data error", e);
resp = DruidStatService.returnJSONResult(DruidStatService.RESULT_CODE_ERROR,
"get data error" + e.getMessage());
}
}
}
return resp;
}
/**
* {@inheritDoc}
*/
@Override
public boolean configure(final FeatureContext context) {
Configuration configuration = context.getConfiguration();
init(configuration);
String path = (String) configuration.getProperty("datasource.resource.path");
if (StringUtils.isNotBlank(path)) {
dsPath = path.startsWith("/") ? path : "/" + path;
}
context.register(WebStatFilter.class);
if (StringUtils.isNotBlank(username)) {
authorizeToken = UUID.randomUUID().toString().toUpperCase();
context.register(AuthorizationRequestFilter.class);
}
context.register(new ModelProcessor() {
@Override
public ResourceModel processResourceModel(ResourceModel resourceModel, final Configuration configuration) {
ResourceModel.Builder resourceModelBuilder = new ResourceModel.Builder(resourceModel, false);
Resource.Builder resourceBuilder = Resource.builder(DsStatViewResource.class);
resourceBuilder.path(dsPath);
Resource resource = resourceBuilder.build();
resourceModelBuilder.addResource(resource);
return resourceModelBuilder.build();
}
@Override
public ResourceModel processSubResource(ResourceModel subResourceModel, Configuration configuration) {
return subResourceModel;
}
});
return true;
}
@NameBinding
@Retention(RetentionPolicy.RUNTIME)
@interface DsAuthorization {
}
@DsAuthorization
static class AuthorizationRequestFilter implements ContainerRequestFilter {
@Override
public void filter(ContainerRequestContext requestContext)
throws IOException {
String path = "/" + requestContext.getUriInfo().getPath();
Cookie cookie = requestContext.getCookies().get(SESSION_USER_KEY);
if ((cookie == null || !authorizeToken.equals(cookie.getValue())) &&
!((dsPath + "/login.html").equals(path)
|| (dsPath + "/submitLogin").equals(path)
|| path.startsWith(dsPath + "/css")
|| path.startsWith(dsPath + "/js")
|| path.startsWith(dsPath + "/img"))) {
requestContext.abortWith(Response
.temporaryRedirect(URI.create(dsPath + "/login.html"))
.build());
}
}
}
@Path("datasource")
@Singleton
@DsAuthorization
public static class DsStatViewResource {
@Context
private UriInfo uriInfo;
@POST
@Path("submitLogin")
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public Response login(@FormParam("loginUsername") String uname, @FormParam("loginPassword") String pwd) {
if (username.equals(uname) && password.equals(pwd)) {
return Response.ok("success").cookie(new NewCookie(SESSION_USER_KEY, authorizeToken)).build();
} else {
return Response.ok("ameba/message/error").build();
}
}
@GET
public Response index() throws IOException {
return Response.temporaryRedirect(URI.create(dsPath + "/index.html")).build();
}
@GET
@Path("{path:.*}")
public Response getService(@PathParam("path") String path) throws IOException {
if ("".equals(path)) {
return Response.temporaryRedirect(URI.create(dsPath + "/index.html")).build();
} else {
if (!path.startsWith("/"))
path = "/" + path;
}
if (!path.contains(".")) {
return Response.ok(genServiceResponse(path + getQueryString())).type(MediaType.APPLICATION_JSON_TYPE).build();
}
// find file in resources path
return returnResourceFile(path);
}
private String getQueryString() {
String query = uriInfo.getRequestUri().getRawQuery();
return (uriInfo.getPath().contains(".json") ? "" : ".json") + (StringUtils.isNotBlank(query) ? "?" + query : "");
}
@POST
@Path("{path:.*}")
public Response postService(@PathParam("path") String path) throws IOException {
if (!path.startsWith("/"))
path = "/" + path;
if (!path.contains(".")) {
return Response.ok(genServiceResponse(path + getQueryString())).type(MediaType.APPLICATION_JSON_TYPE).build();
}
return returnResourceFile(path);
}
private Response returnResourceFile(String fileName) throws IOException {
Response.ResponseBuilder builder;
if (fileName.endsWith(".jpg")) {
byte[] bytes = Utils.readByteArrayFromResource(RESOURCE_PATH + fileName);
builder = Response.ok(bytes);
return builder.build();
}
String text = Utils.readFromResource(RESOURCE_PATH + fileName);
builder = Response.ok(text);
if (fileName.endsWith(".css")) {
builder.type("text/css;charset=utf-8");
} else if (fileName.endsWith(".js")) {
builder.type("text/javascript;charset=utf-8");
}
return builder.build();
}
}
}