/******************************************************************************* * 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.validator; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import org.ebayopensource.turmeric.tools.codegen.exception.PreValidationFailedException; import org.ebayopensource.turmeric.tools.codegen.util.CodeGenUtil; import org.ebayopensource.turmeric.tools.codegen.util.IntrospectUtil; import org.ebayopensource.turmeric.runtime.codegen.common.InputParamType; import org.ebayopensource.turmeric.runtime.codegen.common.InterfaceDefType; import org.ebayopensource.turmeric.runtime.codegen.common.MethodDefListType; import org.ebayopensource.turmeric.runtime.codegen.common.MethodDefType; /** * Provides an API for validating service interface, implementation * class according to rules set by SOA framework. * * * @author rmandapati */ public class SourceValidator { private static final String OVERLOADED_METHOD_MSG = "Method overloading is not allowed"; private static final String MULTIPLE_PARAMS_MSG = "Method cannot have multiple parameters"; private static final String COLLECTION_TYPES_MSG = "Collection types are not allowed as input or output types"; private static final String NESTED_COLLECTION_TYPES_MSG = "Nested collection types are not allowed as input or output types"; private static Set<String> s_primitiveTypes = new HashSet<String>(); static { s_primitiveTypes.add("void"); s_primitiveTypes.add("byte"); s_primitiveTypes.add("boolean"); s_primitiveTypes.add("short"); s_primitiveTypes.add("int"); s_primitiveTypes.add("long"); s_primitiveTypes.add("float"); s_primitiveTypes.add("double"); } public static void main(String[] args) { if ((args == null) || (args.length != 2) || !(args[0].equals("-interface") || args[0].equals("-class"))) { printUsage(); } else { try { if (args[0].equals("-interface")) { validateServiceInterface(args[1]); } else { validateClassForService(args[1]); } } catch (PreValidationFailedException ex) { } } } private static void printUsage() { System.out.println("Usage :"); //KEEPME System.out.println("java [-options] org.ebayopensource.turmeric.runtime.tools.codegen.validator.SourceValidator [args]"); //KEEPME System.out.println("Where args include : "); //KEEPME System.out.println("-interface|-class <FullyQualifiedName>"); //KEEPME } public static List<MessageObject> validateServiceInterface( String qualifiedInterfaceName) throws PreValidationFailedException { Class serviceInterfaceClass = null; try { serviceInterfaceClass = IntrospectUtil.loadClass(qualifiedInterfaceName); } catch (ClassNotFoundException clsNotFound) { throw new PreValidationFailedException(clsNotFound.getMessage(), clsNotFound); } return validateServiceInterface(serviceInterfaceClass); } public static List<MessageObject> validateServiceInterface( Class serviceIntfClass) throws PreValidationFailedException { List<MessageObject> errorList = new ArrayList<MessageObject>(); if (!serviceIntfClass.isInterface()) { String serviceInterfaceName = serviceIntfClass.getName(); String errorMsg = serviceInterfaceName + " : is not an interface"; MessageObject errMsgObj = getErrorMsg(errorMsg, true); errorList.add(errMsgObj); } validateTypesReferred(serviceIntfClass, errorList); Method[] allMethods = serviceIntfClass.getDeclaredMethods(); return validateMethods(allMethods, errorList); } private static void validateTypesReferred( Class serviceIntfClass, List<MessageObject> errorList) { Set<String> allTypesSet = new HashSet<String>(); List<String> noArgConstructorTypes = new ArrayList<String>(); List<String> finalTypes = new ArrayList<String>(); IntrospectUtil.addAllTypesReferred(serviceIntfClass, allTypesSet); for (String typeName : allTypesSet) { Class typeClass = null; try { typeClass = IntrospectUtil.loadClass(typeName); } catch (ClassNotFoundException clsNotFound) { MessageObject errMsgObj = getErrorMsg(clsNotFound.getMessage(), true); errorList.add(errMsgObj); } if (typeClass == null) { continue; } if (!typeClass.isInterface() && !typeClass.isAnnotation() && !typeClass.isEnum()) { try { typeClass.getDeclaredConstructor(new Class[0]); } catch (Exception ex) { noArgConstructorTypes.add(typeName); } int typeModifiers = typeClass.getModifiers(); if (Modifier.isFinal(typeModifiers)) { finalTypes.add(typeName); } } } if (noArgConstructorTypes.size() > 0) { StringBuilder strErrMsg = new StringBuilder(); strErrMsg.append("\nFollowing java beans classes doesn't have default no-arg constructor : ") .append(noArgConstructorTypes.toString()).append("\n"); MessageObject errMsgObj = getErrorMsg(strErrMsg.toString(), true); errorList.add(errMsgObj); } /* Commented for bug 495825 if (finalTypes.size() > 0) { StringBuilder strErrMsg = new StringBuilder(); strErrMsg.append("Following java beans classes are declared as 'final' : ") .append(finalTypes.toString()).append("\n"); MessageObject errMsgObj = getErrorMsg(strErrMsg.toString(), true); errorList.add(errMsgObj); } */ } public static List<MessageObject> validateClassForService( String qualifiedClassName) throws PreValidationFailedException { Class clazz = null; try { clazz = IntrospectUtil.loadClass(qualifiedClassName); } catch (ClassNotFoundException clsNotFound) { throw new PreValidationFailedException(clsNotFound.getMessage(), clsNotFound); } return validateClassForService(clazz); } public static List<MessageObject> validateClassForService( Class clazz) throws PreValidationFailedException { List<MessageObject> errorList = new ArrayList<MessageObject>(); if (clazz.isInterface() || clazz.isEnum()) { String className = clazz.getName(); String errorMsg = className + " : is not a class"; MessageObject errMsgObj = getErrorMsg(errorMsg, true); errorList.add(errMsgObj); } Method[] methods = IntrospectUtil.getPublicInstanceMethods(clazz); return validateMethods(methods, errorList); } public static List<MessageObject> validateClassForService( String qualifiedClassName, List<String> exposedMethodNames) throws PreValidationFailedException { Class clazz = null; try { clazz = IntrospectUtil.loadClass(qualifiedClassName); } catch (ClassNotFoundException clsNotFound) { throw new PreValidationFailedException(clsNotFound.getMessage(), clsNotFound); } if (exposedMethodNames == null || exposedMethodNames.isEmpty()) { // Expose all non-static public methods return validateClassForService(clazz); } else { List<MessageObject> errorList = new ArrayList<MessageObject>(); Set<String> exposedMethodNameSet = new HashSet<String>(exposedMethodNames); Method[] methods = IntrospectUtil.getMatchedMethods(clazz, exposedMethodNameSet); return validateMethods(methods, errorList); } } public static List<MessageObject> validateInterfaceDef( InterfaceDefType interfaceDefType) throws PreValidationFailedException { List<MessageObject> errorList = new ArrayList<MessageObject>(); if (interfaceDefType.getInterfaceName() == null || interfaceDefType.getInterfaceName().length() == 0) { MessageObject errMsg = getErrorMsg("Interface name cannot be empty."); errorList.add(errMsg); } MethodDefListType methodDefListType = interfaceDefType.getMethodDefList(); List<MethodDefType> methodDefList = methodDefListType.getMethodDef(); if (methodDefList == null || methodDefList.size() == 0) { String errorMsg = "Invalid interface, method definitions are empty"; MessageObject errMsg = getErrorMsg(errorMsg); errorList.add(errMsg); } else { Set<String> methodNameSet = new HashSet<String>(methodDefList.size()); for (MethodDefType methodDefType : methodDefList) { String methodName = methodDefType.getMethodName(); if (!methodNameSet.contains(methodName)) { methodNameSet.add(methodName); } else { MessageObject overloadedMethodErrMsg = getOverloadedMethodMsg(methodName); errorList.add(overloadedMethodErrMsg); } String returnTypeClassName = methodDefType.getOutputType(); Class returnTypeClass = null; List<Class> inoutClassList = new ArrayList<Class>(); if (CodeGenUtil.isEmptyString(returnTypeClassName)) { errorList.add(getErrorMsg("Method return type cannot be empty for method : " + methodName)); } else if (!isPrimitiveType(returnTypeClassName)) { try { returnTypeClass = IntrospectUtil.loadClass(returnTypeClassName); inoutClassList.add(returnTypeClass); } catch (ClassNotFoundException clsNotFoundEx) { errorList.add(getErrorMsg( methodName + "'s return type class not found: " + returnTypeClassName)); } } List<InputParamType> inParamList = methodDefType.getInputType(); for (InputParamType inParamType : inParamList) { String paramType = inParamType.getParamType(); if (!isPrimitiveType(paramType)) { try { Class paramTypeClass = IntrospectUtil.loadClass(paramType); inoutClassList.add(paramTypeClass); } catch (ClassNotFoundException clsNotFoundEx) { errorList.add(getErrorMsg(methodName + "'s parameter type class not found : " + paramType)); } } } if (inoutClassList.size() > 0) { Class[] inoutTypes = inoutClassList.toArray(new Class[0]); if (hasCollectionType(inoutTypes)) { errorList.add(getCollectionTypesMsg(methodName)); } } } } return errorList; } public static List<MessageObject> validateServiceImpl( String qualifiedSvcImplName, String qualifiedSvcInterfaceName) throws PreValidationFailedException { // Sanity checks if (CodeGenUtil.isEmptyString(qualifiedSvcImplName)) { throw new PreValidationFailedException("Service Impl class name is empty."); } else if (CodeGenUtil.isEmptyString(qualifiedSvcInterfaceName)) { throw new PreValidationFailedException("Service Interface class name is empty."); } Class svcImplClass = null; Class svcInterfaceClass = null; try { svcImplClass = IntrospectUtil.loadClass(qualifiedSvcImplName); } catch (ClassNotFoundException clsNotFound) { throw new PreValidationFailedException( "Unable to load : " + qualifiedSvcImplName, clsNotFound); } try { svcInterfaceClass = IntrospectUtil.loadClass(qualifiedSvcInterfaceName); } catch (ClassNotFoundException clsNotFound) { throw new PreValidationFailedException( "Unable to load : " + qualifiedSvcInterfaceName, clsNotFound); } return validateServiceImpl(svcImplClass, svcInterfaceClass); } public static List<MessageObject> validateServiceImpl( Class svcImplClass, Class svcInterfaceClass) throws PreValidationFailedException { // Sanity checks if (svcImplClass == null) { throw new PreValidationFailedException("Service Impl class is null"); } else if (svcInterfaceClass == null) { throw new PreValidationFailedException("Service Interface class is null"); } List<MessageObject> errMsgList = new ArrayList<MessageObject>(); if (!svcInterfaceClass.isInterface()) { MessageObject msg = getErrorMsg(svcInterfaceClass.getName() + " : is not an interface"); errMsgList.add(msg); } if (svcImplClass.isInterface() || svcImplClass.isEnum()) { MessageObject msg = getErrorMsg(svcImplClass.getName() + " : is not a class"); errMsgList.add(msg); } else if (Modifier.isAbstract(svcImplClass.getModifiers())) { MessageObject msg = getErrorMsg(svcImplClass.getName() + " : must be a concrete class"); errMsgList.add(msg); } else { // Check whether Srevice Impl implementing given Service Interface Class[] superInterfaces = svcImplClass.getInterfaces(); boolean isImplementsInterface = false; for (Class interfaceClass : superInterfaces) { if (interfaceClass == svcInterfaceClass) { isImplementsInterface = true; break; } } if (!isImplementsInterface) { MessageObject msg = getErrorMsg(svcImplClass.getName() + " : must implement " + svcInterfaceClass.getName()); errMsgList.add(msg); } } return errMsgList; } private static List<MessageObject> validateMethods( Method[] methods, List<MessageObject> errorList) { if (methods == null || methods.length == 0) { MessageObject noMethodsMsg = getErrorMsg("Invalid interface, no methods found."); errorList.add(noMethodsMsg); return errorList; } Set<String> methodNameSet = new HashSet<String>(methods.length); for (Method method : methods) { String methodName = method.getName(); // same method name exist, overloaded methods found. if (!methodNameSet.contains(methodName)) { methodNameSet.add(methodName); } else { MessageObject overloadedMethodErrMsg = getOverloadedMethodMsg(methodName); //codegen should not proceed in case of overloaded methods overloadedMethodErrMsg.setIsFatalError(true); errorList.add(overloadedMethodErrMsg); } validateParamAndReturnTypes(method, errorList); } return errorList; } private static List<MessageObject> validateParamAndReturnTypes( Method method, List<MessageObject> errorList) { String methodName = method.getName(); Class[] paramTypes = method.getParameterTypes(); if (paramTypes.length > 1) { MessageObject multiParamsMsg = getMultipleParamsMsg(methodName); errorList.add(multiParamsMsg); } Class[] allTypes = combineTypes(method); if (hasCollectionType(allTypes)) { MessageObject collectionErrMsg = getCollectionTypesMsg(methodName); Type[] allGenericTypes = combineGenericTypes(method); if (hasNestedCollections(allGenericTypes)) { MessageObject nestedCollectionErrMsg = getNestedCollectionTypesMsg(methodName); errorList.add(nestedCollectionErrMsg); } else { errorList.add(collectionErrMsg); } } return errorList; } private static boolean isPrimitiveType(String typeName) { if (typeName == null || typeName.length() == 0) { return false; } else { return s_primitiveTypes.contains(typeName); } } private static MessageObject getErrorMsg(String errMsg) { return new MessageObject(errMsg); } private static MessageObject getErrorMsg(String errMsg, boolean isFatal) { return new MessageObject(errMsg, isFatal); } private static MessageObject getOverloadedMethodMsg(String methodName) { MessageObject msgObj = new MessageObject( methodName, OVERLOADED_METHOD_MSG); return msgObj; } private static MessageObject getMultipleParamsMsg(String methodName) { MessageObject msgObj = new MessageObject( methodName, MULTIPLE_PARAMS_MSG); return msgObj; } private static MessageObject getCollectionTypesMsg(String methodName) { MessageObject msgObj = new MessageObject( methodName, COLLECTION_TYPES_MSG, ""); return msgObj; } private static MessageObject getNestedCollectionTypesMsg(String methodName) { MessageObject msgObj = new MessageObject( methodName, NESTED_COLLECTION_TYPES_MSG, ""); return msgObj; } private static boolean hasNestedCollections(Type[] types) { if (types == null || types.length == 0) { return false; } int nestedCount = 0; boolean hasNestedCollections = false; for (Type type : types) { nestedCount = 0; if (IntrospectUtil.isParameterizedType(type)) { ParameterizedType paramType = (ParameterizedType) type; if (IntrospectUtil.isCollectionType((Class)paramType.getRawType())) { nestedCount++; } Type[] actualTypes = paramType.getActualTypeArguments(); for (Type actualType : actualTypes) { if (IntrospectUtil.isParameterizedType(actualType)) { ParameterizedType actualParamType = (ParameterizedType) actualType; if (IntrospectUtil.isCollectionType((Class)actualParamType.getRawType())) { nestedCount++; } } else if (IntrospectUtil.isCollectionType((Class) actualType)) { nestedCount++; } } } if (nestedCount > 1) { hasNestedCollections = true; break; } } return hasNestedCollections; } private static boolean hasCollectionType(Class[] types) { boolean hasCollectionType = false; for (Class typeClazz : types) { if (IntrospectUtil.isCollectionType(typeClazz)) { hasCollectionType = true; break; } } return hasCollectionType; } private static Class[] combineTypes(Method method) { Class[] paramTypes = method.getParameterTypes(); Class returnType = method.getReturnType(); Class[] allTypes = new Class[paramTypes.length+1]; System.arraycopy(paramTypes, 0, allTypes, 0, paramTypes.length); allTypes[paramTypes.length] = returnType; return allTypes; } private static Type[] combineGenericTypes(Method method) { Type[] paramTypes = method.getGenericParameterTypes(); Type returnType = method.getGenericReturnType(); Type[] allTypes = new Type[paramTypes.length+1]; System.arraycopy(paramTypes, 0, allTypes, 0, paramTypes.length); allTypes[paramTypes.length] = returnType; return allTypes; } }