/************************************************************************ * Copyright (c) 2011-2012 HONG LEIMING. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to permit * persons to whom the Software is furnished to do so, subject to the * following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE * USE OR OTHER DEALINGS IN THE SOFTWARE. ***************************************************************************/ package org.zbus.client.rpc; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; import org.zbus.client.service.ServiceHandler; import org.zbus.common.logging.Logger; import org.zbus.common.logging.LoggerFactory; import org.zbus.common.remoting.Message; class MethodInstance{ public Method method; public Object instance; public MethodInstance(Method method, Object instance){ this.method = method; this.instance = instance; } @Override public String toString() { return "MethodInstance [method=" + method + ", instance=" + instance + "]"; } } public class RpcServiceHandler implements ServiceHandler { private static final Logger log = LoggerFactory.getLogger(RpcServiceHandler.class); private static final Codec codec = new JsonCodec();//TODO configurable private Map<String,MethodInstance> methods = new HashMap<String, MethodInstance>(); public RpcServiceHandler(Object... services){ addModule(services); } public void addModule(Object... services){ for(Object obj : services){ for(Class<?> intf : obj.getClass().getInterfaces()){ addModule(intf.getSimpleName(), obj); addModule(intf.getCanonicalName(), obj); } addModule(obj.getClass().getSimpleName(), obj); addModule(obj.getClass().getCanonicalName(), obj); } } public void addModule(String module, Object... services){ for(Object service: services){ this.initCommandTable(module, service); } } private void initCommandTable(String module, Object service){ try { Method [] methods = service.getClass().getMethods(); for (Method m : methods) { String method = m.getName(); Remote cmd = m.getAnnotation(Remote.class); if(cmd != null){ method = cmd.id(); if(cmd.exclude()) continue; if("".equals(method)){ method = m.getName(); } } String paramMD5 = ""; Class<?>[] paramTypes = m.getParameterTypes(); StringBuilder sb = new StringBuilder(); for(int i=0;i<paramTypes.length;i++){ sb.append(paramTypes[i].getCanonicalName()); } paramMD5 = sb.toString(); String key = module + ":" + method+":"+paramMD5; String key2 = module + ":" + method; if(this.methods.containsKey(key)){ log.error(key + " duplicated"); } else { log.debug("register "+service.getClass().getSimpleName()+"\t" + key); log.debug("register "+service.getClass().getSimpleName()+"\t" + key2); } m.setAccessible(true); MethodInstance mi = new MethodInstance(m, service); this.methods.put(key, mi); this.methods.put(key2, mi); } } catch (SecurityException e) { log.error(e.getMessage(), e); } } private static boolean isBlank(String value){ return (value == null || "".equals(value.trim())); } private Request decodeRequest(Message msg){ Request req = codec.decodeRequest(msg); Request.normalize(req); //json未设置好的module或者params标准化 if(isBlank(req.getMethod())){ throw new IllegalArgumentException("missing method name"); } return req; } private MethodInstance findMethod(Request req){ String paramTypesMD5 = ""; if(req.getParamTypes() != null){ for(String type : req.getParamTypes()){ paramTypesMD5 += type; } } String module = req.getModule(); String method = req.getMethod(); String key = module+":"+method+":"+paramTypesMD5;//支持语言多态 String key2 = module+":"+method; if(this.methods.containsKey(key)){ return this.methods.get(key); } else { if(this.methods.containsKey(key2)){ return this.methods.get(key2); } else { String errorMsg = String.format("%s:%s not found, module may not set, or wrong", module, method); throw new IllegalArgumentException(errorMsg); } } } private void checkParamTypes(MethodInstance target, Request req){ Class<?>[] targetParamTypes = target.method.getParameterTypes(); if(targetParamTypes.length != req.getParams().length){ String requiredParamTypeString = ""; for(int i=0;i<targetParamTypes.length;i++){ Class<?> paramType = targetParamTypes[i]; requiredParamTypeString += paramType.getName(); if(i<targetParamTypes.length-1){ requiredParamTypeString += ", "; } } Object[] params = req.getParams(); String gotParamsString = ""; for(int i=0;i<params.length;i++){ gotParamsString += params[i]; if(i<params.length-1){ gotParamsString += ", "; } } String errorMsg = String.format("Method:%s(%s), called with %s(%s)", target.method.getName(), requiredParamTypeString, target.method.getName(), gotParamsString); throw new IllegalArgumentException(errorMsg); } } @Override public Message handleRequest(Message msg){ Response resp = new Response(); try { Request req = decodeRequest(msg); MethodInstance target = findMethod(req); checkParamTypes(target, req); Class<?>[] targetParamTypes = target.method.getParameterTypes(); Object[] invokeParams = new Object[targetParamTypes.length]; Object[] reqParams = req.getParams(); for(int i=0; i<targetParamTypes.length; i++){ invokeParams[i] = codec.normalize(reqParams[i], targetParamTypes[i]); } Object result = target.method.invoke(target.instance, invokeParams); resp.setResult(result); resp.setEncoding(msg.getEncoding()); } catch (InvocationTargetException e) { resp.setError(e.getTargetException()); } catch (Throwable e) { resp.setError(e); } try{ return codec.encodeResponse(resp); } catch (Throwable e) { log.error(e.getMessage(), e.getCause()); } return null; //should not here } }