/** * Start time:19:32:15 2009-02-05<br> * Project: mobicents-jainslee-server-core<br> * * @author <a href="mailto:baranowb@gmail.com">baranowb - Bartosz Baranowski * </a> * @author <a href="mailto:brainslog@gmail.com"> Alexandre Mendonca </a> */ package org.mobicents.slee.container.component.validator; import java.beans.Introspector; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Map.Entry; import javax.slee.ComponentID; import javax.slee.usage.UnrecognizedUsageParameterSetNameException; import org.apache.log4j.Logger; import org.mobicents.slee.container.component.ProfileSpecificationComponent; import org.mobicents.slee.container.component.ResourceAdaptorComponent; import org.mobicents.slee.container.component.SbbComponent; import org.mobicents.slee.container.component.deployment.jaxb.descriptors.common.MUsageParameter; /** * Start time:19:32:15 2009-02-05<br> * Project: mobicents-jainslee-server-core<br> * * This class is place where common elements like usage parameter interface is * validated. In 1.1 specs its widely usable - ra, profiles, sbbs. In 1.0 only * sbbs have those. * * @author <a href="mailto:baranowb@gmail.com">baranowb - Bartosz Baranowski * </a> * @author <a href="mailto:brainslog@gmail.com"> Alexandre Mendonca </a> */ public class UsageInterfaceValidator { private final static String _INCREMENT_METHOD_PREFIX = "increment"; private final static String _GET_METHOD_PREFIX = "get"; private final static String _SAMPLE_METHOD_PREFIX = "sample"; private final static Set<String> _FORBBIDEN_WORDS; static { Set<String> tmp = new HashSet<String>(); tmp.add("static"); tmp.add("final"); tmp.add("protected"); tmp.add("public"); tmp.add("native"); tmp.add("finally"); tmp.add("throw"); tmp.add("catch"); tmp.add("try"); tmp.add("int"); tmp.add("char"); tmp.add("boolean"); tmp.add("long"); tmp.add("float"); tmp.add("double"); tmp.add("false"); tmp.add("true"); tmp.add("break"); tmp.add("byte"); tmp.add("case"); tmp.add("class"); tmp.add("continue"); tmp.add("default"); tmp.add("do"); tmp.add("else"); tmp.add("extends"); tmp.add("implements"); tmp.add("for"); tmp.add("if"); tmp.add("implements"); tmp.add("import"); tmp.add("instanceof"); tmp.add("interface"); tmp.add("new"); tmp.add("package"); tmp.add("protected"); tmp.add("return"); tmp.add("short"); tmp.add("super"); tmp.add("switch"); tmp.add("this"); tmp.add("throws"); tmp.add("transient"); tmp.add("void"); tmp.add("volatile"); tmp.add("while"); _FORBBIDEN_WORDS = (Set<String>) Collections.unmodifiableSet(tmp); } private static transient Logger logger = Logger.getLogger(UsageInterfaceValidator.class); /** * This methods validate component which has usage parameter interface. Its * interface for methods. In case of 1.1 components parameters list must * match evey method defined. In case of 1.0 components parameters list MUST * be empty. It does not validate get usage method, those * * @param isSlee11 * * @param usageInterface * - interface class itself * @param parameters * - list of parameters, in case of 1.0 sbb this MUST be null. * @return */ static boolean validateUsageParameterInterface(ComponentID id, boolean isSlee11, Class usageInterface, List<MUsageParameter> parameters) { boolean passed = true; String errorBuffer = new String(""); try { if (!usageInterface.isInterface()) { passed = false; errorBuffer = appendToBuffer(id, "Usage parameter interface class is not an interface", "11.2", errorBuffer); return passed; } // Interface constraints if (isSlee11 && usageInterface.getPackage() == null) { passed = false; errorBuffer = appendToBuffer(id, "Usage parameter interface must be declared in pacakge name space.", "11.2", errorBuffer); } if (!Modifier.isPublic(usageInterface.getModifiers())) { passed = false; errorBuffer = appendToBuffer(id, "Usage parameter interface must be declared as public.", "11.2", errorBuffer); } // parameters check HashMap<String, String> parameterNameToparameterType = new HashMap<String, String>(); Set<String> ignore = new HashSet<String>(); ignore.add("java.lang.Object"); Map<String, Method> interfaceMethods = ClassUtils.getAllInterfacesMethods(usageInterface, ignore); Map<String, MUsageParameter> localParametersMap = new HashMap<String, MUsageParameter>(); Set<String> identifiedIncrement = new HashSet<String>(); Set<String> identifiedGetIncrement = new HashSet<String>(); Set<String> identifiedSample = new HashSet<String>(); Set<String> identifiedGetSample = new HashSet<String>(); // this is for 1.1, get and increment methods must match with type // validate parameter names if we are slee11 if (isSlee11) for (MUsageParameter usage : parameters) { char c = usage.getName().charAt(0); if (!Character.isLowerCase(c)) { passed = false; errorBuffer = appendToBuffer(id, "Parameter name must start with lower case character and be start of valid jva identifier, parameter name from descriptor: " + usage.getName(), "11.2", errorBuffer); } localParametersMap.put(usage.getName(), usage); } // at the end we have to have empty list for (Entry<String, Method> entry : interfaceMethods.entrySet()) { // String declaredLongMethodName = entry.getKey(); Method m = entry.getValue(); String declaredMethodName = m.getName(); String declaredPrameterName = null; Character c = null; // he we just do checks, methods against constraints // we remove them from parameters map, there is something left // or not present in map in case of 1.1 // some variable that we need to store info about method boolean isIncrement = false; boolean isGetIncrement = false; boolean isGetSample = false; boolean isSample = false; // 1.0 comp if (declaredMethodName.startsWith(_SAMPLE_METHOD_PREFIX)) { declaredPrameterName = declaredMethodName.replaceFirst(_SAMPLE_METHOD_PREFIX, ""); c = declaredPrameterName.charAt(0); isSample = true; // 1.0 comp } else if (declaredMethodName.startsWith(_INCREMENT_METHOD_PREFIX)) { declaredPrameterName = declaredMethodName.replaceFirst(_INCREMENT_METHOD_PREFIX, ""); c = declaredPrameterName.charAt(0); isIncrement = true; // 1.1 only } else if (declaredMethodName.startsWith(_GET_METHOD_PREFIX)) { declaredPrameterName = declaredMethodName.replaceFirst(_GET_METHOD_PREFIX, ""); c = declaredPrameterName.charAt(0); if (!isSlee11) { passed = false; errorBuffer = appendToBuffer(id, "Wrong method declared in parameter usage interface. Get method for counter parameter types are allowed only in JSLEE 1.1, method: " + declaredMethodName, "11.2.X", errorBuffer); } if (m.getReturnType().getName().compareTo("javax.slee.usage.SampleStatistics") == 0) { isGetSample = true; } else { // we asume thats increment get isGetIncrement = true; } } else { passed = false; errorBuffer = appendToBuffer(id, "Wrong method decalred in parameter usage interface. Methods must start with either \"get\", \"sample\" or \"increment\", method: " + declaredMethodName, "11.2.X", errorBuffer); continue; } if (!Character.isUpperCase(c)) { passed = false; errorBuffer = appendToBuffer(id, "Method stripped of prefix, either \"get\", \"sample\" or \"increment\", must have following upper case character,method: " + declaredMethodName, "11.2", errorBuffer); } declaredPrameterName = Introspector.decapitalize(declaredPrameterName); if (!isValidJavaIdentifier(declaredPrameterName)) { passed = false; errorBuffer = appendToBuffer(id, "Parameter name must be valid java identifier: " + declaredPrameterName, "11.2", errorBuffer); } // well we have indentified parameter, lets store; if (isIncrement) { if (identifiedIncrement.contains(declaredMethodName)) { passed = false; errorBuffer = appendToBuffer(id, "Duplicate declaration of usage parameter, possibly twe methods with the same name and different signature, method: " + declaredMethodName, "11.2", errorBuffer); } else { identifiedIncrement.add(declaredPrameterName); if (!validateParameterSetterSignatureMethod(id, m, "11.2.3")) { passed = false; } } } else if (isGetIncrement) { if (identifiedGetIncrement.contains(declaredMethodName)) { passed = false; errorBuffer = appendToBuffer(id, "Duplicate declaration of usage parameter, possibly twe methods with the same name and different signature, method: " + declaredMethodName, "11.2", errorBuffer); } else { identifiedGetIncrement.add(declaredPrameterName); if (!validateParameterGetterSignatureMethod(id, m, "11.2.2", Long.TYPE)) { passed = false; } } } else if (isGetSample) { if (identifiedGetSample.contains(declaredMethodName)) { passed = false; errorBuffer = appendToBuffer(id, "Duplicate declaration of usage parameter, possibly twe methods with the same name and different signature, method: " + declaredMethodName, "11.2", errorBuffer); } else { identifiedGetSample.add(declaredPrameterName); if (!validateParameterGetterSignatureMethod(id, m, "11.2.4", javax.slee.usage.SampleStatistics.class)) { passed = false; } } } else if (isSample) { if (identifiedSample.contains(declaredMethodName)) { passed = false; errorBuffer = appendToBuffer(id, "Duplicate declaration of usage parameter, possibly twe methods with the same name and different signature, method: " + declaredMethodName, "11.2", errorBuffer); } else { identifiedSample.add(declaredPrameterName); if (!validateParameterSetterSignatureMethod(id, m, "11.2.1")) { passed = false; errorBuffer = appendToBuffer(id, "Duplicate declaration of usage parameter, possibly twe methods with the same name and different signature, method: " + declaredMethodName, "11.2", errorBuffer); } } } // UFFF, lets start the play // /uh its a bit complicated } // we siganture here is ok, return types also, no duplicates, left: // 1. cross check field types that we found - sample vs increments - // there cant be doubles // 2. remove all from list, if something is left, bam, we lack one // method or have to many :) Set<String> agregatedIncrement = new HashSet<String>(); Set<String> agregatedSample = new HashSet<String>(); agregatedIncrement.addAll(identifiedGetIncrement); agregatedIncrement.addAll(identifiedIncrement); agregatedSample.addAll(identifiedGetSample); agregatedSample.addAll(identifiedSample); Set<String> tmp = new HashSet<String>(agregatedSample); tmp.retainAll(agregatedIncrement); if (tmp.size() > 0) { // ugh, its the end passed = false; errorBuffer = appendToBuffer(id, "Usage parameters can be associated only with single type - increment or sample, offending parameters: " + Arrays.toString(tmp.toArray()), "11.2", errorBuffer); return passed; } if (isSlee11) { tmp.clear(); tmp.addAll(agregatedSample); tmp.addAll(agregatedIncrement); // localParametersMap.size()!=0 - cause we can have zero of them // - usage-parameter may not be present so its generation is // turned off if (localParametersMap.size() != tmp.size() && localParametersMap.size() != 0) { passed = false; String errorPart = null; if (localParametersMap.size() > tmp.size()) { // is there any bettter way? for (String s : localParametersMap.keySet()) tmp.remove(s); errorPart = "More parameters are defined in descriptor, offending parameters: " + Arrays.toString(tmp.toArray()); } else { for (String s : tmp) localParametersMap.remove(s); errorPart = "More parameters are defined in descriptor, offending parameters: " + Arrays.toString(localParametersMap.keySet().toArray()); } errorBuffer = appendToBuffer(id, "Failed to map descriptor defined usage parameters against interface class methods. " + errorPart, "11.2", errorBuffer); } } } finally { if (!passed) { logger.error(errorBuffer.toString()); // System.err.println(errorBuffer); } } return passed; } private static boolean isValidJavaIdentifier(String declaredPrameterName) { for (int i = 0; i < declaredPrameterName.length(); i++) { char ch = declaredPrameterName.charAt(i); if (i == 0) { // we allow only alpha and _ ?? if (Character.isLetter(ch) || ch == "_".charAt(0)) { } else { if (logger.isDebugEnabled()) { logger.debug("Usage parameter: " + declaredPrameterName + " is not valid java identifier: " + new Character(ch)); } return false; } } else { if (!Character.isJavaIdentifierPart(ch)) { if (logger.isDebugEnabled()) { logger.debug("Usage parameter: " + declaredPrameterName + " is not valid java identifier: " + new Character(ch)); } return false; } } } if (_FORBBIDEN_WORDS.contains(declaredPrameterName)) { if (logger.isDebugEnabled()) { logger.debug("Usage parameter: " + declaredPrameterName + " is not valid java identifier, its forbbied java keyword."); } return false; } return true; } static boolean validateParameterGetterSignatureMethod(ComponentID id, Method m, String section, Class returnType) { boolean passed = true; String errorBuffer = new String(""); try { // public, abstract, void, no throws, long parameter int modifiers = m.getModifiers(); if (!Modifier.isPublic(modifiers)) { passed = false; errorBuffer = appendToBuffer(id, "Usage paremter method must be declared public, method: " + m, section, errorBuffer); } if (!Modifier.isAbstract(modifiers)) { passed = false; errorBuffer = appendToBuffer(id, "Usage paremter method must be declared abstract, method: " + m, section, errorBuffer); } if (m.getExceptionTypes().length > 0) { passed = false; errorBuffer = appendToBuffer(id, "Usage paremter method must not declared throws clause, method: " + m, section, errorBuffer); } if (m.getReturnType().getName().compareTo(returnType.getName()) != 0) { passed = false; errorBuffer = appendToBuffer(id, "Usage paremter method must not declare return type, method: " + m, section, errorBuffer); } Class[] params = new Class[] {}; if (!Arrays.equals(m.getParameterTypes(), params)) { passed = false; errorBuffer = appendToBuffer(id, "Usage paremter method must not have parameters, method: " + m, section, errorBuffer); } } finally { if (!passed) { logger.error(errorBuffer.toString()); // ////System.err.println(errorBuffer); } } return passed; } static boolean validateParameterSetterSignatureMethod(ComponentID id, Method m, String section) { boolean passed = true; String errorBuffer = new String(""); try { // public, abstract, void, no throws, long parameter int modifiers = m.getModifiers(); if (!Modifier.isPublic(modifiers)) { passed = false; errorBuffer = appendToBuffer(id, "Usage paremter method must be declared public, method: " + m, section, errorBuffer); } if (!Modifier.isAbstract(modifiers)) { passed = false; errorBuffer = appendToBuffer(id, "Usage paremter method must be declared abstract, method: " + m, section, errorBuffer); } if (m.getExceptionTypes().length > 0) { passed = false; errorBuffer = appendToBuffer(id, "Usage paremter method must not declared throws clause, method: " + m, section, errorBuffer); } if (m.getReturnType().getName().compareTo("void") != 0) { passed = false; errorBuffer = appendToBuffer(id, "Usage paremter method must not declare return type, method: " + m, section, errorBuffer); } Class[] params = new Class[] { Long.TYPE }; if (!Arrays.equals(m.getParameterTypes(), params)) { passed = false; errorBuffer = appendToBuffer(id, "Usage paremter method must have single parameter of type long, method: " + m, section, errorBuffer); } } finally { if (!passed) { logger.error(errorBuffer.toString()); // ////System.err.println(errorBuffer); } } return passed; } static boolean validateSbbUsageParameterInterface(SbbComponent component, Map<String, Method> sbbAbstractClassMethods, Map<String, Method> sbbAbstractMethodsFromSuperClasses) { String errorBuffer = new String(""); boolean passed = true; boolean foundAtleastOne = false; String methodName = "getDefaultSbbUsageParameterSet"; Class componentClass = component.getAbstractSbbClass(); Method m = null; m = ClassUtils.getMethodFromMap(methodName, new Class[] {}, sbbAbstractClassMethods, sbbAbstractMethodsFromSuperClasses); if (m != null) { foundAtleastOne = true; if (!validateGetUsageMethodSignature(component.getSbbID(), m, component.getUsageParametersInterface(), new Class[] {}, "8.4.1")) { passed = false; } sbbAbstractClassMethods.remove(ClassUtils.getMethodKey(m)); sbbAbstractMethodsFromSuperClasses.remove(ClassUtils.getMethodKey(m)); } methodName = "getSbbUsageParameterSet"; m = ClassUtils.getMethodFromMap(methodName, new Class[] { String.class }, sbbAbstractClassMethods, sbbAbstractMethodsFromSuperClasses); if (m != null) { foundAtleastOne = true; if (!validateGetUsageMethodSignature(component.getSbbID(), m, component.getUsageParametersInterface(), new Class[] { UnrecognizedUsageParameterSetNameException.class }, "8.4.1")) { passed = false; } sbbAbstractClassMethods.remove(ClassUtils.getMethodKey(m)); sbbAbstractMethodsFromSuperClasses.remove(ClassUtils.getMethodKey(m)); } if (!validateUsageParameterInterface(component.getSbbID(), component.isSlee11(), component.getUsageParametersInterface(), component.getDescriptor().getSbbClasses() .getSbbUsageParametersInterface().getUsageParameter())) { passed = false; } if (!foundAtleastOne) { passed = false; errorBuffer = appendToBuffer(component.getSbbID(), "Atleast one get usage interface method must be present(it must be public).", "8.4.1", errorBuffer); } if (!passed) { logger.error(errorBuffer.toString()); // System.err.println(errorBuffer); } return passed; } static boolean validateGetUsageMethodSignature(ComponentID id, Method m, Class returnType, Class[] exceptions, String section) { // must be public, abstract boolean passed = true; String errorBuffer = new String(""); int modifiers = m.getModifiers(); if (!Modifier.isAbstract(modifiers)) { passed = false; errorBuffer = appendToBuffer(id, "Usage interface access method must be abstract, method name: " + m.getName(), section, errorBuffer); } if (!Modifier.isPublic(modifiers)) { passed = false; errorBuffer = appendToBuffer(id, "Usage interface access method must be public, method name: " + m.getName(), section, errorBuffer); } // if (Modifier.isStatic(modifiers)) { // passed = false; // errorBuffer = // appendToBuffer(id,"Usage interface access method must be abstract is static, method name: " // + m.getName(), // section, errorBuffer); // } // FIXME: native? if (!Arrays.equals(m.getExceptionTypes(), exceptions)) { passed = false; errorBuffer = appendToBuffer(id, "Usage interface access method has wrong exception types defined, method: " + m.getName() + ", allowed: " + Arrays.toString(exceptions) + ", present: " + Arrays.toString(m.getExceptionTypes()), section, errorBuffer); } if (m.getReturnType().getName().compareTo(returnType.getName()) == 0 || ClassUtils.checkInterfaces(returnType, m.getReturnType().getName()) != null) { // ok } else { passed = false; errorBuffer = appendToBuffer(id, "Usage interface access method has wrong return type defined, method: " + m.getName(), section, errorBuffer); } if (!passed) { logger.error(errorBuffer.toString()); // System.err.println(errorBuffer); } return passed; } static boolean validateResourceAdaptorUsageParameterInterface(ResourceAdaptorComponent component) { boolean passed = true; if (!validateUsageParameterInterface(component.getResourceAdaptorID(), component.isSlee11(), component.getUsageParametersInterface(), component.getDescriptor() .getResourceAdaptorUsageParametersInterface().getUsageParameter())) { passed = false; } return passed; } static boolean validateProfileSpecificationUsageParameterInterface(ProfileSpecificationComponent component, Map<String, Method> abstractClassMethods, Map<String, Method> abstractMethodsFromSuperClasses) { String errorBuffer = new String(""); boolean passed = true; boolean foundAtleastOne = false; String methodName = "getDefaultUsageParameterSet"; Class componentClass = component.getProfileAbstractClass(); Method m = null; m = ClassUtils.getMethodFromMap(methodName, new Class[] {}, abstractClassMethods, abstractMethodsFromSuperClasses); if (m != null) { foundAtleastOne = true; if (!validateGetUsageMethodSignature(component.getProfileSpecificationID(), m, component.getUsageParametersInterface(), new Class[] {}, "11.4.2")) { passed = false; } abstractClassMethods.remove(ClassUtils.getMethodKey(m)); abstractMethodsFromSuperClasses.remove(ClassUtils.getMethodKey(m)); } methodName = "getUsageParameterSet"; m = ClassUtils.getMethodFromMap(methodName, new Class[] { String.class }, abstractClassMethods, abstractMethodsFromSuperClasses); if (m != null) { foundAtleastOne = true; if (!validateGetUsageMethodSignature(component.getProfileSpecificationID(), m, component.getUsageParametersInterface(), new Class[] { UnrecognizedUsageParameterSetNameException.class }, "11.4.2")) { passed = false; } abstractClassMethods.remove(ClassUtils.getMethodKey(m)); abstractMethodsFromSuperClasses.remove(ClassUtils.getMethodKey(m)); } if (!validateUsageParameterInterface(component.getProfileSpecificationID(), component.isSlee11(), component.getUsageParametersInterface(), component.getDescriptor().getProfileClasses() .getProfileUsageParameterInterface().getUsageParameter())) { passed = false; } if (!foundAtleastOne) { passed = false; errorBuffer = appendToBuffer(component.getProfileSpecificationID(), "Atleast one get usage interface method must be present(it must be public).", "11.4.2", errorBuffer); } if (!passed) { logger.error(errorBuffer.toString()); // System.err.println(errorBuffer); } return passed; } protected static String appendToBuffer(ComponentID id, String message, String section, String buffer) { buffer += (id + " : violates section " + section + " of jSLEE 1.1 specification : " + message + "\n"); return buffer; } }