/** * Tencent is pleased to support the open source community by making MSEC available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the GNU General Public 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 * * https://opensource.org/licenses/GPL-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.msec.rpc; import api.log.msec.org.AccessLog; import api.monitor.msec.org.AccessMonitor; import com.google.protobuf.Message; import com.google.protobuf.MessageLite; import com.google.protobuf.Parser; import org.apache.log4j.Logger; import org.apache.log4j.PropertyConfigurator; import org.ini4j.Ini; import org.ini4j.Profile; import org.msec.net.NettyClient; import org.msec.net.NettyServer; import java.io.File; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.InetAddress; import java.net.NetworkInterface; import java.util.*; import java.util.concurrent.ConcurrentHashMap; public final class ServiceFactory { private static Logger log = Logger.getLogger(ServiceFactory.class.getName()); public static final int SRPC_VERSION = 10000; public static final int DEFAULT_PORT = 7963; public static final String SRPC_CONF_PATH = "etc/config.ini"; public static final String LOG4J_CONF_PATH = "etc/log4j.properties"; public static final String MONITOR_AGENT_MMAP_PATH = "/msec/agent/monitor/monitor.mmap"; public static long uniqueSequence = 0; public static synchronized long generateSequence() { if (uniqueSequence == 0) { uniqueSequence = new Random(System.currentTimeMillis()).nextLong(); uniqueSequence = uniqueSequence & 0x7fffffff; if (uniqueSequence == 0) ++uniqueSequence; } return uniqueSequence++; } public static String getVersionString(int version) { int major = version / 10000; int minor = (version % 10000) / 100; int patch = version % 100; return Integer.toString(major) + "." + minor + "." + patch; } protected static String moduleName = null; protected static String listenIP = null; protected static int listenPort = DEFAULT_PORT; protected static String listenType = "tcp"; protected static boolean initSucc = false; public static synchronized void initModule(String moduleName, int version) { PropertyConfigurator.configure(LOG4J_CONF_PATH); try { if (version != SRPC_VERSION) { throw new Exception("SRPC version not compatible: " + getVersionString(version) + ", " + getVersionString(SRPC_VERSION)); } log.info("SRPC version: " + getVersionString(SRPC_VERSION)); if (ServiceFactory.moduleName == null) { ServiceFactory.moduleName = moduleName; AccessLog.initLog(SRPC_CONF_PATH); AccessMonitor.initialize(MONITOR_AGENT_MMAP_PATH); AccessMonitor.initServiceName(moduleName); } parseListenConf(); initSucc = true; } catch (Exception e) { log.error("init module failed. ", e); } } public static String getModuleName() { if (moduleName != null && !moduleName.isEmpty()) return ServiceFactory.moduleName; else return "Noname-Module"; } public static void parseListenConf() throws Exception { String listenConf = getConfig("SRPC", "listen"); if (listenConf != null) { int pos = listenConf.indexOf(' '); if (pos >= 0) listenConf = listenConf.substring(0, pos); pos = listenConf.indexOf(':'); if (pos > 0) { listenIP = listenConf.substring(0, pos); listenConf = listenConf.substring(pos+1); } pos = listenConf.indexOf('/'); String listenPortStr = listenConf; if (pos > 0) { listenPortStr = listenConf.substring(0, pos); listenType = listenConf.substring(pos+1); if (listenType.compareToIgnoreCase("tcp") != 0 && listenType.compareToIgnoreCase("udp") != 0) { throw new Exception("Invalid listen type: " + listenType); } } try { listenPort = Integer.valueOf(listenPortStr); } catch (NumberFormatException ex) { throw new Exception("Invalid listen port: " + listenPortStr); } if (listenIP != null && !listenIP.isEmpty()) { Enumeration interfaces = NetworkInterface.getNetworkInterfaces(); while (interfaces.hasMoreElements()) { NetworkInterface intf = (NetworkInterface) interfaces.nextElement(); if (intf.getName().compareToIgnoreCase(listenIP) != 0) continue; // Enumerate InetAddresses of this network interface Enumeration addresses = intf.getInetAddresses(); while (addresses.hasMoreElements()) { InetAddress address = (InetAddress) addresses.nextElement(); listenIP = address.getHostAddress(); break; } } } } } public static String getConfig(String section, String key) { Ini ini = new Ini(); try { ini.load(new File(SRPC_CONF_PATH)); Profile.Section sec = ini.get(section); if (sec != null) { return sec.get(key); } } catch (IOException e) { log.error("get config " + section + ":" + key + " failed.", e); } return null; } protected static Map<String, Map<String, List<ServiceMethodEntry>>> serviceMethodMap = new ConcurrentHashMap<String, Map<String, List<ServiceMethodEntry>>>(); public static final class ServiceMethodEntry { private Object service; private Method method; private Class<?> paramType; private Class<?> returnType; private Parser paramTypeParser; private Parser returnTypeParser; private Message.Builder paramTypeBuilder; public ServiceMethodEntry(Object service, Method method) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { this.service = service; this.method = method; this.paramType = method.getParameterTypes()[1]; this.returnType = method.getReturnType(); this.paramTypeParser = ((MessageLite)this.paramType.getMethod("getDefaultInstance").invoke(null)).getParserForType(); this.returnTypeParser = ((MessageLite)this.returnType.getMethod("getDefaultInstance").invoke(null)).getParserForType(); this.paramTypeBuilder = (Message.Builder)this.paramType.getMethod("newBuilder").invoke(null); } public Object invoke(Object argument) throws InvocationTargetException, IllegalAccessException { return method.invoke(service, null, argument); } public Object getService() { return service; } public Method getMethod() { return method; } public Class<?> getParamType() { return paramType; } public Class<?> getReturnType() { return returnType; } public Parser getParamTypeParser() { return paramTypeParser; } public Parser getReturnTypeParser() { return returnTypeParser;} public Message.Builder getParamTypeBuilder() { return paramTypeBuilder; } @Override public String toString() { return "ServiceMethodEntry{" + "service=" + service + ", method=" + method + ", paramType=" + paramType + ", returnType=" + returnType + '}'; } } public static Set<String> ignoreMethodNames = new HashSet<String>(); static { Method[] methods1 = Object.class.getMethods(); for (Method oneMethod: methods1) { ignoreMethodNames.add(oneMethod.getName()); } Method[] methods2 = Class.class.getMethods(); for (Method oneMethod: methods2) { ignoreMethodNames.add(oneMethod.getName()); } ignoreMethodNames.add("main"); } public static Map<String, Map<String, List<ServiceMethodEntry>>> getServiceMethodMap() { return serviceMethodMap; } public static void runService() { try { if (!initSucc) return; NettyServer server; if (listenIP == null) { server = new NettyServer(listenPort); } else { server = new NettyServer(listenIP, listenPort); } server.start(); System.in.read(); } catch (Exception e) { log.error("run service failed. ", e); } } public static <T> void addService(Class<T> serviceClass, T serviceObj) { addService(serviceClass.getCanonicalName(), serviceClass, serviceObj); } public static <T> void addService(String serviceKeyName, Class<T> serviceClass, T serviceObj) { addService(serviceKeyName, serviceClass, serviceObj, DEFAULT_PORT); } public static <T> void addService(String serviceKeyName, Class<T> serviceClass, T serviceObj, int port) { Map<String, List<ServiceMethodEntry>> methodMap = serviceMethodMap.get(serviceKeyName); if (methodMap == null) { methodMap = new ConcurrentHashMap<String, List<ServiceMethodEntry>>(); serviceMethodMap.put(serviceKeyName, methodMap); } List<ServiceMethodEntry> entries; Method[] allMethods = serviceObj.getClass().getMethods(); String paramTypeName = null; String returnTypeName = null; for (Method oneMethod: allMethods) { if (!ignoreMethodNames.contains(oneMethod.getName())) { oneMethod.setAccessible(true); entries = methodMap.get(oneMethod.getName()); if (entries == null) { entries = new ArrayList<ServiceMethodEntry>(); methodMap.put(oneMethod.getName(), entries); } try { paramTypeName = oneMethod.getParameterTypes()[1].getName(); returnTypeName = oneMethod.getReturnType().getName(); Class.forName(paramTypeName); Class.forName(returnTypeName); entries.add(new ServiceMethodEntry(serviceObj, oneMethod)); } catch (Exception e) { log.error("Load class failed: " + paramTypeName + " " + returnTypeName, e); } } } } public static String underscoresToCamelCase(String input, boolean cap_next_letter) { String result = ""; for (int i = 0; i < input.length(); i++) { if ('a' <= input.charAt(i) && input.charAt(i) <= 'z') { if (cap_next_letter) { result += Character.toUpperCase(input.charAt(i)); } else { result += input.charAt(i); } cap_next_letter = false; } else if ('A' <= input.charAt(i) && input.charAt(i) <= 'Z') { if (i == 0 && !cap_next_letter) { result += Character.toLowerCase(input.charAt(i)); } else { result += input.charAt(i); } cap_next_letter = false; } else if ('0' <= input.charAt(i) && input.charAt(i) <= '9') { result += input.charAt(i); cap_next_letter = true; } else { cap_next_letter = true; } } return result; } public static ServiceMethodEntry getServiceMethodEntry(String serviceKeyName, String methodName) { Map<String, List<ServiceMethodEntry>> methodMap = serviceMethodMap.get(serviceKeyName); if (methodMap == null) { return null; } List<ServiceMethodEntry> entries = methodMap.get(methodName); if (entries == null) { entries = methodMap.get(underscoresToCamelCase(methodName, false)); if (entries == null) return null; } return entries.get(0); } public static MessageLite callMethod(String moduleName, String serviceMethodName, MessageLite request, MessageLite responseInstance, int timeoutMillis) throws Exception { MessageLite ret = null; NettyClient client = null; //System.out.println("GetClient begin: " + System.currentTimeMillis()); try { client = ClientManager.getClient(moduleName, true); } catch (Exception e) { log.error("get route for [" + moduleName + "] failed.", e); return ret; } //System.out.println("AfterConnect: " + System.currentTimeMillis()); int pos = serviceMethodName.lastIndexOf('.'); if (pos == -1 || pos == 0 || pos == serviceMethodName.length() - 1) { throw new Exception("Invalid serviceMethodName. Must be in format like *.*"); } String serviceName = serviceMethodName.substring(0, pos); String methodName = serviceMethodName.substring(pos + 1); RpcRequest rpcRequest = new RpcRequest(); rpcRequest.setSeq(generateSequence()); rpcRequest.setServiceName(serviceName); rpcRequest.setMethodName(methodName); rpcRequest.setParameter(request); RpcContext context = (RpcContext)RequestProcessor.getThreadContext("session"); if (context != null) { rpcRequest.setFlowid(context.getRequest().getFlowid()); } RpcResponse rpcResponse = client.sendRequestAndWaitResponse(rpcRequest, timeoutMillis); if (rpcResponse != null) { if (rpcResponse.getErrno() == 0) { ret = responseInstance.getParserForType().parseFrom((byte[]) rpcResponse.getResultObj()); return ret; } else if (rpcResponse.getError() != null){ throw rpcResponse.getError(); } else { throw new Exception("Rpc failed: errno = " + rpcResponse.getErrno()); } } return ret; } //������ʹ�ó����ӣ�������ʵ��SEQ���ƣ��Է����� public static byte[] callMethod(String moduleName, String serviceMethodName, CustomPackageCodec packageCodec, int timeoutMillis) throws Exception { byte[] ret = null; NettyClient client = null; try { client = ClientManager.getClient(moduleName, true); } catch (Exception e) { log.error("get route for [" + moduleName + "] failed.", e); return ret; } int pos = serviceMethodName.lastIndexOf('.'); if (pos == -1 || pos == 0 || pos == serviceMethodName.length() - 1) { throw new Exception("Invalid serviceMethodName. Must be in format like *.*"); } String serviceName = serviceMethodName.substring(0, pos); String methodName = serviceMethodName.substring(pos + 1); RpcRequest rpcRequest = new RpcRequest(); rpcRequest.setSeq(generateSequence()); rpcRequest.setServiceName(serviceName); rpcRequest.setMethodName(methodName); rpcRequest.setParameter(null); RpcResponse rpcResponse = client.sendRequestAndWaitResponse(packageCodec, rpcRequest, timeoutMillis); if (rpcResponse != null) { if (rpcResponse.getErrno() == 0) { ret = (byte[]) rpcResponse.getResultObj(); return ret; } else if (rpcResponse.getError() != null){ throw rpcResponse.getError(); } else { throw new Exception("Rpc failed: errno = " + rpcResponse.getErrno()); } } return ret; } //������ʹ�ö����� public static byte[] callMethod(String moduleName, String serviceMethodName, CustomPackageLengthChecker lengthChecker, byte[] requestData, int timeoutMillis) throws Exception { byte[] ret = null; NettyClient client = null; try { client = ClientManager.getClient(moduleName, false); } catch (Exception e) { log.error("get route for [" + moduleName + "] failed.", e); return ret; } //System.out.println("AfterConnect: " + System.currentTimeMillis()); int pos = serviceMethodName.lastIndexOf('.'); if (pos == -1 || pos == 0 || pos == serviceMethodName.length() - 1) { throw new Exception("Invalid serviceMethodName. Must be in format like *.*"); } String serviceName = serviceMethodName.substring(0, pos); String methodName = serviceMethodName.substring(pos + 1); RpcRequest rpcRequest = new RpcRequest(); rpcRequest.setSeq(generateSequence()); rpcRequest.setServiceName(serviceName); rpcRequest.setMethodName(methodName); rpcRequest.setParameter(null); RpcResponse rpcResponse = client.sendRequestAndWaitResponse(lengthChecker, requestData, rpcRequest, timeoutMillis); //System.out.println("WaitFinished: " + System.currentTimeMillis()); client.close(); if (rpcResponse != null) { if (rpcResponse.getErrno() == 0) { ret = (byte[]) rpcResponse.getResultObj(); return ret; } else if (rpcResponse.getError() != null){ throw rpcResponse.getError(); } else { throw new Exception("Rpc failed: errno = " + rpcResponse.getErrno()); } } return ret; } }