Vojtech Szocs has uploaded a new change for review. Change subject: core: Add GWT RPC serializable check ......................................................................
core: Add GWT RPC serializable check This patch adds GwtRpcSerializableCheck to ensure that common classes which participate in GWT RPC calls meet standard rules for Serializable User-defined Classes [*]: * is assignable to Serializable or IsSerializable interface, either because it directly implements one of these interfaces or because it derives from a superclass that does * all non-final, non-transient instance fields are themselves serializable * has a default (zero argument) constructor (with any access modifier) or no constructor at all [*] http://www.gwtproject.org/doc/latest/ DevGuideServerCommunication.html#DevGuideSerializableTypes GwtRpcSerializableCheck applies to 'common' module and checks all classes assignable to following root types: * VdcQueryParametersBase [all query parameters] * VdcActionParametersBase [all action parameters] * VdcUser [used in GenericApiGWTService.logOff method] * BusinessEntity [all business entities] GwtRpcSerializableCheck uses Java Reflection API instead of Checkstyle's AST Token API. This is because of advanced class introspection (i.e. check if given class implements interface X either directly or indirectly) required by this check. GwtRpcSerializableCheck makes best effort not to load/check given class more than once. Change-Id: I51c87efc432444226daa7a18be047435d21b84ef Signed-off-by: Vojtech Szocs <vsz...@redhat.com> --- M backend/manager/modules/common/pom.xml M build-tools-root/checkstyles/src/main/resources/checkstyle.xml A build-tools-root/ovirt-checkstyle-extension/src/main/java/checks/GwtRpcSerializableCheck.java 3 files changed, 190 insertions(+), 0 deletions(-) git pull ssh://gerrit.ovirt.org:29418/ovirt-engine refs/changes/10/18910/1 diff --git a/backend/manager/modules/common/pom.xml b/backend/manager/modules/common/pom.xml index a184fa2..d94be7a 100644 --- a/backend/manager/modules/common/pom.xml +++ b/backend/manager/modules/common/pom.xml @@ -68,6 +68,7 @@ <configuration> <propertyExpansion>disallowFinals=true</propertyExpansion> <propertyExpansion>disallowMemberInit=true</propertyExpansion> + <propertyExpansion>runGwtRpcSerializableCheck=true</propertyExpansion> </configuration> </plugin> <plugin> diff --git a/build-tools-root/checkstyles/src/main/resources/checkstyle.xml b/build-tools-root/checkstyles/src/main/resources/checkstyle.xml index 77fc4a6..09831fd 100644 --- a/build-tools-root/checkstyles/src/main/resources/checkstyle.xml +++ b/build-tools-root/checkstyles/src/main/resources/checkstyle.xml @@ -28,5 +28,8 @@ <module name="checks.NoMemberInitializationCheck"> <property name="run" value="${disallowMemberInit}" default="false"/> </module> + <module name="checks.GwtRpcSerializableCheck"> + <property name="run" value="${runGwtRpcSerializableCheck}" default="false"/> + </module> </module> </module> diff --git a/build-tools-root/ovirt-checkstyle-extension/src/main/java/checks/GwtRpcSerializableCheck.java b/build-tools-root/ovirt-checkstyle-extension/src/main/java/checks/GwtRpcSerializableCheck.java new file mode 100644 index 0000000..8d7e6f4 --- /dev/null +++ b/build-tools-root/ovirt-checkstyle-extension/src/main/java/checks/GwtRpcSerializableCheck.java @@ -0,0 +1,186 @@ +package checks; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import com.puppycrawl.tools.checkstyle.api.DetailAST; +import com.puppycrawl.tools.checkstyle.api.TokenTypes; +import com.puppycrawl.tools.checkstyle.checks.AbstractTypeAwareCheck; + +/** + * Checks that classes meet GWT RPC rules for <em>Serializable User-defined Classes</em>: + * <ul> + * <li>is assignable to Serializable or IsSerializable interface, either because it directly implements one of these + * interfaces or because it derives from a superclass that does</li> + * <li>all non-final, non-transient instance fields are themselves serializable</li> + * <li>has a default (zero argument) constructor (with any access modifier) or no constructor at all</li> + * </ul> + */ +public class GwtRpcSerializableCheck extends AbstractTypeAwareCheck { + + /** + * Root types whose subclasses should be checked: + * <ul> + * <li>type is used in GWT RPC service method signature</li> + * <li>type is used at runtime (cannot be easily determined through static analysis)</li> + * </ul> + */ + private static final String[] RPC_SERIALIZABLE_ROOTS = { + "org.ovirt.engine.core.common.queries.VdcQueryParametersBase", + "org.ovirt.engine.core.common.action.VdcActionParametersBase", + "org.ovirt.engine.core.common.users.VdcUser", + "org.ovirt.engine.core.common.businessentities.BusinessEntity" + }; + + /** + * Types to exclude from checking in addition to <code>java.lang.*</code> classes: + * <ul> + * <li>type serialization is provided by GWT RPC out of the box</li> + * <li>type serialization is provided by custom field serializer</li> + * </ul> + */ + private static final String[] RPC_SERIALIZABLE_EXCLUDES = { + "java.util.Date", "java.math.BigDecimal", "java.math.BigInteger", + "java.util.UUID" + }; + + public static final String JAVA_SERIALIZABLE = "java.io.Serializable"; + public static final String GWT_ISSERIALIZABLE = "com.google.gwt.user.client.rpc.IsSerializable"; + + private final Map<String, Class<?>> typeCache = new HashMap<String, Class<?>>(); + private final Set<String> typesChecked = new HashSet<String>(); + + private boolean run = true; + + public void setRun(boolean run) { + this.run = run; + } + + @Override + public int[] getDefaultTokens() { + return new int[] { + // Processed by AbstractTypeAwareCheck + TokenTypes.PACKAGE_DEF, TokenTypes.IMPORT, + TokenTypes.CLASS_DEF, TokenTypes.ENUM_DEF, + // Processed by this check + TokenTypes.OBJBLOCK + }; + } + + @Override + public int[] getAcceptableTokens() { + return new int[] { + TokenTypes.OBJBLOCK + }; + } + + @Override + protected void processAST(DetailAST ast) { + // Expect object block enclosed by class declaration + if (!run || ast.getType() != TokenTypes.OBJBLOCK || ast.getParent().getType() != TokenTypes.CLASS_DEF) { + return; + } + + // Check class enclosing current object block + Class<?> clazz = resolveClass(getCurrentClassName()); + if (clazz != null && isUserDefinedClass(clazz) && isRootTypeSubclass(clazz)) { + performCheck(clazz); + } + } + + @Override + protected void logLoadError(Token ident) { + logLoadErrorImpl(ident.getLineNo(), ident.getColumnNo(), + "Cannot load class {0}", ident.getText()); + } + + Class<?> resolveClass(String name) { + if (!typeCache.containsKey(name)) { + Class<?> clazz = resolveClass(name, ""); + typeCache.put(name, clazz); + } + return typeCache.get(name); + } + + boolean isJavaLangClass(Class<?> clazz) { + return clazz.getPackage().getName().startsWith("java.lang"); + } + + boolean isExcludedClass(Class<?> clazz) { + for (String exclude : RPC_SERIALIZABLE_EXCLUDES) { + if (clazz.getName().equals(exclude)) { + return true; + } + } + return false; + } + + boolean isUserDefinedClass(Class<?> clazz) { + return !clazz.isPrimitive() && !clazz.isArray() && !clazz.isEnum() && !clazz.isAnnotation() + && !clazz.isInterface() && !isJavaLangClass(clazz) && !isExcludedClass(clazz); + } + + boolean isRootTypeSubclass(Class<?> clazz) { + for (String root : RPC_SERIALIZABLE_ROOTS) { + if (isSubclass(clazz, resolveClass(root))) { + return true; + } + } + return false; + } + + void performCheck(Class<?> clazz) { + // Skip class if already checked + if (typesChecked.contains(clazz.getName())) { + return; + } + + // 1. is assignable to Serializable or IsSerializable interface, either because it directly + // implements one of these interfaces or because it derives from a superclass that does + if (!isSubclass(clazz, resolveClass(JAVA_SERIALIZABLE)) + && !isSubclass(clazz, resolveClass(GWT_ISSERIALIZABLE))) { + log(0, "Class {0} must be assignable to " + JAVA_SERIALIZABLE + + " or " + GWT_ISSERIALIZABLE + " interface", clazz.getName()); + } + + // 2. all non-final, non-transient instance fields are themselves serializable + for (Field field : clazz.getDeclaredFields()) { + int mods = field.getModifiers(); + if (!Modifier.isStatic(mods) && !Modifier.isFinal(mods) && !Modifier.isTransient(mods)) { + Class<?> fieldType = field.getType(); + if (isUserDefinedClass(fieldType)) { + // Check field's declared type + performCheck(fieldType); + } else if (fieldType.isArray()) { + Class<?> componentType = fieldType.getComponentType(); + if (isUserDefinedClass(componentType)) { + // Check field's array component type + performCheck(componentType); + } + } + } + } + + // 3. has a default (zero argument) constructor (with any access modifier) or no constructor at all + boolean foundDefaultConstructor = false; + for (Constructor<?> ctor : clazz.getDeclaredConstructors()) { + if (ctor.getParameterTypes().length == 0) { + foundDefaultConstructor = true; + break; + } + } + if (!foundDefaultConstructor) { + log(0, "Class {0} must have a default (zero argument) constructor" + + " (with any access modifier) or no constructor at all", clazz.getName()); + } + + // Mark class as checked + typesChecked.add(clazz.getName()); + } + +} -- To view, visit http://gerrit.ovirt.org/18910 To unsubscribe, visit http://gerrit.ovirt.org/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I51c87efc432444226daa7a18be047435d21b84ef Gerrit-PatchSet: 1 Gerrit-Project: ovirt-engine Gerrit-Branch: master Gerrit-Owner: Vojtech Szocs <vsz...@redhat.com> _______________________________________________ Engine-patches mailing list Engine-patches@ovirt.org http://lists.ovirt.org/mailman/listinfo/engine-patches