/*******************************************************************************
* Copyright (c) 2006-2010 eBay Inc. All Rights Reserved.
* 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
*******************************************************************************/
package org.ebayopensource.turmeric.tools.codegen.builders;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.namespace.QName;
import javax.xml.ws.Dispatch;
import org.ebayopensource.turmeric.runtime.common.exceptions.ServiceInvocationException;
import org.ebayopensource.turmeric.runtime.common.impl.internal.utils.ServiceNameUtils;
import org.ebayopensource.turmeric.runtime.common.impl.utils.LogManager;
import org.ebayopensource.turmeric.runtime.sif.impl.internal.service.BaseServiceProxy;
import org.ebayopensource.turmeric.runtime.sif.service.Service;
import org.ebayopensource.turmeric.tools.codegen.CodeGenContext;
import org.ebayopensource.turmeric.tools.codegen.JTypeTable;
import org.ebayopensource.turmeric.tools.codegen.SourceGenerator;
import org.ebayopensource.turmeric.tools.codegen.exception.CodeGenFailedException;
import org.ebayopensource.turmeric.tools.codegen.exception.PreProcessFailedException;
import org.ebayopensource.turmeric.tools.codegen.util.CodeGenConstants;
import org.ebayopensource.turmeric.tools.codegen.util.CodeGenUtil;
import org.ebayopensource.turmeric.tools.codegen.util.CodeModelUtil;
import org.ebayopensource.turmeric.tools.codegen.util.IntrospectUtil;
import com.sun.codemodel.JArray;
import com.sun.codemodel.JBlock;
import com.sun.codemodel.JCatchBlock;
import com.sun.codemodel.JClass;
import com.sun.codemodel.JCodeModel;
import com.sun.codemodel.JConditional;
import com.sun.codemodel.JDefinedClass;
import com.sun.codemodel.JExpr;
import com.sun.codemodel.JExpression;
import com.sun.codemodel.JFieldRef;
import com.sun.codemodel.JInvocation;
import com.sun.codemodel.JMethod;
import com.sun.codemodel.JMod;
import com.sun.codemodel.JPrimitiveType;
import com.sun.codemodel.JTryBlock;
import com.sun.codemodel.JType;
import com.sun.codemodel.JVar;
/**
* Generates client side Proxy class.
*
* The Proxy class is responsible for invoking service methods
* by hiding low-level network and transport interactions from
* client applications.
*
*
* @author rmandapati
*/
public class ServiceProxyGenerator extends BaseCodeGenerator implements SourceGenerator {
private static final String ASYNC_METHOD_SUFFIX = "Async";
private static final String SERVICE_FIELD_NAME = "m_service";
private static final String FUTURE_OBJECT = "Future<";
private static final String ASYNC_HANDLER_FQN = "javax.xml.ws.AsyncHandler";
private static Logger s_logger = LogManager.getInstance(ServiceProxyGenerator.class);
private static ServiceProxyGenerator s_serviceProxyGenerator =
new ServiceProxyGenerator();
private ServiceProxyGenerator() {
}
public static ServiceProxyGenerator getInstance() {
return s_serviceProxyGenerator;
}
private Logger getLogger() {
return s_logger;
}
public boolean continueOnError() {
return false;
}
public void generate(CodeGenContext codeGenCtx) throws CodeGenFailedException {
JTypeTable jTypeTable = codeGenCtx.getJTypeTable();
JCodeModel jCodeModel = new JCodeModel();
Class<?> serviceInterfaceClass = null;
// Check if Async interface is required and
if(codeGenCtx.isAsyncInterfaceRequired() && codeGenCtx.getServiceAsyncInterfaceClassName() != null){
String asyncClassname = codeGenCtx.getServiceAsyncInterfaceClassName();
try {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
serviceInterfaceClass = Class.forName(asyncClassname, true, cl);
} catch (ClassNotFoundException e) {
getLogger().log(Level.WARNING, "Unable to find Async interface class: " + asyncClassname);
serviceInterfaceClass = jTypeTable.getClazz();
}
}else {
serviceInterfaceClass = jTypeTable.getClazz();
}
String proxyClassName =
getProxyClassName(codeGenCtx, serviceInterfaceClass);
JDefinedClass proxyClass = createNewClass(jCodeModel, proxyClassName);
inheritFromBaseClasses(proxyClass, serviceInterfaceClass, jCodeModel);
// Add instance variables
addConstructor(jCodeModel, proxyClass);
addMethods(proxyClass, jCodeModel, codeGenCtx);
addJavaDocs(proxyClass);
generateJavaFile(codeGenCtx, proxyClass, CodeGenConstants.CLIENT_GEN_FOLDER);
if (codeGenCtx.getInputOptions().isNoCompile() == false) {
compileJavaFilesNoException(
codeGenCtx.getGeneratedJavaFiles(),
codeGenCtx.getBinLocation());
}
getLogger().log(Level.INFO, "Successfully generated " + proxyClassName);
}
private void inheritFromBaseClasses(
JDefinedClass serviceProxyClass,
Class<?> serviceInterfaceClass,
JCodeModel jCodeModel) throws CodeGenFailedException {
JClass baseProxyJClazz = getJClass(BaseServiceProxy.class, jCodeModel);
JClass serviceInterfaceJClazz = getJClass(serviceInterfaceClass, jCodeModel);
JClass narrowedBaseProxyClazz = baseProxyJClazz.narrow(serviceInterfaceJClazz);
extend(serviceProxyClass, narrowedBaseProxyClazz);
implement(serviceProxyClass, serviceInterfaceClass);
}
private String getProxyClassName(CodeGenContext codeGenCtx, Class<?> interfaceClazz) {
String inerfaceClassName = interfaceClazz.getName();
QName serviceQName = codeGenCtx.getServiceQName();
String proxyClassName =
ServiceNameUtils.getServiceProxyClassName(
serviceQName.getLocalPart(),
inerfaceClassName);
return proxyClassName;
}
private void addConstructor(JCodeModel jCodeModel, JDefinedClass proxyClass) {
// generates:
// public <Class Name>(Service service) {
// m_service = service;
// }
JMethod proxyConstructor = proxyClass.constructor(JMod.PUBLIC);
JVar serviceParam = proxyConstructor.param(getServiceClass(), "service");
// generates m_service = service;
JBlock contrcutorBody = proxyConstructor.body();
JInvocation superInvoker = contrcutorBody.invoke("super");
superInvoker.arg(serviceParam);
}
private void addMethods(
JDefinedClass proxyClass,
JCodeModel jCodeModel,
CodeGenContext codeGenCtx) throws CodeGenFailedException {
JTypeTable jTypeTable = null;
if(codeGenCtx.isAsyncInterfaceRequired() && codeGenCtx.getServiceAsyncInterfaceClassName() != null){
String asyncClassname = codeGenCtx.getServiceAsyncInterfaceClassName();
try {
jTypeTable = IntrospectUtil.initializeAsyncInterfaceJType(asyncClassname);
} catch (PreProcessFailedException e) {
getLogger().log(Level.WARNING, "Unable to initialize async interface jtype: " + asyncClassname, e);
// TODO Auto-generated catch block
jTypeTable = codeGenCtx.getJTypeTable();
//e.printStackTrace();
}
}else {
jTypeTable = codeGenCtx.getJTypeTable();
}
List<Method> interfaceMethods = jTypeTable.getMethods();
Map<String, Method> nameToMethodMap = createNameToMethodMap(interfaceMethods);
// Add business methods
JMethod[] jServiceMethods =
addSyncAsyncMethods(proxyClass, interfaceMethods,jCodeModel,codeGenCtx);
// Add logic inside methods
implementMethods(proxyClass, jServiceMethods, jCodeModel, nameToMethodMap,codeGenCtx);
}
private Map<String, Method> createNameToMethodMap(List<Method> methods) {
Map<String, Method> nameToMethodMap = new HashMap<String, Method>();
for (Method method : methods) {
nameToMethodMap.put(method.getName(), method);
}
return nameToMethodMap;
}
private JMethod[] addSyncAsyncMethods(
JDefinedClass jDefinedClass,
List<Method> methods, JCodeModel jCodeModel, CodeGenContext codeGenCtx) {
List<JMethod> listOfMethods = new ArrayList<JMethod>();
for (Method method : methods) {
// Add Sync call method with same signature
JMethod methodAdded = null;
boolean isAsynchPollMethod = false;
if(method.getName().equals(CodeGenConstants.POLL_METHOD_NAME))
if(!isPollMethodAnInterfaceMethod(method))
isAsynchPollMethod = true;
if(isAsynchPollMethod && codeGenCtx.isGeneratePollMethod()){
methodAdded = CodeModelUtil.getInstance().generatePollMethod(jCodeModel, jDefinedClass) ;
}
else
methodAdded = addMethod(jDefinedClass, method);
listOfMethods.add(methodAdded);
// Add Async call method with extra CallbackHandler parameter
/*Type[] paramTypes = method.getGenericParameterTypes();
Type[] asyncMethodParams = new Type[paramTypes.length+1];
System.arraycopy(paramTypes, 0, asyncMethodParams, 0, paramTypes.length);
asyncMethodParams[asyncMethodParams.length-1] = CallbackHandler.class;
jMethods[index] =
addMethod(jDefinedClass,
getAsyncMethodName(method),
asyncMethodParams,
method.getGenericExceptionTypes(),
method.getGenericReturnType());
index++;
*/
}
JMethod[] jMethodsArray = listOfMethods.toArray(new JMethod[0]);
return jMethodsArray;
}
private Class<?> getServiceClass() {
return Service.class;
}
/*private String getAsyncMethodName(Method method) {
return method.getName() + ASYNC_METHOD_SUFFIX;
}*/
private void implementMethods(
JDefinedClass proxyClass,
JMethod[] jServiceMethods,
JCodeModel jCodeModel,
Map<String, Method> nameToMethodMap, CodeGenContext codeGenCtx) throws CodeGenFailedException {
JFieldRef serviceFieldRef = JExpr.ref(SERVICE_FIELD_NAME);
for (JMethod serviceMethod : jServiceMethods) {
// don't generate normal interface method code for the poll method, its not a method related to any of the WSDL operations
if (serviceMethod.name().equals(CodeGenConstants.POLL_METHOD_NAME)){
Method method = nameToMethodMap.get(CodeGenConstants.POLL_METHOD_NAME);
if( ! isPollMethodAnInterfaceMethod(method)){
implementAsynchPollMethod(proxyClass,jCodeModel,serviceMethod);
continue;
}
}
if (serviceMethod.name().endsWith(ASYNC_METHOD_SUFFIX)) {
Method interfaceAsyncMethod = nameToMethodMap.get(serviceMethod.name());
addASyncMethodLogic(serviceMethod, interfaceAsyncMethod, serviceFieldRef, jCodeModel,codeGenCtx);
} else {
Method interfaceMethod = nameToMethodMap.get(serviceMethod.name());
addSyncMethodLogic(serviceMethod, interfaceMethod, serviceFieldRef, jCodeModel,codeGenCtx);
}
}
}
private void implementAsynchPollMethod(JDefinedClass proxyClass, JCodeModel jCodeModel, JMethod pollMethod) {
JBlock pollMethodBody = pollMethod.body();
/*
* return m_service.poll(block, partial);
*/
JFieldRef serviceFieldRef = JExpr.ref(SERVICE_FIELD_NAME);
JInvocation invocation = serviceFieldRef.invoke(CodeGenConstants.POLL_METHOD_NAME);
invocation.arg(JExpr.ref(CodeGenConstants.POLL_METHOD_PARAM_BLOCK));
invocation.arg(JExpr.ref(CodeGenConstants.POLL_METHOD_PARAM_PARTIAL));
pollMethodBody._return(invocation);
}
/**
* given a Method of name "poll", this method verifies whether this "poll" method is same as the asynch "poll" method by checking
* a) numbr of params
* b) type of params
*
* @param method
* @return
*/
private boolean isPollMethodAnInterfaceMethod(Method method) {
Class<?>[]paramTypes = method.getParameterTypes();
if(paramTypes != null && paramTypes.length == 2){
if(paramTypes[0] == boolean.class && paramTypes[1] == boolean.class)
return false;
}
return true;
}
private void addSyncMethodLogic(
JMethod serviceMethod,
Method interfaceMethod,
JFieldRef serviceFieldRef,
JCodeModel jCodeModel, CodeGenContext codeGenCtx) throws CodeGenFailedException {
JBlock methodBody = serviceMethod.body();
JVar[] paramValues = serviceMethod.listParams();
Class<?>[] exceptionTypes = interfaceMethod.getExceptionTypes();
//---------------------------------------------------------------
// generates:
// Object[] params = new Object[<Size>];
//---------------------------------------------------------------
JClass objClazz = getJClass(Object.class, jCodeModel);
JClass objClazzArray = objClazz.array();
JVar paramObjArray = methodBody.decl(objClazzArray, "params");
JArray objValueArray = JExpr.newArray(objClazz, paramValues.length);
paramObjArray.init(objValueArray);
// if method accepts any parameters, copy them into object array
//---------------------------------------------------------------
// generates
// params[0] = <method param 1>;
// params[1] = <method param 2>
//---------------------------------------------------------------
if (paramValues != null && paramValues.length > 0) {
for (int i = 0; i < paramValues.length; i++) {
methodBody.assign(
JExpr.component(paramObjArray, JExpr.lit(i)),
paramValues[i]);
}
}
//---------------------------------------------------------------
// generates:
// List<Object> returnParamList = new ArrayList<Object>();
//---------------------------------------------------------------
JClass listClazz = getJClass(List.class, jCodeModel).narrow(Object.class);
JClass arrayListClazz = getJClass(ArrayList.class, jCodeModel).narrow(Object.class);
JInvocation arrayListInvoker = JExpr._new(arrayListClazz);
JVar returnParamVar =
methodBody.decl(listClazz, "returnParamList", arrayListInvoker);
// Add try-catch block
JTryBlock tryBlock = methodBody._try();
JBlock tryBlockBody = tryBlock.body();
//---------------------------------------------------------------
// generates:
// m_service.invoke(<Operation Name>, <Param Array>, <Return Param List>
//---------------------------------------------------------------
String javaMethodName = serviceMethod.name();
String operationName = codeGenCtx.getJavaMethodOperationNameMap().get(javaMethodName);
if(CodeGenUtil.isEmptyString(operationName))
operationName = javaMethodName;
JInvocation serviceInvoker = serviceFieldRef.invoke("invoke");
serviceInvoker.arg(operationName);
serviceInvoker.arg(paramObjArray);
serviceInvoker.arg(returnParamVar);
tryBlockBody.add(serviceInvoker);
JCatchBlock jCatchBlock =
tryBlock._catch(getJClass(ServiceInvocationException.class, jCodeModel));
JVar svcInvocationExVar = jCatchBlock.param("svcInvocationEx");
JBlock catchBody = jCatchBlock.body();
if (exceptionTypes.length > 0) {
//---------------------------------------------------------------
// generates :
// if (svcInvocationEx.getErrorResponse()!= null) { ...}
//---------------------------------------------------------------
/*JConditional ifErrResponse = catchBody._if(
svcInvocationExVar.invoke("getErrorResponse").ne(JExpr._null()));
JBlock ifErrResponseThen = ifErrResponse._then();*/
//---------------------------------------------------------------
// generates :
// Throwable exception = decodeErrorResponse(
// svcInvocationEx.getErrorResponse(),
// new Class[] {Exception1.class, Exception2.class});
//---------------------------------------------------------------
/*JClass jClass = getJClass(Class.class, jCodeModel);*/
//---------------------------------------------------------------
// generates :
// new Class[] {Exception1.class, Exception2.class ...}
//---------------------------------------------------------------
/*JArray jClassArray = JExpr.newArray(jClass);
for (int i = 0; i < exceptionTypes.length; i++) {
jClassArray.add(JExpr.dotclass(getJClass(exceptionTypes[i], jCodeModel)));
}
JInvocation decodeExInvoker = JExpr.invoke("decodeErrorResponse");
decodeExInvoker.arg(svcInvocationExVar.invoke("getErrorResponse"));
decodeExInvoker.arg(jClassArray);
JClass throwableJClass = getJClass(Throwable.class, jCodeModel);
JVar thExVar = ifErrResponseThen.decl(throwableJClass, "th");
thExVar.init(decodeExInvoker); */
//---------------------------------------------------------------
// generates :
// svcInvocationEx.setApplicationException(exception);
//---------------------------------------------------------------
/*JInvocation setAppExInvocation =
ifErrResponseThen.invoke(svcInvocationExVar, "setApplicationException");
setAppExInvocation.arg(thExVar);*/
JConditional ifAppOnlyEx = catchBody._if(
svcInvocationExVar.invoke("isAppOnlyException"));
JBlock ifAppOnlyExThen = ifAppOnlyEx._then();
JInvocation getAppExInvoker = svcInvocationExVar.invoke("getApplicationException");
JClass throwableJClass = getJClass(Throwable.class, jCodeModel);
JVar appExVar = ifAppOnlyExThen.decl(throwableJClass, "appEx", getAppExInvoker);
for (int i = 0; i < exceptionTypes.length; i++) {
//---------------------------------------------------------------
// generates:
//if (th instanceof Exception) {
// throw((Exception) th);
//}
//---------------------------------------------------------------
JConditional ifExInstCheck = ifAppOnlyExThen._if(
appExVar._instanceof(getJClass(exceptionTypes[i], jCodeModel)));
JBlock ifExInstCheckThen = ifExInstCheck._then();
ifExInstCheckThen._throw(
JExpr.cast(getJClass(exceptionTypes[i], jCodeModel), appExVar));
}
}
//---------------------------------------------------------------
// generates :
// throw wrapInvocationException(svcInvocationEx);
//---------------------------------------------------------------
JInvocation wrapExInvoker = JExpr.invoke("wrapInvocationException");
wrapExInvoker.arg(svcInvocationExVar);
catchBody._throw(wrapExInvoker);
/******************************************************************
* :: Method Return Logic :: *
******************************************************************/
JType returnType = serviceMethod.type();
// For methods retuning nothing (void)
if ( returnType == null || returnType == jCodeModel.VOID) {
methodBody._return();
}
else {
JInvocation returnParamInvoker =
returnParamVar.invoke("get").arg(JExpr.lit(0));
// for primitive return types (like long, int, boolean etc)
if (returnType.isPrimitive()) {
JType wrapperReturnType = returnType.boxify();
JExpression returnParamExpr =
JExpr.cast(wrapperReturnType, returnParamInvoker);
//---------------------------------------------------------------
// generates:
// <WrapperType> result = (<WrapperType>) returnParamList.get(0);
// ex: Long result = (Long) returnParamList.get(0)
//---------------------------------------------------------------
JVar result =
methodBody.decl(
wrapperReturnType, "result", returnParamExpr);
// Unwraps and returns only primitive type (long, int, boolean)
JPrimitiveType primitiveType = (JPrimitiveType) returnType;
methodBody._return(primitiveType.unwrap(result));
}
else { // for object based return types
JExpression returnParamExpr =
JExpr.cast(returnType, returnParamInvoker);
//---------------------------------------------------------------
// generates:
// <Return Type> result = (<Return Type>) returnParamList.get(0);
//---------------------------------------------------------------
JVar result = methodBody.decl(returnType, "result", returnParamExpr);
//---------------------------------------------------------------
// generates:
// return result;
//---------------------------------------------------------------
methodBody._return(result);
}
}
}
private void addASyncMethodLogic(
JMethod serviceMethod,
Method asyncInterfaceMethod,
JFieldRef serviceFieldRef,
JCodeModel jCodeModel, CodeGenContext codeGenCtx) throws CodeGenFailedException {
JBlock methodBody = serviceMethod.body();
//---------------------------------------------------------------
// generates:
// ServiceDisptach dispatch = m_service.createDisptach();
//---------------------------------------------------------------
// JClass svcJClass = getJClass(Dispatch.class, jCodeModel);
String javaMethodName = getJavaMethodNameFromAsynchMethodName(serviceMethod.name());
String operationName = codeGenCtx.getJavaMethodOperationNameMap().get(javaMethodName);
if(CodeGenUtil.isEmptyString(operationName))
operationName = javaMethodName;
JClass requestMsgJClass = jCodeModel.ref(Dispatch.class);
JInvocation serviceInvoker = serviceFieldRef.invoke("createDispatch");
serviceInvoker.arg(operationName);
JVar operationNameVar =
methodBody.decl(requestMsgJClass, "dispatch", serviceInvoker);
// JClass responseMsgJClass = jCodeModel.ref(Dispatch.class);
// JType returnJType = null;
// Type returnType = asyncInterfaceMethod.getGenericReturnType();
JType returnType = serviceMethod.type();
JInvocation asyncInvoker = operationNameVar.invoke("invokeAsync");
//String operationName = getOperationName(serviceMethod.name());
//asyncInvoker.arg(operationName); // Need to truncate the Async suffix
JVar resultVar =
methodBody.decl(returnType, "result", asyncInvoker);
//Type[] paramTypes = asyncInterfaceMethod.getGenericParameterTypes();
JVar[] paramValues = serviceMethod.listParams();
if (paramValues != null && paramValues.length > 0) {
for (int i = 0; i < paramValues.length; i++) {
// For handling operations with no arguments
if((paramValues.length == 1) && (returnType.name().startsWith(FUTURE_OBJECT))
&& (paramValues[i].type().fullName().startsWith(ASYNC_HANDLER_FQN)) ) {
asyncInvoker.arg(JExpr._null());
}
asyncInvoker.arg(paramValues[i]);
}
} else {
asyncInvoker.arg(JExpr._null());
}
if (resultVar != null) {
methodBody._return(resultVar);
} else {
methodBody._return();
}
}
private String getJavaMethodNameFromAsynchMethodName(String asyncOperationName){
String operationName = "";
int index = asyncOperationName.indexOf(ASYNC_METHOD_SUFFIX);
operationName = asyncOperationName.substring(0,index);
return operationName;
}
public String getFilePath(String serviceAdminName, String interfaceName){
return null;
}
}